mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-29 12:02:56 +02:00
Merge remote-tracking branch 'origin/main' into feature/restore-history
This commit is contained in:
@@ -2,6 +2,7 @@ package deltacomp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
@@ -74,6 +75,18 @@ func ApplyDataDiff(diff []byte, baseData []byte) []byte {
|
||||
}
|
||||
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)
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/Andoryuuta/Erupe/server/channelserver/compression/nullcomp"
|
||||
"erupe-ce/server/channelserver/compression/nullcomp"
|
||||
)
|
||||
|
||||
var tests = []struct {
|
||||
|
||||
@@ -70,10 +70,15 @@ func Compress(rawData []byte) ([]byte, error) {
|
||||
if err == io.EOF {
|
||||
output = append(output, []byte{byte(nullCount)}...)
|
||||
break
|
||||
} else if i != 0 {
|
||||
} 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
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
169
server/channelserver/handlers_achievement.go
Normal file
169
server/channelserver/handlers_achievement.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
var achievementCurves = [][]int32{
|
||||
// 0: HR weapon use, Class use, Tore dailies
|
||||
{5, 15, 30, 50, 100, 150, 200, 300},
|
||||
// 1: Weapon collector, G wep enhances
|
||||
{1, 5, 10, 15, 30, 50, 75, 100},
|
||||
// 2: Festa wins
|
||||
{1, 2, 3, 4, 5, 6, 7, 8},
|
||||
// 3: GR weapon use, Sigil crafts
|
||||
{10, 50, 100, 200, 350, 500, 750, 999},
|
||||
}
|
||||
|
||||
var achievementCurveMap = map[uint8][]int32{
|
||||
0: achievementCurves[0], 1: achievementCurves[0], 2: achievementCurves[0], 3: achievementCurves[0],
|
||||
4: achievementCurves[0], 5: achievementCurves[0], 6: achievementCurves[0], 7: achievementCurves[1],
|
||||
8: achievementCurves[2], 9: achievementCurves[0], 10: achievementCurves[0], 11: achievementCurves[0],
|
||||
12: achievementCurves[0], 13: achievementCurves[0], 14: achievementCurves[0], 15: achievementCurves[0],
|
||||
16: achievementCurves[3], 17: achievementCurves[3], 18: achievementCurves[3], 19: achievementCurves[3],
|
||||
20: achievementCurves[3], 21: achievementCurves[3], 22: achievementCurves[3], 23: achievementCurves[3],
|
||||
24: achievementCurves[3], 25: achievementCurves[3], 26: achievementCurves[3], 27: achievementCurves[1],
|
||||
28: achievementCurves[1], 29: achievementCurves[3], 30: achievementCurves[3], 31: achievementCurves[3],
|
||||
32: achievementCurves[3],
|
||||
}
|
||||
|
||||
type Achievement struct {
|
||||
Level uint8
|
||||
Value uint32
|
||||
NextValue uint16
|
||||
Required uint32
|
||||
Updated bool
|
||||
Progress uint32
|
||||
Trophy uint8
|
||||
}
|
||||
|
||||
func GetAchData(id uint8, score int32) Achievement {
|
||||
curve := achievementCurveMap[id]
|
||||
var ach Achievement
|
||||
for i, v := range curve {
|
||||
temp := score - v
|
||||
if temp < 0 {
|
||||
ach.Progress = uint32(score)
|
||||
ach.Required = uint32(curve[i])
|
||||
switch ach.Level {
|
||||
case 0:
|
||||
ach.NextValue = 5
|
||||
case 1, 2, 3:
|
||||
ach.NextValue = 10
|
||||
case 4, 5:
|
||||
ach.NextValue = 15
|
||||
case 6:
|
||||
ach.NextValue = 15
|
||||
ach.Trophy = 0x40
|
||||
case 7:
|
||||
ach.NextValue = 20
|
||||
ach.Trophy = 0x60
|
||||
}
|
||||
return ach
|
||||
} else {
|
||||
score = temp
|
||||
ach.Level++
|
||||
switch ach.Level {
|
||||
case 1:
|
||||
ach.Value += 5
|
||||
case 2, 3, 4:
|
||||
ach.Value += 10
|
||||
case 5, 6, 7:
|
||||
ach.Value += 15
|
||||
case 8:
|
||||
ach.Value += 20
|
||||
}
|
||||
}
|
||||
}
|
||||
ach.Required = uint32(curve[7])
|
||||
ach.Trophy = 0x7F
|
||||
ach.Progress = ach.Required
|
||||
return ach
|
||||
}
|
||||
|
||||
func handleMsgMhfGetAchievement(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetAchievement)
|
||||
|
||||
var exists int
|
||||
err := s.server.db.QueryRow("SELECT id FROM achievements WHERE id=$1", pkt.CharID).Scan(&exists)
|
||||
if err != nil {
|
||||
s.server.db.Exec("INSERT INTO achievements (id) VALUES ($1)", pkt.CharID)
|
||||
}
|
||||
|
||||
var scores [33]int32
|
||||
err = s.server.db.QueryRow("SELECT * FROM achievements WHERE id=$1", pkt.CharID).Scan(&scores[0],
|
||||
&scores[0], &scores[1], &scores[2], &scores[3], &scores[4], &scores[5], &scores[6], &scores[7], &scores[8],
|
||||
&scores[9], &scores[10], &scores[11], &scores[12], &scores[13], &scores[14], &scores[15], &scores[16],
|
||||
&scores[17], &scores[18], &scores[19], &scores[20], &scores[21], &scores[22], &scores[23], &scores[24],
|
||||
&scores[25], &scores[26], &scores[27], &scores[28], &scores[29], &scores[30], &scores[31], &scores[32])
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 20))
|
||||
return
|
||||
}
|
||||
|
||||
resp := byteframe.NewByteFrame()
|
||||
var points uint32
|
||||
resp.WriteBytes(make([]byte, 16))
|
||||
resp.WriteBytes([]byte{0x02, 0x00, 0x00}) // Unk
|
||||
|
||||
var id uint8
|
||||
entries := uint8(33)
|
||||
resp.WriteUint8(entries) // Entry count
|
||||
for id = 0; id < entries; id++ {
|
||||
achData := GetAchData(id, scores[id])
|
||||
points += achData.Value
|
||||
resp.WriteUint8(id)
|
||||
resp.WriteUint8(achData.Level)
|
||||
resp.WriteUint16(achData.NextValue)
|
||||
resp.WriteUint32(achData.Required)
|
||||
resp.WriteBool(false) // TODO: Notify on rank increase since last checked, see MhfDisplayedAchievement
|
||||
resp.WriteUint8(achData.Trophy)
|
||||
/* Trophy bitfield
|
||||
0000 0000
|
||||
abcd efgh
|
||||
B - Bronze (0x40)
|
||||
B-C - Silver (0x60)
|
||||
B-H - Gold (0x7F)
|
||||
*/
|
||||
resp.WriteUint16(0) // Unk
|
||||
resp.WriteUint32(achData.Progress)
|
||||
}
|
||||
resp.Seek(0, io.SeekStart)
|
||||
resp.WriteUint32(points)
|
||||
resp.WriteUint32(points)
|
||||
resp.WriteUint32(points)
|
||||
resp.WriteUint32(points)
|
||||
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) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAddAchievement)
|
||||
|
||||
var exists int
|
||||
err := s.server.db.QueryRow("SELECT id FROM achievements WHERE id=$1", s.charID).Scan(&exists)
|
||||
if err != nil {
|
||||
s.server.db.Exec("INSERT INTO achievements (id) VALUES ($1)", s.charID)
|
||||
}
|
||||
|
||||
s.server.db.Exec(fmt.Sprintf("UPDATE achievements SET ach%d=ach%d+1 WHERE id=$1", pkt.AchievementID, pkt.AchievementID), s.charID)
|
||||
}
|
||||
|
||||
func handleMsgMhfPaymentAchievement(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfDisplayedAchievement(s *Session, p mhfpacket.MHFPacket) {
|
||||
// This is how you would figure out if the rank-up notification needs to occur
|
||||
}
|
||||
|
||||
func handleMsgMhfGetCaAchievementHist(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfSetCaAchievement(s *Session, p mhfpacket.MHFPacket) {}
|
||||
41
server/channelserver/handlers_bbs.go
Normal file
41
server/channelserver/handlers_bbs.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/stringsupport"
|
||||
"erupe-ce/common/token"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
func handleMsgMhfGetBbsUserStatus(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetBbsUserStatus)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(200)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(0)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfGetBbsSnsStatus(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetBbsSnsStatus)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(200)
|
||||
bf.WriteUint32(401)
|
||||
bf.WriteUint32(401)
|
||||
bf.WriteUint32(0)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfApplyBbsArticle(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfApplyBbsArticle)
|
||||
bf := byteframe.NewByteFrame()
|
||||
articleToken := token.Generate(40)
|
||||
bf.WriteUint32(200)
|
||||
bf.WriteUint32(80)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteBytes(stringsupport.PaddedString(articleToken, 64, false))
|
||||
bf.WriteBytes(stringsupport.PaddedString(s.server.erupeConfig.ScreenshotAPIURL, 64, false))
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
281
server/channelserver/handlers_cafe.go
Normal file
281
server/channelserver/handlers_cafe.go
Normal file
@@ -0,0 +1,281 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/mhfcourse"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
func handleMsgMhfAcquireCafeItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAcquireCafeItem)
|
||||
var netcafePoints uint32
|
||||
err := s.server.db.QueryRow("UPDATE characters SET netcafe_points = netcafe_points - $1 WHERE id = $2 RETURNING netcafe_points", pkt.PointCost, s.charID).Scan(&netcafePoints)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get netcafe points from db", zap.Error(err))
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(netcafePoints)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfUpdateCafepoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfUpdateCafepoint)
|
||||
var netcafePoints uint32
|
||||
err := s.server.db.QueryRow("SELECT COALESCE(netcafe_points, 0) FROM characters WHERE id = $1", s.charID).Scan(&netcafePoints)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get netcate points from db", zap.Error(err))
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(netcafePoints)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfCheckDailyCafepoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfCheckDailyCafepoint)
|
||||
|
||||
midday := TimeMidnight().Add(12 * time.Hour)
|
||||
if TimeAdjusted().After(midday) {
|
||||
midday = midday.Add(24 * time.Hour)
|
||||
}
|
||||
|
||||
// get time after which daily claiming would be valid from db
|
||||
var dailyTime time.Time
|
||||
err := s.server.db.QueryRow("SELECT COALESCE(daily_time, $2) FROM characters WHERE id = $1", s.charID, time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)).Scan(&dailyTime)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get daily_time savedata from db", zap.Error(err))
|
||||
}
|
||||
|
||||
var bondBonus, bonusQuests, dailyQuests uint32
|
||||
bf := byteframe.NewByteFrame()
|
||||
if midday.After(dailyTime) {
|
||||
addPointNetcafe(s, 5)
|
||||
bondBonus = 5 // Bond point bonus quests
|
||||
bonusQuests = s.server.erupeConfig.GameplayOptions.BonusQuestAllowance
|
||||
dailyQuests = s.server.erupeConfig.GameplayOptions.DailyQuestAllowance
|
||||
s.server.db.Exec("UPDATE characters SET daily_time=$1, bonus_quests = $2, daily_quests = $3 WHERE id=$4", midday, bonusQuests, dailyQuests, s.charID)
|
||||
bf.WriteBool(true) // Success?
|
||||
} else {
|
||||
bf.WriteBool(false)
|
||||
}
|
||||
bf.WriteUint32(bondBonus)
|
||||
bf.WriteUint32(bonusQuests)
|
||||
bf.WriteUint32(dailyQuests)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetCafeDuration)
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
var cafeReset time.Time
|
||||
err := s.server.db.QueryRow(`SELECT cafe_reset FROM characters WHERE id=$1`, s.charID).Scan(&cafeReset)
|
||||
if err != nil {
|
||||
cafeReset = TimeWeekNext()
|
||||
s.server.db.Exec(`UPDATE characters SET cafe_reset=$1 WHERE id=$2`, cafeReset, s.charID)
|
||||
}
|
||||
if TimeAdjusted().After(cafeReset) {
|
||||
cafeReset = TimeWeekNext()
|
||||
s.server.db.Exec(`UPDATE characters SET cafe_time=0, cafe_reset=$1 WHERE id=$2`, cafeReset, s.charID)
|
||||
s.server.db.Exec(`DELETE FROM cafe_accepted WHERE character_id=$1`, s.charID)
|
||||
}
|
||||
|
||||
var cafeTime uint32
|
||||
err = s.server.db.QueryRow("SELECT cafe_time FROM characters WHERE id = $1", s.charID).Scan(&cafeTime)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if mhfcourse.CourseExists(30, s.courses) {
|
||||
cafeTime = uint32(TimeAdjusted().Unix()) - uint32(s.sessionStart) + cafeTime
|
||||
}
|
||||
bf.WriteUint32(cafeTime) // Total cafe time
|
||||
bf.WriteUint16(0)
|
||||
ps.Uint16(bf, fmt.Sprintf(s.server.dict["cafeReset"], int(cafeReset.Month()), cafeReset.Day()), true)
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
type CafeBonus struct {
|
||||
ID uint32 `db:"id"`
|
||||
TimeReq uint32 `db:"time_req"`
|
||||
ItemType uint32 `db:"item_type"`
|
||||
ItemID uint32 `db:"item_id"`
|
||||
Quantity uint32 `db:"quantity"`
|
||||
Claimed bool `db:"claimed"`
|
||||
}
|
||||
|
||||
func handleMsgMhfGetCafeDurationBonusInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetCafeDurationBonusInfo)
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
var count uint32
|
||||
rows, err := s.server.db.Queryx(`
|
||||
SELECT cb.id, time_req, item_type, item_id, quantity,
|
||||
(
|
||||
SELECT count(*)
|
||||
FROM cafe_accepted ca
|
||||
WHERE cb.id = ca.cafe_id AND ca.character_id = $1
|
||||
)::int::bool AS claimed
|
||||
FROM cafebonus cb ORDER BY id ASC;`, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Error("Error getting cafebonus", zap.Error(err))
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
} else {
|
||||
for rows.Next() {
|
||||
count++
|
||||
cafeBonus := &CafeBonus{}
|
||||
err = rows.StructScan(&cafeBonus)
|
||||
if err != nil {
|
||||
s.logger.Error("Error scanning cafebonus", zap.Error(err))
|
||||
}
|
||||
bf.WriteUint32(cafeBonus.TimeReq)
|
||||
bf.WriteUint32(cafeBonus.ItemType)
|
||||
bf.WriteUint32(cafeBonus.ItemID)
|
||||
bf.WriteUint32(cafeBonus.Quantity)
|
||||
bf.WriteBool(cafeBonus.Claimed)
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(uint32(TimeAdjusted().Unix()))
|
||||
resp.WriteUint32(count)
|
||||
resp.WriteBytes(bf.Data())
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfReceiveCafeDurationBonus(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReceiveCafeDurationBonus)
|
||||
bf := byteframe.NewByteFrame()
|
||||
var count uint32
|
||||
bf.WriteUint32(0)
|
||||
rows, err := s.server.db.Queryx(`
|
||||
SELECT c.id, time_req, item_type, item_id, quantity
|
||||
FROM cafebonus c
|
||||
WHERE (
|
||||
SELECT count(*)
|
||||
FROM cafe_accepted ca
|
||||
WHERE c.id = ca.cafe_id AND ca.character_id = $1
|
||||
) < 1 AND (
|
||||
SELECT ch.cafe_time + $2
|
||||
FROM characters ch
|
||||
WHERE ch.id = $1
|
||||
) >= time_req`, s.charID, TimeAdjusted().Unix()-s.sessionStart)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
} else {
|
||||
for rows.Next() {
|
||||
cafeBonus := &CafeBonus{}
|
||||
err = rows.StructScan(cafeBonus)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
count++
|
||||
bf.WriteUint32(cafeBonus.ID)
|
||||
bf.WriteUint32(cafeBonus.ItemType)
|
||||
bf.WriteUint32(cafeBonus.ItemID)
|
||||
bf.WriteUint32(cafeBonus.Quantity)
|
||||
}
|
||||
bf.Seek(0, io.SeekStart)
|
||||
bf.WriteUint32(count)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfPostCafeDurationBonusReceived(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostCafeDurationBonusReceived)
|
||||
var cafeBonus CafeBonus
|
||||
for _, cbID := range pkt.CafeBonusID {
|
||||
err := s.server.db.QueryRow(`
|
||||
SELECT cb.id, item_type, quantity FROM cafebonus cb WHERE cb.id=$1
|
||||
`, cbID).Scan(&cafeBonus.ID, &cafeBonus.ItemType, &cafeBonus.Quantity)
|
||||
if err == nil {
|
||||
if cafeBonus.ItemType == 17 {
|
||||
addPointNetcafe(s, int(cafeBonus.Quantity))
|
||||
}
|
||||
}
|
||||
s.server.db.Exec("INSERT INTO public.cafe_accepted VALUES ($1, $2)", cbID, s.charID)
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func addPointNetcafe(s *Session, p int) error {
|
||||
var points int
|
||||
err := s.server.db.QueryRow("SELECT netcafe_points FROM characters WHERE id = $1", s.charID).Scan(&points)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if points+p > s.server.erupeConfig.GameplayOptions.MaximumNP {
|
||||
points = s.server.erupeConfig.GameplayOptions.MaximumNP
|
||||
} else {
|
||||
points += p
|
||||
}
|
||||
s.server.db.Exec("UPDATE characters SET netcafe_points=$1 WHERE id=$2", points, s.charID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleMsgMhfStartBoostTime(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfStartBoostTime)
|
||||
bf := byteframe.NewByteFrame()
|
||||
boostLimit := TimeAdjusted().Add(time.Duration(s.server.erupeConfig.GameplayOptions.BoostTimeDuration) * time.Minute)
|
||||
if s.server.erupeConfig.GameplayOptions.DisableBoostTime {
|
||||
bf.WriteUint32(0)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
return
|
||||
}
|
||||
s.server.db.Exec("UPDATE characters SET boost_time=$1 WHERE id=$2", boostLimit, s.charID)
|
||||
bf.WriteUint32(uint32(boostLimit.Unix()))
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfGetBoostTime(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetBoostTime)
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{})
|
||||
}
|
||||
|
||||
func handleMsgMhfGetBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetBoostTimeLimit)
|
||||
bf := byteframe.NewByteFrame()
|
||||
var boostLimit time.Time
|
||||
err := s.server.db.QueryRow("SELECT boost_time FROM characters WHERE id=$1", s.charID).Scan(&boostLimit)
|
||||
if err != nil {
|
||||
bf.WriteUint32(0)
|
||||
} else {
|
||||
bf.WriteUint32(uint32(boostLimit.Unix()))
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfGetBoostRight(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetBoostRight)
|
||||
var boostLimit time.Time
|
||||
err := s.server.db.QueryRow("SELECT boost_time FROM characters WHERE id=$1", s.charID).Scan(&boostLimit)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
return
|
||||
}
|
||||
if boostLimit.After(TimeAdjusted()) {
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x01})
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x02})
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfPostBoostTimeQuestReturn(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostBoostTimeQuestReturn)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfPostBoostTime(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostBoostTime)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfPostBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostBoostTimeLimit)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
176
server/channelserver/handlers_campaign.go
Normal file
176
server/channelserver/handlers_campaign.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
"erupe-ce/common/stringsupport"
|
||||
_config "erupe-ce/config"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CampaignEvent struct {
|
||||
ID uint32
|
||||
Unk0 uint32
|
||||
MinHR int16
|
||||
MaxHR int16
|
||||
MinSR int16
|
||||
MaxSR int16
|
||||
MinGR int16
|
||||
MaxGR int16
|
||||
Unk1 uint16
|
||||
Unk2 uint8
|
||||
Unk3 uint8
|
||||
Unk4 uint16
|
||||
Unk5 uint16
|
||||
Start time.Time
|
||||
End time.Time
|
||||
Unk6 uint8
|
||||
String0 string
|
||||
String1 string
|
||||
String2 string
|
||||
String3 string
|
||||
Link string
|
||||
Prefix string
|
||||
Categories []uint16
|
||||
}
|
||||
|
||||
type CampaignCategory struct {
|
||||
ID uint16
|
||||
Type uint8
|
||||
Title string
|
||||
Description string
|
||||
}
|
||||
|
||||
type CampaignLink struct {
|
||||
CategoryID uint16
|
||||
CampaignID uint32
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateCampaign(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateCampaign)
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
events := []CampaignEvent{}
|
||||
categories := []CampaignCategory{}
|
||||
var campaignLinks []CampaignLink
|
||||
|
||||
if len(events) > 255 {
|
||||
bf.WriteUint8(255)
|
||||
bf.WriteUint16(uint16(len(events)))
|
||||
} else {
|
||||
bf.WriteUint8(uint8(len(events)))
|
||||
}
|
||||
for _, event := range events {
|
||||
bf.WriteUint32(event.ID)
|
||||
bf.WriteUint32(event.Unk0)
|
||||
bf.WriteInt16(event.MinHR)
|
||||
bf.WriteInt16(event.MaxHR)
|
||||
bf.WriteInt16(event.MinSR)
|
||||
bf.WriteInt16(event.MaxSR)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G3 {
|
||||
bf.WriteInt16(event.MinGR)
|
||||
bf.WriteInt16(event.MaxGR)
|
||||
}
|
||||
bf.WriteUint16(event.Unk1)
|
||||
bf.WriteUint8(event.Unk2)
|
||||
bf.WriteUint8(event.Unk3)
|
||||
bf.WriteUint16(event.Unk4)
|
||||
bf.WriteUint16(event.Unk5)
|
||||
bf.WriteUint32(uint32(event.Start.Unix()))
|
||||
bf.WriteUint32(uint32(event.End.Unix()))
|
||||
bf.WriteUint8(event.Unk6)
|
||||
ps.Uint8(bf, event.String0, true)
|
||||
ps.Uint8(bf, event.String1, true)
|
||||
ps.Uint8(bf, event.String2, true)
|
||||
ps.Uint8(bf, event.String3, true)
|
||||
ps.Uint8(bf, event.Link, true)
|
||||
for i := range event.Categories {
|
||||
campaignLinks = append(campaignLinks, CampaignLink{event.Categories[i], event.ID})
|
||||
}
|
||||
}
|
||||
|
||||
if len(events) > 255 {
|
||||
bf.WriteUint8(255)
|
||||
bf.WriteUint16(uint16(len(events)))
|
||||
} else {
|
||||
bf.WriteUint8(uint8(len(events)))
|
||||
}
|
||||
for _, event := range events {
|
||||
bf.WriteUint32(event.ID)
|
||||
bf.WriteUint8(1) // Always 1?
|
||||
bf.WriteBytes([]byte(event.Prefix))
|
||||
}
|
||||
|
||||
if len(categories) > 255 {
|
||||
bf.WriteUint8(255)
|
||||
bf.WriteUint16(uint16(len(categories)))
|
||||
} else {
|
||||
bf.WriteUint8(uint8(len(categories)))
|
||||
}
|
||||
for _, category := range categories {
|
||||
bf.WriteUint16(category.ID)
|
||||
bf.WriteUint8(category.Type)
|
||||
xTitle := stringsupport.UTF8ToSJIS(category.Title)
|
||||
xDescription := stringsupport.UTF8ToSJIS(category.Description)
|
||||
bf.WriteUint8(uint8(len(xTitle)))
|
||||
bf.WriteUint8(uint8(len(xDescription)))
|
||||
bf.WriteBytes(xTitle)
|
||||
bf.WriteBytes(xDescription)
|
||||
}
|
||||
|
||||
if len(campaignLinks) > 255 {
|
||||
bf.WriteUint8(255)
|
||||
bf.WriteUint16(uint16(len(campaignLinks)))
|
||||
} else {
|
||||
bf.WriteUint8(uint8(len(campaignLinks)))
|
||||
}
|
||||
for _, link := range campaignLinks {
|
||||
bf.WriteUint16(link.CategoryID)
|
||||
bf.WriteUint32(link.CampaignID)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfStateCampaign(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfStateCampaign)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(1)
|
||||
bf.WriteUint16(0)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfApplyCampaign(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfApplyCampaign)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(1)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateItem)
|
||||
items := []struct {
|
||||
Unk0 uint32
|
||||
Unk1 uint16
|
||||
Unk2 uint16
|
||||
Unk3 uint16
|
||||
Unk4 uint32
|
||||
Unk5 uint32
|
||||
}{}
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(uint16(len(items)))
|
||||
for _, item := range items {
|
||||
bf.WriteUint32(item.Unk0)
|
||||
bf.WriteUint16(item.Unk1)
|
||||
bf.WriteUint16(item.Unk2)
|
||||
bf.WriteUint16(item.Unk3)
|
||||
bf.WriteUint32(item.Unk4)
|
||||
bf.WriteUint32(item.Unk5)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAcquireItem)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
123
server/channelserver/handlers_caravan.go
Normal file
123
server/channelserver/handlers_caravan.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/stringsupport"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RyoudamaReward struct {
|
||||
Unk0 uint8
|
||||
Unk1 uint8
|
||||
Unk2 uint16
|
||||
Unk3 uint16
|
||||
Unk4 uint16
|
||||
Unk5 uint16
|
||||
}
|
||||
|
||||
type RyoudamaKeyScore struct {
|
||||
Unk0 uint8
|
||||
Unk1 int32
|
||||
}
|
||||
|
||||
type RyoudamaCharInfo struct {
|
||||
CID uint32
|
||||
Unk0 int32
|
||||
Name string
|
||||
}
|
||||
|
||||
type RyoudamaBoostInfo struct {
|
||||
Start time.Time
|
||||
End time.Time
|
||||
}
|
||||
|
||||
type Ryoudama struct {
|
||||
Reward []RyoudamaReward
|
||||
KeyScore []RyoudamaKeyScore
|
||||
CharInfo []RyoudamaCharInfo
|
||||
BoostInfo []RyoudamaBoostInfo
|
||||
Score []int32
|
||||
}
|
||||
|
||||
func handleMsgMhfGetRyoudama(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetRyoudama)
|
||||
var data []*byteframe.ByteFrame
|
||||
ryoudama := Ryoudama{Score: []int32{0}}
|
||||
switch pkt.Request2 {
|
||||
case 4:
|
||||
for _, score := range ryoudama.Score {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteInt32(score)
|
||||
data = append(data, bf)
|
||||
}
|
||||
case 5:
|
||||
for _, info := range ryoudama.CharInfo {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(info.CID)
|
||||
bf.WriteInt32(info.Unk0)
|
||||
bf.WriteBytes(stringsupport.PaddedString(info.Name, 14, true))
|
||||
data = append(data, bf)
|
||||
}
|
||||
case 6:
|
||||
for _, info := range ryoudama.BoostInfo {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(uint32(info.Start.Unix()))
|
||||
bf.WriteUint32(uint32(info.End.Unix()))
|
||||
data = append(data, bf)
|
||||
}
|
||||
}
|
||||
doAckEarthSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfPostRyoudama(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfGetTinyBin(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetTinyBin)
|
||||
// requested after conquest quests
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{})
|
||||
}
|
||||
|
||||
func handleMsgMhfPostTinyBin(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostTinyBin)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfCaravanMyScore(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfCaravanMyScore)
|
||||
var data []*byteframe.ByteFrame
|
||||
/*
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
*/
|
||||
doAckEarthSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfCaravanRanking(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfCaravanRanking)
|
||||
var data []*byteframe.ByteFrame
|
||||
/* RYOUDAN
|
||||
bf.WriteInt32(1)
|
||||
bf.WriteUint32(2)
|
||||
bf.WriteBytes(stringsupport.PaddedString("Test", 26, true))
|
||||
*/
|
||||
|
||||
/* PERSONAL
|
||||
bf.WriteInt32(1)
|
||||
bf.WriteBytes(stringsupport.PaddedString("Test", 14, true))
|
||||
*/
|
||||
doAckEarthSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfCaravanMyRank(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfCaravanMyRank)
|
||||
var data []*byteframe.ByteFrame
|
||||
/*
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
*/
|
||||
doAckEarthSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
448
server/channelserver/handlers_cast_binary.go
Normal file
448
server/channelserver/handlers_cast_binary.go
Normal file
@@ -0,0 +1,448 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/mhfcourse"
|
||||
"erupe-ce/common/token"
|
||||
"erupe-ce/config"
|
||||
"erupe-ce/network/binpacket"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"fmt"
|
||||
"golang.org/x/exp/slices"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// MSG_SYS_CAST[ED]_BINARY types enum
|
||||
const (
|
||||
BinaryMessageTypeState = 0
|
||||
BinaryMessageTypeChat = 1
|
||||
BinaryMessageTypeQuest = 2
|
||||
BinaryMessageTypeData = 3
|
||||
BinaryMessageTypeMailNotify = 4
|
||||
BinaryMessageTypeEmote = 6
|
||||
)
|
||||
|
||||
// MSG_SYS_CAST[ED]_BINARY broadcast types enum
|
||||
const (
|
||||
BroadcastTypeTargeted = 0x01
|
||||
BroadcastTypeStage = 0x03
|
||||
BroadcastTypeServer = 0x06
|
||||
BroadcastTypeWorld = 0x0a
|
||||
)
|
||||
|
||||
var commands map[string]_config.Command
|
||||
|
||||
func init() {
|
||||
commands = make(map[string]_config.Command)
|
||||
zapConfig := zap.NewDevelopmentConfig()
|
||||
zapConfig.DisableCaller = true
|
||||
zapLogger, _ := zapConfig.Build()
|
||||
defer zapLogger.Sync()
|
||||
logger := zapLogger.Named("commands")
|
||||
cmds := _config.ErupeConfig.Commands
|
||||
for _, cmd := range cmds {
|
||||
commands[cmd.Name] = cmd
|
||||
if cmd.Enabled {
|
||||
logger.Info(fmt.Sprintf("Command %s: Enabled, prefix: %s", cmd.Name, cmd.Prefix))
|
||||
} else {
|
||||
logger.Info(fmt.Sprintf("Command %s: Disabled", cmd.Name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sendDisabledCommandMessage(s *Session, cmd _config.Command) {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandDisabled"], cmd.Name))
|
||||
}
|
||||
|
||||
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 parseChatCommand(s *Session, command string) {
|
||||
args := strings.Split(command[1:], " ")
|
||||
switch args[0] {
|
||||
case commands["PSN"].Prefix:
|
||||
if commands["PSN"].Enabled {
|
||||
if len(args) > 1 {
|
||||
var exists int
|
||||
s.server.db.QueryRow(`SELECT count(*) FROM users WHERE psn_id = $1`, args[1]).Scan(&exists)
|
||||
if exists == 0 {
|
||||
_, err := s.server.db.Exec(`UPDATE users u SET psn_id=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, args[1], s.charID)
|
||||
if err == nil {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandPSNSuccess"], args[1]))
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandPSNExists"])
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandPSNError"], commands["PSN"].Prefix))
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["PSN"])
|
||||
}
|
||||
case commands["Reload"].Prefix:
|
||||
if commands["Reload"].Enabled {
|
||||
sendServerChatMessage(s, s.server.dict["commandReload"])
|
||||
var temp mhfpacket.MHFPacket
|
||||
deleteNotif := byteframe.NewByteFrame()
|
||||
for _, object := range s.stage.objects {
|
||||
if object.ownerCharID == s.charID {
|
||||
continue
|
||||
}
|
||||
temp = &mhfpacket.MsgSysDeleteObject{ObjID: object.id}
|
||||
deleteNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(deleteNotif, s.clientContext)
|
||||
}
|
||||
for _, session := range s.server.sessions {
|
||||
if s == session {
|
||||
continue
|
||||
}
|
||||
temp = &mhfpacket.MsgSysDeleteUser{CharID: session.charID}
|
||||
deleteNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(deleteNotif, s.clientContext)
|
||||
}
|
||||
deleteNotif.WriteUint16(0x0010)
|
||||
s.QueueSend(deleteNotif.Data())
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
reloadNotif := byteframe.NewByteFrame()
|
||||
for _, session := range s.server.sessions {
|
||||
if s == session {
|
||||
continue
|
||||
}
|
||||
temp = &mhfpacket.MsgSysInsertUser{CharID: session.charID}
|
||||
reloadNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(reloadNotif, s.clientContext)
|
||||
for i := 0; i < 3; i++ {
|
||||
temp = &mhfpacket.MsgSysNotifyUserBinary{
|
||||
CharID: session.charID,
|
||||
BinaryType: uint8(i + 1),
|
||||
}
|
||||
reloadNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(reloadNotif, s.clientContext)
|
||||
}
|
||||
}
|
||||
for _, obj := range s.stage.objects {
|
||||
if obj.ownerCharID == s.charID {
|
||||
continue
|
||||
}
|
||||
temp = &mhfpacket.MsgSysDuplicateObject{
|
||||
ObjID: obj.id,
|
||||
X: obj.x,
|
||||
Y: obj.y,
|
||||
Z: obj.z,
|
||||
Unk0: 0,
|
||||
OwnerCharID: obj.ownerCharID,
|
||||
}
|
||||
reloadNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(reloadNotif, s.clientContext)
|
||||
}
|
||||
reloadNotif.WriteUint16(0x0010)
|
||||
s.QueueSend(reloadNotif.Data())
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Reload"])
|
||||
}
|
||||
case commands["KeyQuest"].Prefix:
|
||||
if commands["KeyQuest"].Enabled {
|
||||
if len(args) > 1 {
|
||||
if args[1] == "get" {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandKqfGet"], s.kqf))
|
||||
} else if args[1] == "set" {
|
||||
if len(args) > 2 && len(args[2]) == 16 {
|
||||
hexd, _ := hex.DecodeString(args[2])
|
||||
s.kqf = hexd
|
||||
s.kqfOverride = true
|
||||
sendServerChatMessage(s, s.server.dict["commandKqfSetSuccess"])
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandKqfSetError"], commands["KeyQuest"].Prefix))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["KeyQuest"])
|
||||
}
|
||||
case commands["Rights"].Prefix:
|
||||
if commands["Rights"].Enabled {
|
||||
if len(args) > 1 {
|
||||
v, _ := strconv.Atoi(args[1])
|
||||
_, err := s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", v, s.charID)
|
||||
if err == nil {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsSuccess"], v))
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsError"], commands["Rights"].Prefix))
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsError"], commands["Rights"].Prefix))
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Rights"])
|
||||
}
|
||||
case commands["Course"].Prefix:
|
||||
if commands["Course"].Enabled {
|
||||
if len(args) > 1 {
|
||||
for _, course := range mhfcourse.Courses() {
|
||||
for _, alias := range course.Aliases() {
|
||||
if strings.ToLower(args[1]) == strings.ToLower(alias) {
|
||||
if slices.Contains(s.server.erupeConfig.Courses, _config.Course{Name: course.Aliases()[0], Enabled: true}) {
|
||||
var delta, rightsInt uint32
|
||||
if mhfcourse.CourseExists(course.ID, s.courses) {
|
||||
ei := slices.IndexFunc(s.courses, func(c mhfcourse.Course) bool {
|
||||
for _, alias := range c.Aliases() {
|
||||
if strings.ToLower(args[1]) == strings.ToLower(alias) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
if ei != -1 {
|
||||
delta = uint32(-1 * math.Pow(2, float64(course.ID)))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseDisabled"], course.Aliases()[0]))
|
||||
}
|
||||
} else {
|
||||
delta = uint32(math.Pow(2, float64(course.ID)))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseEnabled"], course.Aliases()[0]))
|
||||
}
|
||||
err := s.server.db.QueryRow("SELECT rights FROM users u INNER JOIN characters c ON u.id = c.user_id WHERE c.id = $1", s.charID).Scan(&rightsInt)
|
||||
if err == nil {
|
||||
s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", rightsInt+delta, s.charID)
|
||||
}
|
||||
updateRights(s)
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseLocked"], course.Aliases()[0]))
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseError"], commands["Course"].Prefix))
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Course"])
|
||||
}
|
||||
case commands["Raviente"].Prefix:
|
||||
if commands["Raviente"].Enabled {
|
||||
if len(args) > 1 {
|
||||
if s.server.getRaviSemaphore() != nil {
|
||||
switch args[1] {
|
||||
case "start":
|
||||
if s.server.raviente.register[1] == 0 {
|
||||
s.server.raviente.register[1] = s.server.raviente.register[3]
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviStartSuccess"])
|
||||
s.notifyRavi()
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviStartError"])
|
||||
}
|
||||
case "cm", "check", "checkmultiplier", "multiplier":
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRaviMultiplier"], s.server.GetRaviMultiplier()))
|
||||
case "sr", "sendres", "resurrection", "ss", "sendsed", "rs", "reqsed":
|
||||
if s.server.erupeConfig.RealClientMode == _config.ZZ {
|
||||
switch args[1] {
|
||||
case "sr", "sendres", "resurrection":
|
||||
if s.server.raviente.state[28] > 0 {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviResSuccess"])
|
||||
s.server.raviente.state[28] = 0
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviResError"])
|
||||
}
|
||||
case "ss", "sendsed":
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviSedSuccess"])
|
||||
// Total BerRavi HP
|
||||
HP := s.server.raviente.state[0] + s.server.raviente.state[1] + s.server.raviente.state[2] + s.server.raviente.state[3] + s.server.raviente.state[4]
|
||||
s.server.raviente.support[1] = HP
|
||||
case "rs", "reqsed":
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviRequest"])
|
||||
// Total BerRavi HP
|
||||
HP := s.server.raviente.state[0] + s.server.raviente.state[1] + s.server.raviente.state[2] + s.server.raviente.state[3] + s.server.raviente.state[4]
|
||||
s.server.raviente.support[1] = HP + 1
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviVersion"])
|
||||
}
|
||||
default:
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviError"])
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviNoPlayers"])
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviError"])
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Raviente"])
|
||||
}
|
||||
case commands["Teleport"].Prefix:
|
||||
if commands["Teleport"].Enabled {
|
||||
if len(args) > 2 {
|
||||
x, _ := strconv.Atoi(args[1])
|
||||
y, _ := strconv.Atoi(args[2])
|
||||
payload := byteframe.NewByteFrame()
|
||||
payload.SetLE()
|
||||
payload.WriteUint8(2) // SetState type(position == 2)
|
||||
payload.WriteInt16(int16(x)) // X
|
||||
payload.WriteInt16(int16(y)) // Y
|
||||
payloadBytes := payload.Data()
|
||||
s.QueueSendMHF(&mhfpacket.MsgSysCastedBinary{
|
||||
CharID: s.charID,
|
||||
MessageType: BinaryMessageTypeState,
|
||||
RawDataPayload: payloadBytes,
|
||||
})
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandTeleportSuccess"], x, y))
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandTeleportError"], commands["Teleport"].Prefix))
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Teleport"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
if s.server.erupeConfig.DevModeOptions.QuestDebugTools == true && s.server.erupeConfig.DevMode {
|
||||
if pkt.BroadcastType == 0x03 && pkt.MessageType == 0x02 && len(pkt.RawDataPayload) > 32 {
|
||||
// This is only correct most of the time
|
||||
tmp.ReadBytes(20)
|
||||
tmp.SetLE()
|
||||
x := tmp.ReadFloat32()
|
||||
y := tmp.ReadFloat32()
|
||||
z := tmp.ReadFloat32()
|
||||
s.logger.Debug("Coord", zap.Float32s("XYZ", []float32{x, y, z}))
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
dice := fmt.Sprintf("%d", token.RNG().Intn(100)+1)
|
||||
roll.WriteUint16(uint16(len(dice) + 1))
|
||||
roll.WriteNullTerminatedBytes([]byte(dice))
|
||||
roll.WriteNullTerminatedBytes(tmp.ReadNullTerminatedBytes())
|
||||
realPayload = roll.Data()
|
||||
} else {
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
|
||||
bf.SetLE()
|
||||
chatMessage := &binpacket.MsgBinChat{}
|
||||
chatMessage.Parse(bf)
|
||||
if strings.HasPrefix(chatMessage.Message, "!") {
|
||||
parseChatCommand(s, chatMessage.Message)
|
||||
return
|
||||
}
|
||||
if (pkt.BroadcastType == BroadcastTypeStage && s.stage.id == "sl1Ns200p0a0u0") || pkt.BroadcastType == BroadcastTypeWorld {
|
||||
s.server.DiscordChannelSend(chatMessage.SenderName, chatMessage.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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, nil)
|
||||
case BroadcastTypeStage:
|
||||
if isDiceCommand {
|
||||
s.stage.BroadcastMHF(resp, nil) // send dice result back to caller
|
||||
} else {
|
||||
s.stage.BroadcastMHF(resp, s)
|
||||
}
|
||||
case BroadcastTypeServer:
|
||||
if pkt.MessageType == 1 {
|
||||
raviSema := s.server.getRaviSemaphore()
|
||||
if raviSema != nil {
|
||||
raviSema.BroadcastMHF(resp, s)
|
||||
}
|
||||
} else {
|
||||
s.server.BroadcastMHF(resp, s)
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgSysCastedBinary(s *Session, p mhfpacket.MHFPacket) {}
|
||||
217
server/channelserver/handlers_character.go
Normal file
217
server/channelserver/handlers_character.go
Normal file
@@ -0,0 +1,217 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"erupe-ce/common/bfutil"
|
||||
"erupe-ce/common/stringsupport"
|
||||
_config "erupe-ce/config"
|
||||
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"erupe-ce/server/channelserver/compression/nullcomp"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type SavePointer int
|
||||
|
||||
const (
|
||||
pGender = iota // +1
|
||||
pRP // +2
|
||||
pHouseTier // +5
|
||||
pHouseData // +195
|
||||
pBookshelfData // +5576
|
||||
pGalleryData // +1748
|
||||
pToreData // +240
|
||||
pGardenData // +68
|
||||
pWeaponType // +1
|
||||
pWeaponID // +2
|
||||
pHRP // +2
|
||||
pGRP // +4
|
||||
pKQF // +8
|
||||
)
|
||||
|
||||
type CharacterSaveData struct {
|
||||
CharID uint32
|
||||
Name string
|
||||
IsNewCharacter bool
|
||||
Pointers map[SavePointer]int
|
||||
|
||||
Gender bool
|
||||
RP uint16
|
||||
HouseTier []byte
|
||||
HouseData []byte
|
||||
BookshelfData []byte
|
||||
GalleryData []byte
|
||||
ToreData []byte
|
||||
GardenData []byte
|
||||
WeaponType uint8
|
||||
WeaponID uint16
|
||||
HRP uint16
|
||||
GR uint16
|
||||
KQF []byte
|
||||
|
||||
compSave []byte
|
||||
decompSave []byte
|
||||
}
|
||||
|
||||
func getPointers() map[SavePointer]int {
|
||||
pointers := map[SavePointer]int{pGender: 81}
|
||||
switch _config.ErupeConfig.RealClientMode {
|
||||
case _config.ZZ:
|
||||
pointers[pWeaponID] = 128522
|
||||
pointers[pWeaponType] = 128789
|
||||
pointers[pHouseTier] = 129900
|
||||
pointers[pToreData] = 130228
|
||||
pointers[pHRP] = 130550
|
||||
pointers[pGRP] = 130556
|
||||
pointers[pHouseData] = 130561
|
||||
pointers[pBookshelfData] = 139928
|
||||
pointers[pGalleryData] = 140064
|
||||
pointers[pGardenData] = 142424
|
||||
pointers[pRP] = 142614
|
||||
pointers[pKQF] = 146720
|
||||
case _config.Z2, _config.Z1, _config.G101, _config.G10:
|
||||
pointers[pWeaponID] = 92522
|
||||
pointers[pWeaponType] = 92789
|
||||
pointers[pHouseTier] = 93900
|
||||
pointers[pToreData] = 94228
|
||||
pointers[pHRP] = 94550
|
||||
pointers[pGRP] = 94556
|
||||
pointers[pHouseData] = 94561
|
||||
pointers[pBookshelfData] = 103928
|
||||
pointers[pGalleryData] = 104064
|
||||
pointers[pGardenData] = 106424
|
||||
pointers[pRP] = 106614
|
||||
pointers[pKQF] = 110720
|
||||
}
|
||||
return pointers
|
||||
}
|
||||
|
||||
func GetCharacterSaveData(s *Session, charID uint32) (*CharacterSaveData, error) {
|
||||
result, err := s.server.db.Query("SELECT id, savedata, is_new_character, name FROM characters WHERE id = $1", charID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get savedata", zap.Error(err), zap.Uint32("charID", charID))
|
||||
return nil, err
|
||||
}
|
||||
defer result.Close()
|
||||
if !result.Next() {
|
||||
err = errors.New("no savedata found")
|
||||
s.logger.Error("No savedata found", zap.Uint32("charID", charID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
saveData := &CharacterSaveData{
|
||||
Pointers: getPointers(),
|
||||
}
|
||||
err = result.Scan(&saveData.CharID, &saveData.compSave, &saveData.IsNewCharacter, &saveData.Name)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to scan savedata", zap.Error(err), zap.Uint32("charID", charID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if saveData.compSave == nil {
|
||||
return saveData, nil
|
||||
}
|
||||
|
||||
err = saveData.Decompress()
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to decompress savedata", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
saveData.updateStructWithSaveData()
|
||||
|
||||
return saveData, nil
|
||||
}
|
||||
|
||||
func (save *CharacterSaveData) Save(s *Session) {
|
||||
if !s.kqfOverride {
|
||||
s.kqf = save.KQF
|
||||
} else {
|
||||
save.KQF = s.kqf
|
||||
}
|
||||
|
||||
save.updateSaveDataWithStruct()
|
||||
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G1 {
|
||||
err := save.Compress()
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to compress savedata", zap.Error(err))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Saves were not compressed
|
||||
save.compSave = save.decompSave
|
||||
}
|
||||
|
||||
_, err := s.server.db.Exec(`UPDATE characters SET savedata=$1, is_new_character=false, hrp=$2, gr=$3, is_female=$4, weapon_type=$5, weapon_id=$6 WHERE id=$7
|
||||
`, save.compSave, save.HRP, save.GR, save.Gender, save.WeaponType, save.WeaponID, save.CharID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to update savedata", zap.Error(err), zap.Uint32("charID", save.CharID))
|
||||
}
|
||||
|
||||
s.server.db.Exec(`UPDATE user_binary SET house_tier=$1, house_data=$2, bookshelf=$3, gallery=$4, tore=$5, garden=$6 WHERE id=$7
|
||||
`, save.HouseTier, save.HouseData, save.BookshelfData, save.GalleryData, save.ToreData, save.GardenData, s.charID)
|
||||
}
|
||||
|
||||
func (save *CharacterSaveData) Compress() error {
|
||||
var err error
|
||||
save.compSave, err = nullcomp.Compress(save.decompSave)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (save *CharacterSaveData) Decompress() error {
|
||||
var err error
|
||||
save.decompSave, err = nullcomp.Decompress(save.compSave)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// This will update the character save with the values stored in the save struct
|
||||
func (save *CharacterSaveData) updateSaveDataWithStruct() {
|
||||
rpBytes := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(rpBytes, save.RP)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G10 {
|
||||
copy(save.decompSave[save.Pointers[pRP]:save.Pointers[pRP]+2], rpBytes)
|
||||
copy(save.decompSave[save.Pointers[pKQF]:save.Pointers[pKQF]+8], save.KQF)
|
||||
}
|
||||
}
|
||||
|
||||
// This will update the save struct with the values stored in the character save
|
||||
func (save *CharacterSaveData) updateStructWithSaveData() {
|
||||
save.Name = stringsupport.SJISToUTF8(bfutil.UpToNull(save.decompSave[88:100]))
|
||||
if save.decompSave[save.Pointers[pGender]] == 1 {
|
||||
save.Gender = true
|
||||
} else {
|
||||
save.Gender = false
|
||||
}
|
||||
if !save.IsNewCharacter {
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G10 {
|
||||
save.RP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pRP] : save.Pointers[pRP]+2])
|
||||
save.HouseTier = save.decompSave[save.Pointers[pHouseTier] : save.Pointers[pHouseTier]+5]
|
||||
save.HouseData = save.decompSave[save.Pointers[pHouseData] : save.Pointers[pHouseData]+195]
|
||||
save.BookshelfData = save.decompSave[save.Pointers[pBookshelfData] : save.Pointers[pBookshelfData]+5576]
|
||||
save.GalleryData = save.decompSave[save.Pointers[pGalleryData] : save.Pointers[pGalleryData]+1748]
|
||||
save.ToreData = save.decompSave[save.Pointers[pToreData] : save.Pointers[pToreData]+240]
|
||||
save.GardenData = save.decompSave[save.Pointers[pGardenData] : save.Pointers[pGardenData]+68]
|
||||
save.WeaponType = save.decompSave[save.Pointers[pWeaponType]]
|
||||
save.WeaponID = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pWeaponID] : save.Pointers[pWeaponID]+2])
|
||||
save.HRP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pHRP] : save.Pointers[pHRP]+2])
|
||||
if save.HRP == uint16(999) {
|
||||
save.GR = grpToGR(binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pGRP] : save.Pointers[pGRP]+4]))
|
||||
}
|
||||
save.KQF = save.decompSave[save.Pointers[pKQF] : save.Pointers[pKQF]+8]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func handleMsgMhfSexChanger(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSexChanger)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
114
server/channelserver/handlers_clients.go
Normal file
114
server/channelserver/handlers_clients.go
Normal file
@@ -0,0 +1,114 @@
|
||||
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.Get {
|
||||
case 0: // All
|
||||
for _, cid := range stage.clients {
|
||||
clients = append(clients, cid)
|
||||
}
|
||||
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) {}
|
||||
1682
server/channelserver/handlers_data.go
Normal file
1682
server/channelserver/handlers_data.go
Normal file
File diff suppressed because it is too large
Load Diff
89
server/channelserver/handlers_discord.go
Normal file
89
server/channelserver/handlers_discord.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type Player struct {
|
||||
CharName string
|
||||
QuestID int
|
||||
}
|
||||
|
||||
func getPlayerSlice(s *Server) []Player {
|
||||
var p []Player
|
||||
var questIndex int
|
||||
|
||||
for _, channel := range s.Channels {
|
||||
for _, stage := range channel.stages {
|
||||
if len(stage.clients) == 0 {
|
||||
continue
|
||||
}
|
||||
questID := 0
|
||||
if stage.isQuest() {
|
||||
questIndex++
|
||||
questID = questIndex
|
||||
}
|
||||
for client := range stage.clients {
|
||||
p = append(p, Player{
|
||||
CharName: client.Name,
|
||||
QuestID: questID,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func getCharacterList(s *Server) string {
|
||||
questEmojis := []string{
|
||||
":person_in_lotus_position:",
|
||||
":white_circle:",
|
||||
":red_circle:",
|
||||
":blue_circle:",
|
||||
":brown_circle:",
|
||||
":green_circle:",
|
||||
":purple_circle:",
|
||||
":yellow_circle:",
|
||||
":orange_circle:",
|
||||
":black_circle:",
|
||||
}
|
||||
|
||||
playerSlice := getPlayerSlice(s)
|
||||
|
||||
sort.SliceStable(playerSlice, func(i, j int) bool {
|
||||
return playerSlice[i].QuestID < playerSlice[j].QuestID
|
||||
})
|
||||
|
||||
message := fmt.Sprintf("===== Online: %d =====\n", len(playerSlice))
|
||||
for _, player := range playerSlice {
|
||||
message += fmt.Sprintf("%s %s", questEmojis[player.QuestID], player.CharName)
|
||||
}
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
// 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.Bot || m.ChannelID != s.erupeConfig.Discord.RealtimeChannelID {
|
||||
return
|
||||
}
|
||||
|
||||
paddedName := strings.TrimSpace(strings.Map(func(r rune) rune {
|
||||
if r > unicode.MaxASCII {
|
||||
return -1
|
||||
}
|
||||
return r
|
||||
}, m.Author.Username))
|
||||
|
||||
for i := 0; i < 8-len(m.Author.Username); i++ {
|
||||
paddedName += " "
|
||||
}
|
||||
|
||||
message := fmt.Sprintf("[D] %s > %s", paddedName, m.Content)
|
||||
s.BroadcastChatMessage(s.discordBot.NormalizeDiscordMessage(message))
|
||||
}
|
||||
160
server/channelserver/handlers_distitem.go
Normal file
160
server/channelserver/handlers_distitem.go
Normal file
@@ -0,0 +1,160 @@
|
||||
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
|
||||
}
|
||||
|
||||
if len(dist.Data) >= 2 {
|
||||
distData := byteframe.NewByteFrameFromBytes(dist.Data)
|
||||
distItems := int(distData.ReadUint16())
|
||||
for i := 0; i < distItems; i++ {
|
||||
if len(dist.Data) >= 2+(i*13) {
|
||||
itemType := distData.ReadUint8()
|
||||
_ = distData.ReadBytes(6)
|
||||
quantity := int(distData.ReadUint16())
|
||||
_ = distData.ReadBytes(4)
|
||||
switch itemType {
|
||||
case 17:
|
||||
_ = addPointNetcafe(s, quantity)
|
||||
case 19:
|
||||
s.server.db.Exec("UPDATE users u SET gacha_premium=gacha_premium+$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", quantity, s.charID)
|
||||
case 20:
|
||||
s.server.db.Exec("UPDATE users u SET gacha_trial=gacha_trial+$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", quantity, s.charID)
|
||||
case 21:
|
||||
s.server.db.Exec("UPDATE users u SET frontier_points=frontier_points+$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", quantity, s.charID)
|
||||
case 23:
|
||||
saveData, err := GetCharacterSaveData(s, s.charID)
|
||||
if err == nil {
|
||||
saveData.RP += uint16(quantity)
|
||||
saveData.Save(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
321
server/channelserver/handlers_diva.go
Normal file
321
server/channelserver/handlers_diva.go
Normal file
@@ -0,0 +1,321 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"erupe-ce/common/stringsupport"
|
||||
_config "erupe-ce/config"
|
||||
"time"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
func cleanupDiva(s *Session) {
|
||||
s.server.db.Exec("DELETE FROM events WHERE event_type='diva'")
|
||||
}
|
||||
|
||||
func generateDivaTimestamps(s *Session, start uint32, debug bool) []uint32 {
|
||||
timestamps := make([]uint32, 6)
|
||||
midnight := TimeMidnight()
|
||||
if debug && start <= 3 {
|
||||
midnight := uint32(midnight.Unix())
|
||||
switch start {
|
||||
case 1:
|
||||
timestamps[0] = midnight
|
||||
timestamps[1] = timestamps[0] + 601200
|
||||
timestamps[2] = timestamps[1] + 3900
|
||||
timestamps[3] = timestamps[1] + 604800
|
||||
timestamps[4] = timestamps[3] + 3900
|
||||
timestamps[5] = timestamps[3] + 604800
|
||||
case 2:
|
||||
timestamps[0] = midnight - 605100
|
||||
timestamps[1] = midnight - 3900
|
||||
timestamps[2] = midnight
|
||||
timestamps[3] = timestamps[1] + 604800
|
||||
timestamps[4] = timestamps[3] + 3900
|
||||
timestamps[5] = timestamps[3] + 604800
|
||||
case 3:
|
||||
timestamps[0] = midnight - 1213800
|
||||
timestamps[1] = midnight - 608700
|
||||
timestamps[2] = midnight - 604800
|
||||
timestamps[3] = midnight - 3900
|
||||
timestamps[4] = midnight
|
||||
timestamps[5] = timestamps[3] + 604800
|
||||
}
|
||||
return timestamps
|
||||
}
|
||||
if start == 0 || TimeAdjusted().Unix() > int64(start)+2977200 {
|
||||
cleanupDiva(s)
|
||||
// Generate a new diva defense, starting midnight tomorrow
|
||||
start = uint32(midnight.Add(24 * time.Hour).Unix())
|
||||
s.server.db.Exec("INSERT INTO events (event_type, start_time) VALUES ('diva', to_timestamp($1)::timestamp without time zone)", start)
|
||||
}
|
||||
timestamps[0] = start
|
||||
timestamps[1] = timestamps[0] + 601200
|
||||
timestamps[2] = timestamps[1] + 3900
|
||||
timestamps[3] = timestamps[1] + 604800
|
||||
timestamps[4] = timestamps[3] + 3900
|
||||
timestamps[5] = timestamps[3] + 604800
|
||||
return timestamps
|
||||
}
|
||||
|
||||
func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetUdSchedule)
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
id, start := uint32(0xCAFEBEEF), uint32(0)
|
||||
rows, _ := s.server.db.Queryx("SELECT id, (EXTRACT(epoch FROM start_time)::int) as start_time FROM events WHERE event_type='diva'")
|
||||
for rows.Next() {
|
||||
rows.Scan(&id, &start)
|
||||
}
|
||||
|
||||
var timestamps []uint32
|
||||
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.DivaEvent >= 0 {
|
||||
if s.server.erupeConfig.DevModeOptions.DivaEvent == 0 {
|
||||
if s.server.erupeConfig.RealClientMode <= _config.Z1 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 32))
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 36))
|
||||
}
|
||||
return
|
||||
}
|
||||
timestamps = generateDivaTimestamps(s, uint32(s.server.erupeConfig.DevModeOptions.DivaEvent), true)
|
||||
} else {
|
||||
timestamps = generateDivaTimestamps(s, start, false)
|
||||
}
|
||||
|
||||
if s.server.erupeConfig.RealClientMode <= _config.Z1 {
|
||||
bf.WriteUint32(id)
|
||||
}
|
||||
for i := range timestamps {
|
||||
bf.WriteUint32(timestamps[i])
|
||||
}
|
||||
|
||||
bf.WriteUint16(0x19) // Unk 00011001
|
||||
bf.WriteUint16(0x2D) // Unk 00101101
|
||||
bf.WriteUint16(0x02) // Unk 00000010
|
||||
bf.WriteUint16(0x02) // Unk 00000010
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.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
|
||||
}{}
|
||||
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(uint8(len(udInfos)))
|
||||
for _, udInfo := range udInfos {
|
||||
resp.WriteBytes(stringsupport.PaddedString(udInfo.Text, 1024, true))
|
||||
resp.WriteUint32(uint32(udInfo.StartTime.Unix()))
|
||||
resp.WriteUint32(uint32(udInfo.EndTime.Unix()))
|
||||
}
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
223
server/channelserver/handlers_event.go
Normal file
223
server/channelserver/handlers_event.go
Normal file
@@ -0,0 +1,223 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/token"
|
||||
_config "erupe-ce/config"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
type Event struct {
|
||||
EventType uint16
|
||||
Unk1 uint16
|
||||
Unk2 uint16
|
||||
Unk3 uint16
|
||||
Unk4 uint16
|
||||
Unk5 uint32
|
||||
Unk6 uint32
|
||||
QuestFileIDs []uint16
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateEvent(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateEvent)
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
events := []Event{}
|
||||
|
||||
bf.WriteUint8(uint8(len(events)))
|
||||
for _, event := range events {
|
||||
bf.WriteUint16(event.EventType)
|
||||
bf.WriteUint16(event.Unk1)
|
||||
bf.WriteUint16(event.Unk2)
|
||||
bf.WriteUint16(event.Unk3)
|
||||
bf.WriteUint16(event.Unk4)
|
||||
bf.WriteUint32(event.Unk5)
|
||||
bf.WriteUint32(event.Unk6)
|
||||
if event.EventType == 2 {
|
||||
bf.WriteUint8(uint8(len(event.QuestFileIDs)))
|
||||
for _, qf := range event.QuestFileIDs {
|
||||
bf.WriteUint16(qf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
type activeFeature struct {
|
||||
StartTime time.Time `db:"start_time"`
|
||||
ActiveFeatures uint32 `db:"featured"`
|
||||
}
|
||||
|
||||
func handleMsgMhfGetWeeklySchedule(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetWeeklySchedule)
|
||||
|
||||
var features []activeFeature
|
||||
times := []time.Time{
|
||||
TimeMidnight().Add(-24 * time.Hour),
|
||||
TimeMidnight(),
|
||||
TimeMidnight().Add(24 * time.Hour),
|
||||
}
|
||||
|
||||
for _, t := range times {
|
||||
var temp activeFeature
|
||||
err := s.server.db.QueryRowx(`SELECT start_time, featured FROM feature_weapon WHERE start_time=$1`, t).StructScan(&temp)
|
||||
if err != nil || temp.StartTime.IsZero() {
|
||||
temp = generateFeatureWeapons(s.server.erupeConfig.GameplayOptions.FeaturedWeapons)
|
||||
temp.StartTime = t
|
||||
s.server.db.Exec(`INSERT INTO feature_weapon VALUES ($1, $2)`, temp.StartTime, temp.ActiveFeatures)
|
||||
}
|
||||
features = append(features, temp)
|
||||
}
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(uint8(len(features)))
|
||||
bf.WriteUint32(uint32(TimeAdjusted().Add(-5 * time.Minute).Unix()))
|
||||
for _, feature := range features {
|
||||
bf.WriteUint32(uint32(feature.StartTime.Unix()))
|
||||
bf.WriteUint32(feature.ActiveFeatures)
|
||||
bf.WriteUint16(0)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func generateFeatureWeapons(count int) activeFeature {
|
||||
_max := 14
|
||||
if _config.ErupeConfig.RealClientMode < _config.ZZ {
|
||||
_max = 13
|
||||
}
|
||||
if _config.ErupeConfig.RealClientMode < _config.G10 {
|
||||
_max = 12
|
||||
}
|
||||
if _config.ErupeConfig.RealClientMode < _config.GG {
|
||||
_max = 11
|
||||
}
|
||||
if count > _max {
|
||||
count = _max
|
||||
}
|
||||
nums := make([]int, 0)
|
||||
var result int
|
||||
for len(nums) < count {
|
||||
rng := token.RNG()
|
||||
num := rng.Intn(_max)
|
||||
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 activeFeature{ActiveFeatures: uint32(result)}
|
||||
}
|
||||
|
||||
type loginBoost struct {
|
||||
WeekReq uint8 `db:"week_req"`
|
||||
WeekCount uint8
|
||||
Active bool
|
||||
Expiration time.Time `db:"expiration"`
|
||||
Reset time.Time `db:"reset"`
|
||||
}
|
||||
|
||||
func handleMsgMhfGetKeepLoginBoostStatus(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetKeepLoginBoostStatus)
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
var loginBoosts []loginBoost
|
||||
rows, err := s.server.db.Queryx("SELECT week_req, expiration, reset FROM login_boost WHERE char_id=$1 ORDER BY week_req", s.charID)
|
||||
if err != nil || s.server.erupeConfig.GameplayOptions.DisableLoginBoost {
|
||||
rows.Close()
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 35))
|
||||
return
|
||||
}
|
||||
for rows.Next() {
|
||||
var temp loginBoost
|
||||
rows.StructScan(&temp)
|
||||
loginBoosts = append(loginBoosts, temp)
|
||||
}
|
||||
if len(loginBoosts) == 0 {
|
||||
temp := TimeWeekStart()
|
||||
loginBoosts = []loginBoost{
|
||||
{WeekReq: 1, Expiration: temp},
|
||||
{WeekReq: 2, Expiration: temp},
|
||||
{WeekReq: 3, Expiration: temp},
|
||||
{WeekReq: 4, Expiration: temp},
|
||||
{WeekReq: 5, Expiration: temp},
|
||||
}
|
||||
for _, boost := range loginBoosts {
|
||||
s.server.db.Exec(`INSERT INTO login_boost VALUES ($1, $2, $3, $4)`, s.charID, boost.WeekReq, boost.Expiration, time.Time{})
|
||||
}
|
||||
}
|
||||
|
||||
for _, boost := range loginBoosts {
|
||||
// Reset if next week
|
||||
if !boost.Reset.IsZero() && boost.Reset.Before(TimeAdjusted()) {
|
||||
boost.Expiration = TimeWeekStart()
|
||||
boost.Reset = time.Time{}
|
||||
s.server.db.Exec(`UPDATE login_boost SET expiration=$1, reset=$2 WHERE char_id=$3 AND week_req=$4`, boost.Expiration, boost.Reset, s.charID, boost.WeekReq)
|
||||
}
|
||||
|
||||
boost.WeekCount = uint8((TimeAdjusted().Unix()-boost.Expiration.Unix())/604800 + 1)
|
||||
|
||||
if boost.WeekCount >= boost.WeekReq {
|
||||
boost.Active = true
|
||||
boost.WeekCount = boost.WeekReq
|
||||
}
|
||||
|
||||
// Show reset timer on expired boosts
|
||||
if boost.Reset.After(TimeAdjusted()) {
|
||||
boost.Active = true
|
||||
boost.WeekCount = 0
|
||||
}
|
||||
|
||||
bf.WriteUint8(boost.WeekReq)
|
||||
bf.WriteBool(boost.Active)
|
||||
bf.WriteUint8(boost.WeekCount)
|
||||
if !boost.Reset.IsZero() {
|
||||
bf.WriteUint32(uint32(boost.Expiration.Unix()))
|
||||
} else {
|
||||
bf.WriteUint32(0)
|
||||
}
|
||||
}
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfUseKeepLoginBoost(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfUseKeepLoginBoost)
|
||||
var expiration time.Time
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(0)
|
||||
switch pkt.BoostWeekUsed {
|
||||
case 1:
|
||||
fallthrough
|
||||
case 3:
|
||||
expiration = TimeAdjusted().Add(120 * time.Minute)
|
||||
case 4:
|
||||
expiration = TimeAdjusted().Add(180 * time.Minute)
|
||||
case 2:
|
||||
fallthrough
|
||||
case 5:
|
||||
expiration = TimeAdjusted().Add(240 * time.Minute)
|
||||
}
|
||||
bf.WriteUint32(uint32(expiration.Unix()))
|
||||
s.server.db.Exec(`UPDATE login_boost SET expiration=$1, reset=$2 WHERE char_id=$3 AND week_req=$4`, expiration, TimeWeekNext(), s.charID, pkt.BoostWeekUsed)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
499
server/channelserver/handlers_festa.go
Normal file
499
server/channelserver/handlers_festa.go
Normal file
@@ -0,0 +1,499 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
"erupe-ce/common/token"
|
||||
_config "erupe-ce/config"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
func handleMsgMhfSaveMezfesData(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSaveMezfesData)
|
||||
s.server.db.Exec(`UPDATE characters SET mezfes=$1 WHERE id=$2`, pkt.RawDataPayload, s.charID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfLoadMezfesData(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadMezfesData)
|
||||
var data []byte
|
||||
s.server.db.QueryRow(`SELECT mezfes FROM characters WHERE id=$1`, s.charID).Scan(&data)
|
||||
bf := byteframe.NewByteFrame()
|
||||
if len(data) > 0 {
|
||||
bf.WriteBytes(data)
|
||||
} else {
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint8(2)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(0)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.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 := TimeMidnight()
|
||||
switch state {
|
||||
case 1:
|
||||
bf.WriteUint32(uint32(midnight.Unix()))
|
||||
bf.WriteUint32(uint32(midnight.Add(3 * 24 * time.Hour).Unix()))
|
||||
bf.WriteUint32(uint32(midnight.Add(13 * 24 * time.Hour).Unix()))
|
||||
bf.WriteUint32(uint32(midnight.Add(20 * 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(10 * 24 * time.Hour).Unix()))
|
||||
bf.WriteUint32(uint32(midnight.Add(17 * 24 * time.Hour).Unix()))
|
||||
case 3:
|
||||
bf.WriteUint32(uint32(midnight.Add(-13 * 24 * time.Hour).Unix()))
|
||||
bf.WriteUint32(uint32(midnight.Add(-10 * 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(TimeAdjusted().Unix())) // TS Current Time
|
||||
bf.WriteUint8(3)
|
||||
bf.WriteBytes(make([]byte, 4))
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
return
|
||||
}
|
||||
bf.WriteUint32(uint32(TimeAdjusted().Unix())) // TS Current Time
|
||||
bf.WriteUint8(3)
|
||||
ps.Uint8(bf, "", false)
|
||||
bf.WriteUint16(0) // numEvents
|
||||
bf.WriteUint8(0) // numCups
|
||||
|
||||
/*
|
||||
struct event
|
||||
uint32 eventID
|
||||
uint16 unk
|
||||
uint16 unk
|
||||
uint32 unk
|
||||
psUint8 name
|
||||
|
||||
struct cup
|
||||
uint32 cupID
|
||||
uint16 unk
|
||||
uint16 unk
|
||||
uint16 unk
|
||||
psUint8 name
|
||||
psUint16 desc
|
||||
*/
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func cleanupFesta(s *Session) {
|
||||
s.server.db.Exec("DELETE FROM events WHERE event_type='festa'")
|
||||
s.server.db.Exec("DELETE FROM festa_registrations")
|
||||
s.server.db.Exec("DELETE FROM festa_prizes_accepted")
|
||||
s.server.db.Exec("UPDATE guild_characters SET souls=0")
|
||||
}
|
||||
|
||||
func generateFestaTimestamps(s *Session, start uint32, debug bool) []uint32 {
|
||||
timestamps := make([]uint32, 5)
|
||||
midnight := TimeMidnight()
|
||||
if debug && start <= 3 {
|
||||
midnight := uint32(midnight.Unix())
|
||||
switch start {
|
||||
case 1:
|
||||
timestamps[0] = midnight
|
||||
timestamps[1] = timestamps[0] + 604800
|
||||
timestamps[2] = timestamps[1] + 604800
|
||||
timestamps[3] = timestamps[2] + 9000
|
||||
timestamps[4] = timestamps[3] + 1240200
|
||||
case 2:
|
||||
timestamps[0] = midnight - 604800
|
||||
timestamps[1] = midnight
|
||||
timestamps[2] = timestamps[1] + 604800
|
||||
timestamps[3] = timestamps[2] + 9000
|
||||
timestamps[4] = timestamps[3] + 1240200
|
||||
case 3:
|
||||
timestamps[0] = midnight - 1209600
|
||||
timestamps[1] = midnight - 604800
|
||||
timestamps[2] = midnight
|
||||
timestamps[3] = timestamps[2] + 9000
|
||||
timestamps[4] = timestamps[3] + 1240200
|
||||
}
|
||||
return timestamps
|
||||
}
|
||||
if start == 0 || TimeAdjusted().Unix() > int64(start)+2977200 {
|
||||
cleanupFesta(s)
|
||||
// Generate a new festa, starting midnight tomorrow
|
||||
start = uint32(midnight.Add(24 * time.Hour).Unix())
|
||||
s.server.db.Exec("INSERT INTO events (event_type, start_time) VALUES ('festa', to_timestamp($1)::timestamp without time zone)", start)
|
||||
}
|
||||
timestamps[0] = start
|
||||
timestamps[1] = timestamps[0] + 604800
|
||||
timestamps[2] = timestamps[1] + 604800
|
||||
timestamps[3] = timestamps[2] + 9000
|
||||
timestamps[4] = timestamps[3] + 1240200
|
||||
return timestamps
|
||||
}
|
||||
|
||||
type FestaTrial struct {
|
||||
ID uint32 `db:"id"`
|
||||
Objective uint16 `db:"objective"`
|
||||
GoalID uint32 `db:"goal_id"`
|
||||
TimesReq uint16 `db:"times_req"`
|
||||
Locale uint16 `db:"locale_req"`
|
||||
Reward uint16 `db:"reward"`
|
||||
Monopoly uint16
|
||||
Unk uint16
|
||||
}
|
||||
|
||||
type FestaReward struct {
|
||||
Unk0 uint8
|
||||
Unk1 uint8
|
||||
ItemType uint16
|
||||
Quantity uint16
|
||||
ItemID uint16
|
||||
Unk5 uint16
|
||||
Unk6 uint16
|
||||
Unk7 uint8
|
||||
}
|
||||
|
||||
func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfInfoFesta)
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
id, start := uint32(0xDEADBEEF), uint32(0)
|
||||
rows, _ := s.server.db.Queryx("SELECT id, (EXTRACT(epoch FROM start_time)::int) as start_time FROM events WHERE event_type='festa'")
|
||||
for rows.Next() {
|
||||
rows.Scan(&id, &start)
|
||||
}
|
||||
|
||||
var timestamps []uint32
|
||||
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.FestaEvent >= 0 {
|
||||
if s.server.erupeConfig.DevModeOptions.FestaEvent == 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
timestamps = generateFestaTimestamps(s, uint32(s.server.erupeConfig.DevModeOptions.FestaEvent), true)
|
||||
} else {
|
||||
timestamps = generateFestaTimestamps(s, start, false)
|
||||
}
|
||||
|
||||
if timestamps[0] > uint32(TimeAdjusted().Unix()) {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
|
||||
var blueSouls, redSouls uint32
|
||||
s.server.db.QueryRow("SELECT SUM(gc.souls) FROM guild_characters gc INNER JOIN festa_registrations fr ON fr.guild_id = gc.guild_id WHERE fr.team = 'blue'").Scan(&blueSouls)
|
||||
s.server.db.QueryRow("SELECT SUM(gc.souls) FROM guild_characters gc INNER JOIN festa_registrations fr ON fr.guild_id = gc.guild_id WHERE fr.team = 'red'").Scan(&redSouls)
|
||||
|
||||
bf.WriteUint32(id)
|
||||
for _, timestamp := range timestamps {
|
||||
bf.WriteUint32(timestamp)
|
||||
}
|
||||
bf.WriteUint32(uint32(TimeAdjusted().Unix()))
|
||||
bf.WriteUint8(4)
|
||||
ps.Uint8(bf, "", false)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(blueSouls)
|
||||
bf.WriteUint32(redSouls)
|
||||
|
||||
var trials []FestaTrial
|
||||
var trial FestaTrial
|
||||
rows, _ = s.server.db.Queryx("SELECT * FROM festa_trials")
|
||||
for rows.Next() {
|
||||
err := rows.StructScan(&trial)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
trials = append(trials, trial)
|
||||
}
|
||||
bf.WriteUint16(uint16(len(trials)))
|
||||
for _, trial := range trials {
|
||||
bf.WriteUint32(trial.ID)
|
||||
bf.WriteUint16(trial.Objective)
|
||||
bf.WriteUint32(trial.GoalID)
|
||||
bf.WriteUint16(trial.TimesReq)
|
||||
bf.WriteUint16(trial.Locale)
|
||||
bf.WriteUint16(trial.Reward)
|
||||
trial.Monopoly = 0xFFFF // NYI
|
||||
bf.WriteUint16(trial.Monopoly)
|
||||
bf.WriteUint16(trial.Unk)
|
||||
}
|
||||
|
||||
// The Winner and Loser Armor IDs are missing
|
||||
rewards := []FestaReward{
|
||||
{1, 0, 7, 350, 1520, 0, 0, 0},
|
||||
{1, 0, 7, 1000, 7011, 0, 0, 1},
|
||||
{1, 0, 12, 1000, 0, 0, 0, 0},
|
||||
{1, 0, 13, 0, 0, 0, 0, 0},
|
||||
//{1, 0, 1, 0, 0, 0, 0, 0},
|
||||
{2, 0, 7, 350, 1520, 0, 0, 0},
|
||||
{2, 0, 7, 1000, 7011, 0, 0, 1},
|
||||
{2, 0, 12, 1000, 0, 0, 0, 0},
|
||||
{2, 0, 13, 0, 0, 0, 0, 0},
|
||||
//{2, 0, 4, 0, 0, 0, 0, 0},
|
||||
{3, 0, 7, 350, 1520, 0, 0, 0},
|
||||
{3, 0, 7, 1000, 7011, 0, 0, 1},
|
||||
{3, 0, 12, 1000, 0, 0, 0, 0},
|
||||
{3, 0, 13, 0, 0, 0, 0, 0},
|
||||
//{3, 0, 1, 0, 0, 0, 0, 0},
|
||||
{4, 0, 7, 350, 1520, 0, 0, 0},
|
||||
{4, 0, 7, 1000, 7011, 0, 0, 1},
|
||||
{4, 0, 12, 1000, 0, 0, 0, 0},
|
||||
{4, 0, 13, 0, 0, 0, 0, 0},
|
||||
//{4, 0, 4, 0, 0, 0, 0, 0},
|
||||
{5, 0, 7, 350, 1520, 0, 0, 0},
|
||||
{5, 0, 7, 1000, 7011, 0, 0, 1},
|
||||
{5, 0, 12, 1000, 0, 0, 0, 0},
|
||||
{5, 0, 13, 0, 0, 0, 0, 0},
|
||||
//{5, 0, 1, 0, 0, 0, 0, 0},
|
||||
}
|
||||
bf.WriteUint16(uint16(len(rewards)))
|
||||
for _, reward := range rewards {
|
||||
bf.WriteUint8(reward.Unk0)
|
||||
bf.WriteUint8(reward.Unk1)
|
||||
bf.WriteUint16(reward.ItemType)
|
||||
bf.WriteUint16(reward.Quantity)
|
||||
bf.WriteUint16(reward.ItemID)
|
||||
bf.WriteUint16(reward.Unk5)
|
||||
bf.WriteUint16(reward.Unk6)
|
||||
bf.WriteUint8(reward.Unk7)
|
||||
}
|
||||
|
||||
if _config.ErupeConfig.RealClientMode <= _config.G61 {
|
||||
if s.server.erupeConfig.GameplayOptions.MaximumFP > 0xFFFF {
|
||||
s.server.erupeConfig.GameplayOptions.MaximumFP = 0xFFFF
|
||||
}
|
||||
bf.WriteUint16(uint16(s.server.erupeConfig.GameplayOptions.MaximumFP))
|
||||
} else {
|
||||
bf.WriteUint32(s.server.erupeConfig.GameplayOptions.MaximumFP)
|
||||
}
|
||||
bf.WriteUint16(500)
|
||||
|
||||
categoryWinners := uint16(0) // NYI
|
||||
bf.WriteUint16(categoryWinners)
|
||||
for i := uint16(0); i < categoryWinners; i++ {
|
||||
bf.WriteUint32(0) // Guild ID
|
||||
bf.WriteUint16(i + 1) // Category ID
|
||||
bf.WriteUint16(0) // Festa Team
|
||||
ps.Uint8(bf, "", true) // Guild Name
|
||||
}
|
||||
|
||||
dailyWinners := uint16(0) // NYI
|
||||
bf.WriteUint16(dailyWinners)
|
||||
for i := uint16(0); i < dailyWinners; i++ {
|
||||
bf.WriteUint32(0) // Guild ID
|
||||
bf.WriteUint16(i + 1) // Category ID
|
||||
bf.WriteUint16(0) // Festa Team
|
||||
ps.Uint8(bf, "", true) // Guild Name
|
||||
}
|
||||
|
||||
// Unknown values
|
||||
bf.WriteUint32(1)
|
||||
bf.WriteUint32(5000)
|
||||
bf.WriteUint32(2000)
|
||||
bf.WriteUint32(1000)
|
||||
bf.WriteUint32(100)
|
||||
bf.WriteUint16(300)
|
||||
bf.WriteUint16(200)
|
||||
bf.WriteUint16(150)
|
||||
bf.WriteUint16(100)
|
||||
bf.WriteUint16(50)
|
||||
|
||||
ps.Uint16(bf, "", false)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
// state festa (U)ser
|
||||
func handleMsgMhfStateFestaU(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfStateFestaU)
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
applicant := false
|
||||
if guild != nil {
|
||||
applicant, _ = guild.HasApplicationForCharID(s, s.charID)
|
||||
}
|
||||
if err != nil || guild == nil || applicant {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
var souls, exists uint32
|
||||
s.server.db.QueryRow("SELECT souls FROM guild_characters WHERE character_id=$1", s.charID).Scan(&souls)
|
||||
err = s.server.db.QueryRow("SELECT prize_id FROM festa_prizes_accepted WHERE prize_id=0 AND character_id=$1", s.charID).Scan(&exists)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(souls)
|
||||
if err != nil {
|
||||
bf.WriteBool(true)
|
||||
bf.WriteBool(false)
|
||||
} else {
|
||||
bf.WriteBool(false)
|
||||
bf.WriteBool(true)
|
||||
}
|
||||
bf.WriteUint16(0) // Unk
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
// state festa (G)uild
|
||||
func handleMsgMhfStateFestaG(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfStateFestaG)
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
applicant := false
|
||||
if guild != nil {
|
||||
applicant, _ = guild.HasApplicationForCharID(s, s.charID)
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
if err != nil || guild == nil || applicant {
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(0xFFFFFFFF)
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(0)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
return
|
||||
}
|
||||
resp.WriteUint32(guild.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)
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
if err != nil || guild == nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
members, err := GetGuildMembers(s, guild.ID, false)
|
||||
if err != nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(uint16(len(members)))
|
||||
bf.WriteUint16(0) // Unk
|
||||
sort.Slice(members, func(i, j int) bool {
|
||||
return members[i].Souls > members[j].Souls
|
||||
})
|
||||
for _, member := range members {
|
||||
bf.WriteUint32(member.CharID)
|
||||
bf.WriteUint32(member.Souls)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfVoteFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfVoteFesta)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfEntryFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEntryFesta)
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
if err != nil || guild == nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
team := uint32(token.RNG().Intn(2))
|
||||
switch team {
|
||||
case 0:
|
||||
s.server.db.Exec("INSERT INTO festa_registrations VALUES ($1, 'blue')", guild.ID)
|
||||
case 1:
|
||||
s.server.db.Exec("INSERT INTO festa_registrations VALUES ($1, 'red')", guild.ID)
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(team)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfChargeFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfChargeFesta)
|
||||
s.server.db.Exec("UPDATE guild_characters SET souls=souls+$1 WHERE character_id=$2", pkt.Souls, s.charID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAcquireFesta)
|
||||
s.server.db.Exec("INSERT INTO public.festa_prizes_accepted VALUES (0, $1)", s.charID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireFestaPersonalPrize(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAcquireFestaPersonalPrize)
|
||||
s.server.db.Exec("INSERT INTO public.festa_prizes_accepted VALUES ($1, $2)", pkt.PrizeID, s.charID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireFestaIntermediatePrize(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAcquireFestaIntermediatePrize)
|
||||
s.server.db.Exec("INSERT INTO public.festa_prizes_accepted VALUES ($1, $2)", pkt.PrizeID, s.charID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
type Prize struct {
|
||||
ID uint32 `db:"id"`
|
||||
Tier uint32 `db:"tier"`
|
||||
SoulsReq uint32 `db:"souls_req"`
|
||||
ItemID uint32 `db:"item_id"`
|
||||
NumItem uint32 `db:"num_item"`
|
||||
Claimed int `db:"claimed"`
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateFestaPersonalPrize(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateFestaPersonalPrize)
|
||||
rows, _ := s.server.db.Queryx(`SELECT id, tier, souls_req, item_id, num_item, (SELECT count(*) FROM festa_prizes_accepted fpa WHERE fp.id = fpa.prize_id AND fpa.character_id = $1) AS claimed FROM festa_prizes fp WHERE type='personal'`, s.charID)
|
||||
var count uint32
|
||||
prizeData := byteframe.NewByteFrame()
|
||||
for rows.Next() {
|
||||
prize := &Prize{}
|
||||
err := rows.StructScan(&prize)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
count++
|
||||
prizeData.WriteUint32(prize.ID)
|
||||
prizeData.WriteUint32(prize.Tier)
|
||||
prizeData.WriteUint32(prize.SoulsReq)
|
||||
prizeData.WriteUint32(7) // Unk
|
||||
prizeData.WriteUint32(prize.ItemID)
|
||||
prizeData.WriteUint32(prize.NumItem)
|
||||
prizeData.WriteBool(prize.Claimed > 0)
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(count)
|
||||
bf.WriteBytes(prizeData.Data())
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateFestaIntermediatePrize(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateFestaIntermediatePrize)
|
||||
rows, _ := s.server.db.Queryx(`SELECT id, tier, souls_req, item_id, num_item, (SELECT count(*) FROM festa_prizes_accepted fpa WHERE fp.id = fpa.prize_id AND fpa.character_id = $1) AS claimed FROM festa_prizes fp WHERE type='guild'`, s.charID)
|
||||
var count uint32
|
||||
prizeData := byteframe.NewByteFrame()
|
||||
for rows.Next() {
|
||||
prize := &Prize{}
|
||||
err := rows.StructScan(&prize)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
count++
|
||||
prizeData.WriteUint32(prize.ID)
|
||||
prizeData.WriteUint32(prize.Tier)
|
||||
prizeData.WriteUint32(prize.SoulsReq)
|
||||
prizeData.WriteUint32(7) // Unk
|
||||
prizeData.WriteUint32(prize.ItemID)
|
||||
prizeData.WriteUint32(prize.NumItem)
|
||||
prizeData.WriteBool(prize.Claimed > 0)
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(count)
|
||||
bf.WriteBytes(prizeData.Data())
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
1996
server/channelserver/handlers_guild.go
Normal file
1996
server/channelserver/handlers_guild.go
Normal file
File diff suppressed because it is too large
Load Diff
95
server/channelserver/handlers_guild_adventure.go
Normal file
95
server/channelserver/handlers_guild_adventure.go
Normal file
@@ -0,0 +1,95 @@
|
||||
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.Error("Failed to get guild adventures from db", zap.Error(err))
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
||||
return
|
||||
}
|
||||
temp := byteframe.NewByteFrame()
|
||||
count := 0
|
||||
for data.Next() {
|
||||
count++
|
||||
adventureData := &GuildAdventure{}
|
||||
err = data.StructScan(&adventureData)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
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, TimeAdjusted().Unix(), TimeAdjusted().Add(6*time.Hour).Unix())
|
||||
if err != nil {
|
||||
s.logger.Error("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.Error("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.Error("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.Error("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, TimeAdjusted().Unix(), TimeAdjusted().Add(1*time.Hour).Unix())
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to register guild adventure", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
234
server/channelserver/handlers_guild_alliance.go
Normal file
234
server/channelserver/handlers_guild_alliance.go
Normal file
@@ -0,0 +1,234 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
"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.Error("Failed to get parent guild info", zap.Error(err))
|
||||
return nil, err
|
||||
} else {
|
||||
alliance.ParentGuild = *parentGuild
|
||||
alliance.TotalMembers += parentGuild.MemberCount
|
||||
}
|
||||
|
||||
if alliance.SubGuild1ID > 0 {
|
||||
subGuild1, err := GetGuildInfoByID(s, alliance.SubGuild1ID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get sub guild 1 info", zap.Error(err))
|
||||
return nil, err
|
||||
} else {
|
||||
alliance.SubGuild1 = *subGuild1
|
||||
alliance.TotalMembers += subGuild1.MemberCount
|
||||
}
|
||||
}
|
||||
|
||||
if alliance.SubGuild2ID > 0 {
|
||||
subGuild2, err := GetGuildInfoByID(s, alliance.SubGuild2ID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get sub guild 2 info", zap.Error(err))
|
||||
return nil, 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.Error("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.Error("Failed to get guild info", zap.Error(err))
|
||||
}
|
||||
alliance, err := GetAllianceData(s, pkt.AllianceID)
|
||||
if err != nil {
|
||||
s.logger.Error("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.Error("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 {
|
||||
if guild.ID == alliance.SubGuild1ID && alliance.SubGuild2ID > 0 {
|
||||
s.server.db.Exec(`UPDATE guild_alliances SET sub1_id = sub2_id, sub2_id = NULL WHERE id = $1`, alliance.ID)
|
||||
} else if guild.ID == alliance.SubGuild1ID && alliance.SubGuild2ID == 0 {
|
||||
s.server.db.Exec(`UPDATE guild_alliances SET sub1_id = NULL WHERE id = $1`, alliance.ID)
|
||||
} else {
|
||||
s.server.db.Exec(`UPDATE guild_alliances SET sub2_id = NULL WHERE id = $1`, alliance.ID)
|
||||
}
|
||||
// TODO: Handle deleting Alliance applications
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
} else {
|
||||
s.logger.Warn(
|
||||
"Non-owner of guild attempted alliance leave",
|
||||
zap.Uint32("CharID", s.charID),
|
||||
)
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
case mhfpacket.OPERATE_JOINT_KICK:
|
||||
if alliance.ParentGuild.LeaderCharID == s.charID {
|
||||
_ = pkt.UnkData.ReadUint8()
|
||||
kickedGuildID := pkt.UnkData.ReadUint32()
|
||||
if kickedGuildID == alliance.SubGuild1ID && alliance.SubGuild2ID > 0 {
|
||||
s.server.db.Exec(`UPDATE guild_alliances SET sub1_id = sub2_id, sub2_id = NULL WHERE id = $1`, alliance.ID)
|
||||
} else if kickedGuildID == alliance.SubGuild1ID && alliance.SubGuild2ID == 0 {
|
||||
s.server.db.Exec(`UPDATE guild_alliances SET sub1_id = NULL WHERE id = $1`, alliance.ID)
|
||||
} else {
|
||||
s.server.db.Exec(`UPDATE guild_alliances SET sub2_id = NULL WHERE id = $1`, alliance.ID)
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
} else {
|
||||
s.logger.Warn(
|
||||
"Non-owner of alliance attempted kick",
|
||||
zap.Uint32("CharID", s.charID),
|
||||
zap.Uint32("AllyID", alliance.ID),
|
||||
)
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
default:
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
panic(fmt.Sprintf("Unhandled operate joint action '%d'", pkt.Action))
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfInfoJoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfInfoJoint)
|
||||
bf := byteframe.NewByteFrame()
|
||||
alliance, err := GetAllianceData(s, pkt.AllianceID)
|
||||
if err != nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
} else {
|
||||
bf.WriteUint32(alliance.ID)
|
||||
bf.WriteUint32(uint32(alliance.CreatedAt.Unix()))
|
||||
bf.WriteUint16(alliance.TotalMembers)
|
||||
bf.WriteUint16(0x0000) // Unk
|
||||
ps.Uint16(bf, alliance.Name, true)
|
||||
if alliance.SubGuild1ID > 0 {
|
||||
if alliance.SubGuild2ID > 0 {
|
||||
bf.WriteUint8(3)
|
||||
} else {
|
||||
bf.WriteUint8(2)
|
||||
}
|
||||
} else {
|
||||
bf.WriteUint8(1)
|
||||
}
|
||||
bf.WriteUint32(alliance.ParentGuildID)
|
||||
bf.WriteUint32(alliance.ParentGuild.LeaderCharID)
|
||||
bf.WriteUint16(alliance.ParentGuild.Rank())
|
||||
bf.WriteUint16(alliance.ParentGuild.MemberCount)
|
||||
ps.Uint16(bf, alliance.ParentGuild.Name, true)
|
||||
ps.Uint16(bf, alliance.ParentGuild.LeaderName, true)
|
||||
if alliance.SubGuild1ID > 0 {
|
||||
bf.WriteUint32(alliance.SubGuild1ID)
|
||||
bf.WriteUint32(alliance.SubGuild1.LeaderCharID)
|
||||
bf.WriteUint16(alliance.SubGuild1.Rank())
|
||||
bf.WriteUint16(alliance.SubGuild1.MemberCount)
|
||||
ps.Uint16(bf, alliance.SubGuild1.Name, true)
|
||||
ps.Uint16(bf, alliance.SubGuild1.LeaderName, true)
|
||||
}
|
||||
if alliance.SubGuild2ID > 0 {
|
||||
bf.WriteUint32(alliance.SubGuild2ID)
|
||||
bf.WriteUint32(alliance.SubGuild2.LeaderCharID)
|
||||
bf.WriteUint16(alliance.SubGuild2.Rank())
|
||||
bf.WriteUint16(alliance.SubGuild2.MemberCount)
|
||||
ps.Uint16(bf, alliance.SubGuild2.Name, true)
|
||||
ps.Uint16(bf, alliance.SubGuild2.LeaderName, true)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
}
|
||||
153
server/channelserver/handlers_guild_member.go
Normal file
153
server/channelserver/handlers_guild_member.go
Normal file
@@ -0,0 +1,153 @@
|
||||
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"`
|
||||
Souls uint32 `db:"souls"`
|
||||
RPToday uint16 `db:"rp_today"`
|
||||
RPYesterday uint16 `db:"rp_yesterday"`
|
||||
Name string `db:"name"`
|
||||
IsApplicant bool `db:"is_applicant"`
|
||||
OrderIndex uint16 `db:"order_index"`
|
||||
LastLogin uint32 `db:"last_login"`
|
||||
Recruiter bool `db:"recruiter"`
|
||||
AvoidLeadership bool `db:"avoid_leadership"`
|
||||
IsLeader bool `db:"is_leader"`
|
||||
HRP uint16 `db:"hrp"`
|
||||
GR uint16 `db:"gr"`
|
||||
WeaponID uint16 `db:"weapon_id"`
|
||||
WeaponType uint8 `db:"weapon_type"`
|
||||
}
|
||||
|
||||
func (gm *GuildMember) CanRecruit() bool {
|
||||
if gm.Recruiter {
|
||||
return true
|
||||
}
|
||||
if gm.OrderIndex <= 3 {
|
||||
return true
|
||||
}
|
||||
if gm.IsLeader {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (gm *GuildMember) IsSubLeader() bool {
|
||||
return gm.OrderIndex <= 3
|
||||
}
|
||||
|
||||
func (gm *GuildMember) Save(s *Session) error {
|
||||
_, err := s.server.db.Exec("UPDATE guild_characters SET avoid_leadership=$1, order_index=$2 WHERE character_id=$3", gm.AvoidLeadership, gm.OrderIndex, 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
|
||||
}
|
||||
|
||||
const guildMembersSelectSQL = `
|
||||
SELECT
|
||||
g.id as guild_id,
|
||||
joined_at,
|
||||
coalesce(souls, 0) as souls,
|
||||
COALESCE(rp_today, 0) AS rp_today,
|
||||
COALESCE(rp_yesterday, 0) AS rp_yesterday,
|
||||
c.name,
|
||||
character.character_id,
|
||||
coalesce(gc.order_index, 0) as order_index,
|
||||
c.last_login,
|
||||
coalesce(gc.recruiter, false) as recruiter,
|
||||
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 (
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/stringsupport"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"io"
|
||||
)
|
||||
|
||||
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.CanRecruit() {
|
||||
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)
|
||||
}
|
||||
|
||||
mail := &Mail{
|
||||
SenderID: s.charID,
|
||||
RecipientID: pkt.CharID,
|
||||
Subject: s.server.dict["guildInviteName"],
|
||||
Body: fmt.Sprintf(
|
||||
s.server.dict["guildInvite"],
|
||||
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.CanRecruit() {
|
||||
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)
|
||||
bf := byteframe.NewByteFrame()
|
||||
guild, err := GetGuildInfoByCharacterId(s, pkt.LeaderID)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
app, err := guild.GetApplicationForCharID(s, s.charID, GuildApplicationTypeInvited)
|
||||
|
||||
if app == nil || err != nil {
|
||||
s.logger.Warn(
|
||||
"Guild invite missing, deleted?",
|
||||
zap.Error(err),
|
||||
zap.Uint32("guildID", guild.ID),
|
||||
zap.Uint32("charID", s.charID),
|
||||
)
|
||||
bf.WriteUint32(7)
|
||||
bf.WriteUint32(guild.ID)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
return
|
||||
}
|
||||
|
||||
var mail []Mail
|
||||
if pkt.Answer {
|
||||
err = guild.AcceptApplication(s, s.charID)
|
||||
mail = append(mail, Mail{
|
||||
RecipientID: s.charID,
|
||||
Subject: s.server.dict["guildInviteSuccessName"],
|
||||
Body: fmt.Sprintf(s.server.dict["guildInviteSuccess"], guild.Name),
|
||||
IsSystemMessage: true,
|
||||
})
|
||||
mail = append(mail, Mail{
|
||||
SenderID: s.charID,
|
||||
RecipientID: pkt.LeaderID,
|
||||
Subject: s.server.dict["guildInviteAcceptedName"],
|
||||
Body: fmt.Sprintf(s.server.dict["guildInviteAccepted"], guild.Name),
|
||||
IsSystemMessage: true,
|
||||
})
|
||||
} else {
|
||||
err = guild.RejectApplication(s, s.charID)
|
||||
mail = append(mail, Mail{
|
||||
RecipientID: s.charID,
|
||||
Subject: s.server.dict["guildInviteRejectName"],
|
||||
Body: fmt.Sprintf(s.server.dict["guildInviteReject"], guild.Name),
|
||||
IsSystemMessage: true,
|
||||
})
|
||||
mail = append(mail, Mail{
|
||||
SenderID: s.charID,
|
||||
RecipientID: pkt.LeaderID,
|
||||
Subject: s.server.dict["guildInviteDeclined"],
|
||||
Body: fmt.Sprintf(s.server.dict["guildInviteDeclined"], guild.Name),
|
||||
IsSystemMessage: true,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
bf.WriteUint32(7)
|
||||
bf.WriteUint32(guild.ID)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
} else {
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(guild.ID)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
for _, m := range mail {
|
||||
m.Send(s, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGuildScoutList)
|
||||
|
||||
guildInfo, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
|
||||
if guildInfo == nil && s.prevGuildID == 0 {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
} else {
|
||||
guildInfo, err = GetGuildInfoByID(s, s.prevGuildID)
|
||||
if guildInfo == nil || err != nil {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
rows, err := s.server.db.Queryx(`
|
||||
SELECT c.id, c.name, c.hrp, c.gr, 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))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
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, actorID uint32
|
||||
var hrp, gr uint16
|
||||
|
||||
err = rows.Scan(&charID, &charName, &hrp, &gr, &actorID)
|
||||
|
||||
if err != nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, nil)
|
||||
continue
|
||||
}
|
||||
|
||||
// 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(TimeAdjusted().Unix()))
|
||||
bf.WriteUint16(hrp) // HR?
|
||||
bf.WriteUint16(gr) // GR?
|
||||
bf.WriteBytes(stringsupport.PaddedString(charName, 32, true))
|
||||
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)
|
||||
}
|
||||
181
server/channelserver/handlers_guild_tresure.go
Normal file
181
server/channelserver/handlers_guild_tresure.go
Normal file
@@ -0,0 +1,181 @@
|
||||
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 AND $2 < return+604800", guild.ID, TimeAdjusted().Unix())
|
||||
for rows.Next() {
|
||||
hunt := &TreasureHunt{}
|
||||
err = rows.StructScan(&hunt)
|
||||
// Remove self from other hunter count
|
||||
hunt.Hunters = stringsupport.CSVRemove(hunt.Hunters, int(s.charID))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if pkt.MaxHunts == 1 {
|
||||
if hunt.HostID != s.charID || hunt.Acquired {
|
||||
continue
|
||||
}
|
||||
hunts++
|
||||
bf.WriteUint32(hunt.HuntID)
|
||||
bf.WriteUint32(hunt.Destination)
|
||||
bf.WriteUint32(hunt.Level)
|
||||
bf.WriteUint32(uint32(stringsupport.CSVLength(hunt.Hunters)))
|
||||
bf.WriteUint32(hunt.Return)
|
||||
bf.WriteBool(false)
|
||||
bf.WriteBool(false)
|
||||
bf.WriteBytes(hunt.HuntData)
|
||||
break
|
||||
} else if pkt.MaxHunts == 30 && hunt.Acquired && hunt.Level == 2 {
|
||||
if hunts == 30 {
|
||||
break
|
||||
}
|
||||
hunts++
|
||||
bf.WriteUint32(hunt.HuntID)
|
||||
bf.WriteUint32(hunt.Destination)
|
||||
bf.WriteUint32(hunt.Level)
|
||||
bf.WriteUint32(uint32(stringsupport.CSVLength(hunt.Hunters)))
|
||||
bf.WriteUint32(hunt.Return)
|
||||
bf.WriteBool(hunt.Claimed)
|
||||
bf.WriteBool(stringsupport.CSVContains(hunt.Treasure, int(s.charID)))
|
||||
bf.WriteBytes(hunt.HuntData)
|
||||
}
|
||||
}
|
||||
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, TimeAdjusted().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, err := s.server.db.Queryx("SELECT id, hunters FROM guild_hunts WHERE guild_id=$1", guild.ID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for rows.Next() {
|
||||
rows.Scan(&huntID, &hunters)
|
||||
hunters = stringsupport.CSVRemove(hunters, int(s.charID))
|
||||
s.server.db.Exec("UPDATE guild_hunts SET hunters=$1 WHERE id=$2", hunters, huntID)
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfOperateGuildTresureReport(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfOperateGuildTresureReport)
|
||||
var csv string
|
||||
if pkt.State == 0 { // Report registration
|
||||
// Unregister from all other hunts
|
||||
treasureHuntUnregister(s)
|
||||
if pkt.HuntID != 0 {
|
||||
// Register to selected hunt
|
||||
err := s.server.db.QueryRow("SELECT hunters FROM guild_hunts WHERE id=$1", pkt.HuntID).Scan(&csv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
csv = stringsupport.CSVAdd(csv, int(s.charID))
|
||||
_, err = s.server.db.Exec("UPDATE guild_hunts SET hunters=$1 WHERE id=$2", csv, pkt.HuntID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
} else if pkt.State == 1 { // Collected by hunter
|
||||
s.server.db.Exec("UPDATE guild_hunts SET hunters='', claimed=true WHERE id=$1", pkt.HuntID)
|
||||
} else if pkt.State == 2 { // Claim treasure
|
||||
err := s.server.db.QueryRow("SELECT treasure FROM guild_hunts WHERE id=$1", pkt.HuntID).Scan(&csv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
csv = stringsupport.CSVAdd(csv, int(s.charID))
|
||||
_, err = s.server.db.Exec("UPDATE guild_hunts SET treasure=$1 WHERE id=$2", csv, pkt.HuntID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
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))
|
||||
}
|
||||
554
server/channelserver/handlers_house.go
Normal file
554
server/channelserver/handlers_house.go
Normal file
@@ -0,0 +1,554 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
"erupe-ce/common/stringsupport"
|
||||
_config "erupe-ce/config"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
const warehouseNamesQuery = `
|
||||
SELECT
|
||||
COALESCE(item0name, ''),
|
||||
COALESCE(item1name, ''),
|
||||
COALESCE(item2name, ''),
|
||||
COALESCE(item3name, ''),
|
||||
COALESCE(item4name, ''),
|
||||
COALESCE(item5name, ''),
|
||||
COALESCE(item6name, ''),
|
||||
COALESCE(item7name, ''),
|
||||
COALESCE(item8name, ''),
|
||||
COALESCE(item9name, ''),
|
||||
COALESCE(equip0name, ''),
|
||||
COALESCE(equip1name, ''),
|
||||
COALESCE(equip2name, ''),
|
||||
COALESCE(equip3name, ''),
|
||||
COALESCE(equip4name, ''),
|
||||
COALESCE(equip5name, ''),
|
||||
COALESCE(equip6name, ''),
|
||||
COALESCE(equip7name, ''),
|
||||
COALESCE(equip8name, ''),
|
||||
COALESCE(equip9name, '')
|
||||
FROM warehouse
|
||||
`
|
||||
|
||||
func handleMsgMhfUpdateInterior(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfUpdateInterior)
|
||||
s.server.db.Exec(`UPDATE user_binary SET house_furniture=$1 WHERE id=$2`, pkt.InteriorData, s.charID)
|
||||
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"`
|
||||
HouseState uint8 `db:"house_state"`
|
||||
HousePassword string `db:"house_password"`
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateHouse)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(0)
|
||||
var houses []HouseData
|
||||
houseQuery := `SELECT c.id, hrp, gr, name, COALESCE(ub.house_state, 2) as house_state, COALESCE(ub.house_password, '') as house_password
|
||||
FROM characters c LEFT JOIN user_binary ub ON ub.id = c.id WHERE c.id=$1`
|
||||
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(houseQuery, cid)
|
||||
err := row.StructScan(&house)
|
||||
if err == nil {
|
||||
houses = append(houses, house)
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
if err != nil || guild == nil {
|
||||
break
|
||||
}
|
||||
guildMembers, err := GetGuildMembers(s, guild.ID, false)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
for _, member := range guildMembers {
|
||||
house := HouseData{}
|
||||
row := s.server.db.QueryRowx(houseQuery, member.CharID)
|
||||
err = row.StructScan(&house)
|
||||
if err == nil {
|
||||
houses = append(houses, house)
|
||||
}
|
||||
}
|
||||
case 3:
|
||||
houseQuery = `SELECT c.id, hrp, gr, name, COALESCE(ub.house_state, 2) as house_state, COALESCE(ub.house_password, '') as house_password
|
||||
FROM characters c LEFT JOIN user_binary ub ON ub.id = c.id WHERE name ILIKE $1`
|
||||
house := HouseData{}
|
||||
rows, _ := s.server.db.Queryx(houseQuery, fmt.Sprintf(`%%%s%%`, pkt.Name))
|
||||
for rows.Next() {
|
||||
err := rows.StructScan(&house)
|
||||
if err == nil {
|
||||
houses = append(houses, house)
|
||||
}
|
||||
}
|
||||
case 4:
|
||||
house := HouseData{}
|
||||
row := s.server.db.QueryRowx(houseQuery, pkt.CharID)
|
||||
err := row.StructScan(&house)
|
||||
if err == nil {
|
||||
houses = append(houses, house)
|
||||
}
|
||||
case 5: // Recent visitors
|
||||
break
|
||||
}
|
||||
for _, house := range houses {
|
||||
bf.WriteUint32(house.CharID)
|
||||
bf.WriteUint8(house.HouseState)
|
||||
if len(house.HousePassword) > 0 {
|
||||
bf.WriteUint8(3)
|
||||
} else {
|
||||
bf.WriteUint8(0)
|
||||
}
|
||||
bf.WriteUint16(house.HRP)
|
||||
bf.WriteUint16(house.GR)
|
||||
ps.Uint8(bf, house.Name, true)
|
||||
}
|
||||
bf.Seek(0, 0)
|
||||
bf.WriteUint16(uint16(len(houses)))
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.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.server.db.Exec(`UPDATE user_binary SET house_state=$1, house_password=$2 WHERE id=$3`, pkt.State, pkt.Password, s.charID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfLoadHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadHouse)
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
var state uint8
|
||||
var password string
|
||||
s.server.db.QueryRow(`SELECT COALESCE(house_state, 2) as house_state, COALESCE(house_password, '') as house_password FROM user_binary WHERE id=$1
|
||||
`, pkt.CharID).Scan(&state, &password)
|
||||
|
||||
if pkt.Destination != 9 && len(pkt.Password) > 0 && pkt.CheckPass {
|
||||
if pkt.Password != password {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if pkt.Destination != 9 && state > 2 {
|
||||
allowed := false
|
||||
|
||||
// Friends list verification
|
||||
if state == 3 || state == 5 {
|
||||
var friendsList string
|
||||
s.server.db.QueryRow(`SELECT friends FROM characters WHERE id=$1`, pkt.CharID).Scan(&friendsList)
|
||||
cids := stringsupport.CSVElems(friendsList)
|
||||
for _, cid := range cids {
|
||||
if uint32(cid) == s.charID {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Guild verification
|
||||
if state > 3 {
|
||||
ownGuild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
isApplicant, _ := ownGuild.HasApplicationForCharID(s, s.charID)
|
||||
if err == nil && ownGuild != nil {
|
||||
othersGuild, err := GetGuildInfoByCharacterId(s, pkt.CharID)
|
||||
if err == nil && othersGuild != nil {
|
||||
if othersGuild.ID == ownGuild.ID && !isApplicant {
|
||||
allowed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !allowed {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var houseTier, houseData, houseFurniture, bookshelf, gallery, tore, garden []byte
|
||||
s.server.db.QueryRow(`SELECT house_tier, house_data, house_furniture, bookshelf, gallery, tore, garden FROM user_binary WHERE id=$1
|
||||
`, pkt.CharID).Scan(&houseTier, &houseData, &houseFurniture, &bookshelf, &gallery, &tore, &garden)
|
||||
if houseFurniture == nil {
|
||||
houseFurniture = make([]byte, 20)
|
||||
}
|
||||
|
||||
switch pkt.Destination {
|
||||
case 3: // Others house
|
||||
bf.WriteBytes(houseTier)
|
||||
bf.WriteBytes(houseData)
|
||||
bf.WriteBytes(make([]byte, 19)) // Padding?
|
||||
bf.WriteBytes(houseFurniture)
|
||||
case 4: // Bookshelf
|
||||
bf.WriteBytes(bookshelf)
|
||||
case 5: // Gallery
|
||||
bf.WriteBytes(gallery)
|
||||
case 8: // Tore
|
||||
bf.WriteBytes(tore)
|
||||
case 9: // Own house
|
||||
bf.WriteBytes(houseFurniture)
|
||||
case 10: // Garden
|
||||
bf.WriteBytes(garden)
|
||||
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
|
||||
s.server.db.QueryRow(`SELECT mission FROM user_binary WHERE id=$1`, s.charID).Scan(&data)
|
||||
if len(data) > 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 9))
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfUpdateMyhouseInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfUpdateMyhouseInfo)
|
||||
s.server.db.Exec("UPDATE user_binary SET mission=$1 WHERE id=$2", pkt.Unk0, s.charID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
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.Error("Failed to load decomyset", zap.Error(err))
|
||||
}
|
||||
if len(data) == 0 {
|
||||
if s.server.erupeConfig.RealClientMode < _config.G10 {
|
||||
data = []byte{0x00, 0x00}
|
||||
}
|
||||
data = []byte{0x01, 0x00}
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
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.Error("Failed to load decomyset", 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
|
||||
}
|
||||
dumpSaveData(s, loadData, "decomyset")
|
||||
_, err := s.server.db.Exec("UPDATE characters SET decomyset=$1 WHERE id=$2", loadData, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to save decomyset", zap.Error(err))
|
||||
}
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
type Title struct {
|
||||
ID uint16 `db:"id"`
|
||||
Acquired time.Time `db:"unlocked_at"`
|
||||
Updated time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateTitle(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateTitle)
|
||||
var count uint16
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(0) // Unk
|
||||
rows, err := s.server.db.Queryx("SELECT id, unlocked_at, updated_at FROM titles WHERE char_id=$1", s.charID)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
return
|
||||
}
|
||||
for rows.Next() {
|
||||
title := &Title{}
|
||||
err = rows.StructScan(&title)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
count++
|
||||
bf.WriteUint16(title.ID)
|
||||
bf.WriteUint16(0) // Unk
|
||||
bf.WriteUint32(uint32(title.Acquired.Unix()))
|
||||
bf.WriteUint32(uint32(title.Updated.Unix()))
|
||||
}
|
||||
bf.Seek(0, io.SeekStart)
|
||||
bf.WriteUint16(count)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireTitle(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAcquireTitle)
|
||||
var exists int
|
||||
err := s.server.db.QueryRow("SELECT count(*) FROM titles WHERE id=$1 AND char_id=$2", pkt.TitleID, s.charID).Scan(&exists)
|
||||
if err != nil || exists == 0 {
|
||||
s.server.db.Exec("INSERT INTO titles VALUES ($1, $2, now(), now())", pkt.TitleID, s.charID)
|
||||
} else {
|
||||
s.server.db.Exec("UPDATE titles SET updated_at=now()")
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfResetTitle(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfOperateWarehouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfOperateWarehouse)
|
||||
var t int
|
||||
err := s.server.db.QueryRow("SELECT character_id FROM warehouse WHERE character_id=$1", s.charID).Scan(&t)
|
||||
if err != nil {
|
||||
s.server.db.Exec("INSERT INTO warehouse (character_id) VALUES ($1)", s.charID)
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(pkt.Operation)
|
||||
switch pkt.Operation {
|
||||
case 0:
|
||||
var count uint8
|
||||
itemNames := make([]string, 10)
|
||||
equipNames := make([]string, 10)
|
||||
s.server.db.QueryRow(fmt.Sprintf("%s WHERE character_id=$1", warehouseNamesQuery), s.charID).Scan(&itemNames[0],
|
||||
&itemNames[1], &itemNames[2], &itemNames[3], &itemNames[4], &itemNames[5], &itemNames[6], &itemNames[7], &itemNames[8], &itemNames[9], &equipNames[0],
|
||||
&equipNames[1], &equipNames[2], &equipNames[3], &equipNames[4], &equipNames[5], &equipNames[6], &equipNames[7], &equipNames[8], &equipNames[9])
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint16(10000) // Usages
|
||||
temp := byteframe.NewByteFrame()
|
||||
for i, name := range itemNames {
|
||||
if len(name) > 0 {
|
||||
count++
|
||||
temp.WriteUint8(0)
|
||||
temp.WriteUint8(uint8(i))
|
||||
ps.Uint8(temp, name, true)
|
||||
}
|
||||
}
|
||||
for i, name := range equipNames {
|
||||
if len(name) > 0 {
|
||||
count++
|
||||
temp.WriteUint8(1)
|
||||
temp.WriteUint8(uint8(i))
|
||||
ps.Uint8(temp, name, true)
|
||||
}
|
||||
}
|
||||
bf.WriteUint8(count)
|
||||
bf.WriteBytes(temp.Data())
|
||||
case 1:
|
||||
bf.WriteUint8(0)
|
||||
case 2:
|
||||
s.server.db.Exec(fmt.Sprintf("UPDATE warehouse SET %s%dname=$1 WHERE character_id=$2", pkt.BoxType, pkt.BoxIndex), pkt.Name, s.charID)
|
||||
case 3:
|
||||
bf.WriteUint32(0) // Usage renewal time, >1 = disabled
|
||||
bf.WriteUint16(10000) // Usages
|
||||
case 4:
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint16(10000) // Usages
|
||||
bf.WriteUint8(0)
|
||||
}
|
||||
// Opcodes
|
||||
// 0 = Get box names
|
||||
// 1 = Commit usage
|
||||
// 2 = Rename
|
||||
// 3 = Get usage limit
|
||||
// 4 = Get gift box names (doesn't do anything?)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func addWarehouseGift(s *Session, boxType string, giftStack mhfpacket.WarehouseStack) {
|
||||
giftBox := getWarehouseBox(s, boxType, 10)
|
||||
if boxType == "item" {
|
||||
exists := false
|
||||
for i, stack := range giftBox {
|
||||
if stack.ItemID == giftStack.ItemID {
|
||||
exists = true
|
||||
giftBox[i].Quantity += giftStack.Quantity
|
||||
break
|
||||
}
|
||||
}
|
||||
if exists == false {
|
||||
giftBox = append(giftBox, giftStack)
|
||||
}
|
||||
} else {
|
||||
giftBox = append(giftBox, giftStack)
|
||||
}
|
||||
s.server.db.Exec(fmt.Sprintf("UPDATE warehouse SET %s10=$1 WHERE character_id=$2", boxType), boxToBytes(giftBox, boxType), s.charID)
|
||||
}
|
||||
|
||||
func getWarehouseBox(s *Session, boxType string, boxIndex uint8) []mhfpacket.WarehouseStack {
|
||||
var data []byte
|
||||
s.server.db.QueryRow(fmt.Sprintf("SELECT %s%d FROM warehouse WHERE character_id=$1", boxType, boxIndex), s.charID).Scan(&data)
|
||||
if len(data) > 0 {
|
||||
box := byteframe.NewByteFrameFromBytes(data)
|
||||
numStacks := box.ReadUint16()
|
||||
stacks := make([]mhfpacket.WarehouseStack, numStacks)
|
||||
for i := 0; i < int(numStacks); i++ {
|
||||
if boxType == "item" {
|
||||
stacks[i].ID = box.ReadUint32()
|
||||
stacks[i].Index = box.ReadUint16()
|
||||
stacks[i].ItemID = box.ReadUint16()
|
||||
stacks[i].Quantity = box.ReadUint16()
|
||||
box.ReadUint16()
|
||||
} else {
|
||||
stacks[i].ID = box.ReadUint32()
|
||||
stacks[i].Index = box.ReadUint16()
|
||||
stacks[i].EquipType = box.ReadUint16()
|
||||
stacks[i].ItemID = box.ReadUint16()
|
||||
stacks[i].Data = box.ReadBytes(56)
|
||||
}
|
||||
}
|
||||
return stacks
|
||||
} else {
|
||||
return make([]mhfpacket.WarehouseStack, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func boxToBytes(stacks []mhfpacket.WarehouseStack, boxType string) []byte {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(uint16(len(stacks)))
|
||||
for i, stack := range stacks {
|
||||
if boxType == "item" {
|
||||
bf.WriteUint32(stack.ID)
|
||||
bf.WriteUint16(uint16(i + 1))
|
||||
bf.WriteUint16(stack.ItemID)
|
||||
bf.WriteUint16(stack.Quantity)
|
||||
bf.WriteUint16(0)
|
||||
} else {
|
||||
bf.WriteUint32(stack.ID)
|
||||
bf.WriteUint16(uint16(i + 1))
|
||||
bf.WriteUint16(stack.EquipType)
|
||||
bf.WriteUint16(stack.ItemID)
|
||||
bf.WriteBytes(stack.Data)
|
||||
}
|
||||
}
|
||||
bf.WriteUint16(0)
|
||||
return bf.Data()
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateWarehouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateWarehouse)
|
||||
box := getWarehouseBox(s, pkt.BoxType, pkt.BoxIndex)
|
||||
if len(box) > 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, boxToBytes(box, pkt.BoxType))
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfUpdateWarehouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfUpdateWarehouse)
|
||||
box := getWarehouseBox(s, pkt.BoxType, pkt.BoxIndex)
|
||||
// Update existing stacks
|
||||
var newStacks []mhfpacket.WarehouseStack
|
||||
for _, update := range pkt.Updates {
|
||||
exists := false
|
||||
if pkt.BoxType == "item" {
|
||||
for i, stack := range box {
|
||||
if stack.Index == update.Index {
|
||||
exists = true
|
||||
box[i].Quantity = update.Quantity
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i, stack := range box {
|
||||
if stack.Index == update.Index {
|
||||
exists = true
|
||||
box[i].ItemID = update.ItemID
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if exists == false {
|
||||
newStacks = append(newStacks, update)
|
||||
}
|
||||
}
|
||||
// Append new stacks
|
||||
for _, stack := range newStacks {
|
||||
box = append(box, stack)
|
||||
}
|
||||
// Slice empty stacks
|
||||
var cleanedBox []mhfpacket.WarehouseStack
|
||||
for _, stack := range box {
|
||||
if pkt.BoxType == "item" {
|
||||
if stack.Quantity > 0 {
|
||||
cleanedBox = append(cleanedBox, stack)
|
||||
}
|
||||
} else {
|
||||
if stack.ItemID != 0 {
|
||||
cleanedBox = append(cleanedBox, stack)
|
||||
}
|
||||
}
|
||||
}
|
||||
s.server.db.Exec(fmt.Sprintf("UPDATE warehouse SET %s%d=$1 WHERE character_id=$2", pkt.BoxType, pkt.BoxIndex), boxToBytes(cleanedBox, pkt.BoxType), s.charID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
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.Error("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.Error("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.Error("Failed to update platemyset savedata in db", zap.Error(err))
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(uint32(points))
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
413
server/channelserver/handlers_mail.go
Normal file
413
server/channelserver/handlers_mail.go
Normal file
@@ -0,0 +1,413 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"erupe-ce/common/stringsupport"
|
||||
"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"`
|
||||
IsSystemMessage bool `db:"is_sys_message"`
|
||||
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, is_sys_message)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
`
|
||||
|
||||
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, m.IsSystemMessage)
|
||||
} else {
|
||||
_, err = transaction.Exec(query, m.SenderID, m.RecipientID, m.Subject, m.Body, m.AttachedItemID, m.AttachedItemAmount, m.IsGuildInvite, m.IsSystemMessage)
|
||||
}
|
||||
|
||||
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),
|
||||
zap.Bool("isSystemMessage", m.IsSystemMessage),
|
||||
)
|
||||
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.is_sys_message,
|
||||
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.is_sys_message,
|
||||
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) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
notification := &binpacket.MsgBinMailNotify{
|
||||
SenderName: getCharacterName(s, m.SenderID),
|
||||
}
|
||||
|
||||
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 {
|
||||
row := s.server.db.QueryRow("SELECT name FROM characters WHERE id = $1", charID)
|
||||
|
||||
charName := ""
|
||||
|
||||
err := row.Scan(&charName)
|
||||
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return charName
|
||||
}
|
||||
|
||||
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 := stringsupport.UTF8ToSJIS(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
|
||||
|
||||
msg.WriteUint32(m.SenderID)
|
||||
msg.WriteUint32(uint32(m.CreatedAt.Unix()))
|
||||
|
||||
msg.WriteUint8(accIndex)
|
||||
msg.WriteUint8(uint8(i))
|
||||
|
||||
flags := uint8(0x00)
|
||||
|
||||
if m.Read {
|
||||
flags |= 0x01
|
||||
}
|
||||
|
||||
if m.Locked {
|
||||
flags |= 0x02
|
||||
}
|
||||
|
||||
if m.IsSystemMessage {
|
||||
flags |= 0x04
|
||||
}
|
||||
|
||||
if m.AttachedItemReceived {
|
||||
flags |= 0x08
|
||||
}
|
||||
|
||||
if m.IsGuildInvite {
|
||||
flags |= 0x10
|
||||
}
|
||||
|
||||
msg.WriteUint8(flags)
|
||||
msg.WriteBool(itemAttached)
|
||||
msg.WriteUint8(16)
|
||||
msg.WriteUint8(21)
|
||||
msg.WriteBytes(stringsupport.PaddedString(m.Subject, 16, true))
|
||||
msg.WriteBytes(stringsupport.PaddedString(m.SenderName, 21, true))
|
||||
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 {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
switch pkt.Operation {
|
||||
case mhfpacket.OPERATE_MAIL_DELETE:
|
||||
err = mail.MarkDeleted(s)
|
||||
case mhfpacket.OPERATE_MAIL_LOCK:
|
||||
err = mail.MarkLocked(s, true)
|
||||
case mhfpacket.OPERATE_MAIL_UNLOCK:
|
||||
err = mail.MarkLocked(s, false)
|
||||
case mhfpacket.OPERATE_MAIL_ACQUIRE_ITEM:
|
||||
err = mail.MarkAcquired(s)
|
||||
}
|
||||
|
||||
if err != 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.Error("Failed to get guild info for mail")
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
gm, err := GetGuildMembers(s, g.ID, false)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get guild members for mail")
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
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.Error("Failed to send mail")
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
}
|
||||
} 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.Error("Failed to send mail")
|
||||
}
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
437
server/channelserver/handlers_mercenary.go
Normal file
437
server/channelserver/handlers_mercenary.go
Normal file
@@ -0,0 +1,437 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/stringsupport"
|
||||
_config "erupe-ce/config"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"erupe-ce/server/channelserver/compression/deltacomp"
|
||||
"erupe-ce/server/channelserver/compression/nullcomp"
|
||||
"go.uber.org/zap"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
func handleMsgMhfLoadPartner(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadPartner)
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT partner FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if len(data) == 0 {
|
||||
s.logger.Error("Failed to load partner", zap.Error(err))
|
||||
data = make([]byte, 9)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
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.Error("Failed to save partner", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfLoadLegendDispatch(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadLegendDispatch)
|
||||
bf := byteframe.NewByteFrame()
|
||||
legendDispatch := []struct {
|
||||
Unk uint32
|
||||
Timestamp uint32
|
||||
}{
|
||||
{0, uint32(TimeMidnight().Add(-12 * time.Hour).Unix())},
|
||||
{0, uint32(TimeMidnight().Add(12 * time.Hour).Unix())},
|
||||
{0, uint32(TimeMidnight().Add(36 * time.Hour).Unix())},
|
||||
}
|
||||
bf.WriteUint8(uint8(len(legendDispatch)))
|
||||
for _, dispatch := range legendDispatch {
|
||||
bf.WriteUint32(dispatch.Unk)
|
||||
bf.WriteUint32(dispatch.Timestamp)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfLoadHunterNavi(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadHunterNavi)
|
||||
naviLength := 552
|
||||
if s.server.erupeConfig.RealClientMode <= _config.G7 {
|
||||
naviLength = 280
|
||||
}
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT hunternavi FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if len(data) == 0 {
|
||||
s.logger.Error("Failed to load hunternavi", zap.Error(err))
|
||||
data = make([]byte, naviLength)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfSaveHunterNavi(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSaveHunterNavi)
|
||||
if pkt.IsDataDiff {
|
||||
naviLength := 552
|
||||
if s.server.erupeConfig.RealClientMode <= _config.G7 {
|
||||
naviLength = 280
|
||||
}
|
||||
var data []byte
|
||||
// Load existing save
|
||||
err := s.server.db.QueryRow("SELECT hunternavi FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to load hunternavi", 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, naviLength)
|
||||
}
|
||||
|
||||
// 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.Error("Failed to save hunternavi", zap.Error(err))
|
||||
}
|
||||
s.logger.Info("Wrote recompressed hunternavi back to DB")
|
||||
} else {
|
||||
dumpSaveData(s, pkt.RawDataPayload, "hunternavi")
|
||||
// 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.Error("Failed to save hunternavi", zap.Error(err))
|
||||
}
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfMercenaryHuntdata(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfMercenaryHuntdata)
|
||||
if pkt.Unk0 == 1 {
|
||||
// Format:
|
||||
// uint8 Hunts
|
||||
// struct Hunt
|
||||
// uint32 HuntID
|
||||
// uint32 MonID
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0))
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateMercenaryLog(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateMercenaryLog)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(0)
|
||||
// Format:
|
||||
// struct Log
|
||||
// uint32 Timestamp
|
||||
// []byte Name (len 18)
|
||||
// uint8 Unk
|
||||
// uint8 Unk
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfCreateMercenary(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfCreateMercenary)
|
||||
bf := byteframe.NewByteFrame()
|
||||
var nextID uint32
|
||||
_ = s.server.db.QueryRow("SELECT nextval('rasta_id_seq')").Scan(&nextID)
|
||||
s.server.db.Exec("UPDATE characters SET rasta_id=$1 WHERE id=$2", nextID, s.charID)
|
||||
bf.WriteUint32(nextID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfSaveMercenary(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSaveMercenary)
|
||||
dumpSaveData(s, pkt.MercData, "mercenary")
|
||||
if len(pkt.MercData) > 0 {
|
||||
temp := byteframe.NewByteFrameFromBytes(pkt.MercData)
|
||||
s.server.db.Exec("UPDATE characters SET savemercenary=$1, rasta_id=$2 WHERE id=$3", pkt.MercData, temp.ReadUint32(), s.charID)
|
||||
}
|
||||
s.server.db.Exec("UPDATE characters SET gcp=$1, pact_id=$2 WHERE id=$3", pkt.GCP, pkt.PactMercID, s.charID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReadMercenaryW)
|
||||
if pkt.Op > 0 {
|
||||
bf := byteframe.NewByteFrame()
|
||||
var pactID uint32
|
||||
var name string
|
||||
var cid uint32
|
||||
|
||||
s.server.db.QueryRow("SELECT pact_id FROM characters WHERE id=$1", s.charID).Scan(&pactID)
|
||||
if pactID > 0 {
|
||||
s.server.db.QueryRow("SELECT name, id FROM characters WHERE rasta_id = $1", pactID).Scan(&name, &cid)
|
||||
bf.WriteUint8(1) // numLends
|
||||
bf.WriteUint32(pactID)
|
||||
bf.WriteUint32(cid)
|
||||
bf.WriteBool(false) // ?
|
||||
bf.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -8).Unix()))
|
||||
bf.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -1).Unix()))
|
||||
bf.WriteBytes(stringsupport.PaddedString(name, 18, true))
|
||||
} else {
|
||||
bf.WriteUint8(0)
|
||||
}
|
||||
|
||||
if pkt.Op < 2 {
|
||||
var loans uint8
|
||||
temp := byteframe.NewByteFrame()
|
||||
rows, _ := s.server.db.Query("SELECT name, id, pact_id FROM characters WHERE pact_id=(SELECT rasta_id FROM characters WHERE id=$1)", s.charID)
|
||||
for rows.Next() {
|
||||
loans++
|
||||
rows.Scan(&name, &cid, &pactID)
|
||||
temp.WriteUint32(pactID)
|
||||
temp.WriteUint32(cid)
|
||||
temp.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -8).Unix()))
|
||||
temp.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -1).Unix()))
|
||||
temp.WriteBytes(stringsupport.PaddedString(name, 18, true))
|
||||
}
|
||||
bf.WriteUint8(loans)
|
||||
bf.WriteBytes(temp.Data())
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
return
|
||||
}
|
||||
var data []byte
|
||||
var gcp uint32
|
||||
s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id=$1", s.charID).Scan(&data)
|
||||
s.server.db.QueryRow("SELECT COALESCE(gcp, 0) FROM characters WHERE id=$1", s.charID).Scan(&gcp)
|
||||
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint16(0)
|
||||
if len(data) == 0 {
|
||||
resp.WriteBool(false)
|
||||
} else {
|
||||
resp.WriteBool(true)
|
||||
resp.WriteBytes(data)
|
||||
}
|
||||
resp.WriteUint32(gcp)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfReadMercenaryM(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReadMercenaryM)
|
||||
var data []byte
|
||||
s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id = $1", pkt.CharID).Scan(&data)
|
||||
resp := byteframe.NewByteFrame()
|
||||
if len(data) == 0 {
|
||||
resp.WriteBool(false)
|
||||
} else {
|
||||
resp.WriteBytes(data)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfContractMercenary(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfContractMercenary)
|
||||
switch pkt.Op {
|
||||
case 0: // Form loan
|
||||
s.server.db.Exec("UPDATE characters SET pact_id=$1 WHERE id=$2", pkt.PactMercID, pkt.CID)
|
||||
case 1: // Cancel lend
|
||||
s.server.db.Exec("UPDATE characters SET pact_id=0 WHERE id=$1", s.charID)
|
||||
case 2: // Cancel loan
|
||||
s.server.db.Exec("UPDATE characters SET pact_id=0 WHERE id=$1", pkt.CID)
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
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 len(data) == 0 {
|
||||
s.logger.Error("Failed to load otomoairou", zap.Error(err))
|
||||
data = make([]byte, 10)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfSaveOtomoAirou(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSaveOtomoAirou)
|
||||
dumpSaveData(s, pkt.RawDataPayload, "otomoairou")
|
||||
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)
|
||||
save := byteframe.NewByteFrame()
|
||||
var catsExist uint8
|
||||
save.WriteUint8(0)
|
||||
|
||||
cats := bf.ReadUint8()
|
||||
for i := 0; i < int(cats); i++ {
|
||||
dataLen := bf.ReadUint32()
|
||||
catID := bf.ReadUint32()
|
||||
if catID == 0 {
|
||||
_ = s.server.db.QueryRow("SELECT nextval('airou_id_seq')").Scan(&catID)
|
||||
}
|
||||
exists := bf.ReadBool()
|
||||
data := bf.ReadBytes(uint(dataLen) - 5)
|
||||
if exists {
|
||||
catsExist++
|
||||
save.WriteUint32(dataLen)
|
||||
save.WriteUint32(catID)
|
||||
save.WriteBool(exists)
|
||||
save.WriteBytes(data)
|
||||
}
|
||||
}
|
||||
save.WriteBytes(bf.DataFromCurrent())
|
||||
save.Seek(0, 0)
|
||||
save.WriteUint8(catsExist)
|
||||
comp, err := nullcomp.Compress(save.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, _ := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "airoulist.bin"))
|
||||
resp.WriteBytes(data)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
return
|
||||
}
|
||||
airouList := getGuildAirouList(s)
|
||||
resp.WriteUint16(uint16(len(airouList)))
|
||||
resp.WriteUint16(uint16(len(airouList)))
|
||||
for _, cat := range airouList {
|
||||
resp.WriteUint32(cat.CatID)
|
||||
resp.WriteBytes(cat.CatName)
|
||||
resp.WriteUint32(cat.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, TimeAdjusted().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) {}
|
||||
96
server/channelserver/handlers_object.go
Normal file
96
server/channelserver/handlers_object.go
Normal file
@@ -0,0 +1,96 @@
|
||||
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.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, newObj.id))
|
||||
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) {
|
||||
_ = p.(*mhfpacket.MsgSysSetObjectBinary)
|
||||
/* This causes issues with PS3 as this actually sends with endiness!
|
||||
for _, session := range s.server.sessions {
|
||||
if session.charID == s.charID {
|
||||
s.server.userBinaryPartsLock.Lock()
|
||||
s.server.userBinaryParts[userBinaryPartID{charID: s.charID, index: 3}] = pkt.RawDataPayload
|
||||
s.server.userBinaryPartsLock.Unlock()
|
||||
msg := &mhfpacket.MsgSysNotifyUserBinary{
|
||||
CharID: s.charID,
|
||||
BinaryType: 3,
|
||||
}
|
||||
s.server.BroadcastMHF(msg, s)
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
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) {}
|
||||
164
server/channelserver/handlers_plate.go
Normal file
164
server/channelserver/handlers_plate.go
Normal file
@@ -0,0 +1,164 @@
|
||||
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.Error("Failed to load platedata", zap.Error(err))
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSavePlateData)
|
||||
|
||||
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.Error("Failed to load platedata", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
return
|
||||
}
|
||||
|
||||
if len(data) > 0 {
|
||||
// Decompress
|
||||
s.logger.Info("Decompressing...")
|
||||
data, err = nullcomp.Decompress(data)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to decompress platedata", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
return
|
||||
}
|
||||
} 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.Error("Failed to diff and compress platedata", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
return
|
||||
}
|
||||
|
||||
_, err = s.server.db.Exec("UPDATE characters SET platedata=$1 WHERE id=$2", saveOutput, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to save platedata", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Info("Wrote recompressed platedata back to DB")
|
||||
} else {
|
||||
dumpSaveData(s, pkt.RawDataPayload, "platedata")
|
||||
// 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.Error("Failed to save platedata", 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.Error("Failed to load platebox", zap.Error(err))
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSavePlateBox)
|
||||
|
||||
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.Error("Failed to load platebox", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
return
|
||||
}
|
||||
|
||||
// Decompress
|
||||
if len(data) > 0 {
|
||||
// Decompress
|
||||
s.logger.Info("Decompressing...")
|
||||
data, err = nullcomp.Decompress(data)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to decompress platebox", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
return
|
||||
}
|
||||
} 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.Error("Failed to diff and compress platebox", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
return
|
||||
}
|
||||
|
||||
_, err = s.server.db.Exec("UPDATE characters SET platebox=$1 WHERE id=$2", saveOutput, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to save platebox", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Info("Wrote recompressed platebox back to DB")
|
||||
} else {
|
||||
dumpSaveData(s, pkt.RawDataPayload, "platebox")
|
||||
// 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.Error("Failed to save platebox", 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 len(data) == 0 {
|
||||
s.logger.Error("Failed to load platemyset", zap.Error(err))
|
||||
data = make([]byte, 0x780)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfSavePlateMyset(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSavePlateMyset)
|
||||
// looks to always return the full thing, simply update database, no extra processing
|
||||
dumpSaveData(s, pkt.RawDataPayload, "platemyset")
|
||||
_, err := s.server.db.Exec("UPDATE characters SET platemyset=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to save platemyset", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
729
server/channelserver/handlers_quest.go
Normal file
729
server/channelserver/handlers_quest.go
Normal file
@@ -0,0 +1,729 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/decryption"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
_config "erupe-ce/config"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysGetFile)
|
||||
|
||||
if pkt.IsScenario {
|
||||
if s.server.erupeConfig.DevModeOptions.QuestDebugTools && s.server.erupeConfig.DevMode {
|
||||
s.logger.Debug(
|
||||
"Scenario",
|
||||
zap.Uint8("CategoryID", pkt.ScenarioIdentifer.CategoryID),
|
||||
zap.Uint32("MainID", pkt.ScenarioIdentifer.MainID),
|
||||
zap.Uint8("ChapterID", pkt.ScenarioIdentifer.ChapterID),
|
||||
zap.Uint8("Flags", pkt.ScenarioIdentifer.Flags),
|
||||
)
|
||||
}
|
||||
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 := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("scenarios/%s.bin", filename)))
|
||||
if err != nil {
|
||||
s.logger.Error(fmt.Sprintf("Failed to open file: %s/scenarios/%s.bin", s.server.erupeConfig.BinPath, filename))
|
||||
// This will crash the game.
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
return
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
if s.server.erupeConfig.DevModeOptions.QuestDebugTools && s.server.erupeConfig.DevMode {
|
||||
s.logger.Debug(
|
||||
"Quest",
|
||||
zap.String("Filename", pkt.Filename),
|
||||
)
|
||||
}
|
||||
|
||||
if s.server.erupeConfig.GameplayOptions.SeasonOverride {
|
||||
pkt.Filename = seasonConversion(s, pkt.Filename)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", pkt.Filename)))
|
||||
if err != nil {
|
||||
s.logger.Error(fmt.Sprintf("Failed to open file: %s/quests/%s.bin", s.server.erupeConfig.BinPath, pkt.Filename))
|
||||
// This will crash the game.
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
return
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
}
|
||||
|
||||
func questSuffix(s *Session) string {
|
||||
// Determine the letter to append for day / night
|
||||
var timeSet string
|
||||
if TimeGameAbsolute() > 2880 {
|
||||
timeSet = "d"
|
||||
} else {
|
||||
timeSet = "n"
|
||||
}
|
||||
return fmt.Sprintf("%s%d", timeSet, s.server.Season())
|
||||
}
|
||||
|
||||
func seasonConversion(s *Session, questFile string) string {
|
||||
filename := fmt.Sprintf("%s%s", questFile[:5], questSuffix(s))
|
||||
|
||||
// Return original file if file doesn't exist
|
||||
if _, err := os.Stat(filename); err == nil {
|
||||
return filename
|
||||
} else {
|
||||
return questFile
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
dumpSaveData(s, pkt.Data, "favquest")
|
||||
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 loadQuestFile(s *Session, questId int) []byte {
|
||||
file, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%05dd0.bin", questId)))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
decrypted := decryption.UnpackSimple(file)
|
||||
fileBytes := byteframe.NewByteFrameFromBytes(decrypted)
|
||||
fileBytes.SetLE()
|
||||
fileBytes.Seek(int64(fileBytes.ReadUint32()), 0)
|
||||
|
||||
// The 320 bytes directly following the data pointer must go directly into the event's body, after the header and before the string pointers.
|
||||
questBody := byteframe.NewByteFrameFromBytes(fileBytes.ReadBytes(320))
|
||||
questBody.SetLE()
|
||||
// Find the master quest string pointer
|
||||
questBody.Seek(40, 0)
|
||||
fileBytes.Seek(int64(questBody.ReadUint32()), 0)
|
||||
questBody.Seek(40, 0)
|
||||
// Overwrite it
|
||||
questBody.WriteUint32(320)
|
||||
questBody.Seek(0, 2)
|
||||
|
||||
// Rewrite the quest strings and their pointers
|
||||
var tempString []byte
|
||||
newStrings := byteframe.NewByteFrame()
|
||||
tempPointer := 352
|
||||
for i := 0; i < 8; i++ {
|
||||
questBody.WriteUint32(uint32(tempPointer))
|
||||
temp := int64(fileBytes.Index())
|
||||
fileBytes.Seek(int64(fileBytes.ReadUint32()), 0)
|
||||
tempString = fileBytes.ReadNullTerminatedBytes()
|
||||
fileBytes.Seek(temp+4, 0)
|
||||
tempPointer += len(tempString) + 1
|
||||
newStrings.WriteNullTerminatedBytes(tempString)
|
||||
}
|
||||
questBody.WriteBytes(newStrings.Data())
|
||||
|
||||
return questBody.Data()
|
||||
}
|
||||
|
||||
func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) {
|
||||
var id, mark uint32
|
||||
var questId int
|
||||
var maxPlayers, questType uint8
|
||||
rows.Scan(&id, &maxPlayers, &questType, &questId, &mark)
|
||||
|
||||
data := loadQuestFile(s, questId)
|
||||
if data == nil {
|
||||
return nil, fmt.Errorf("failed to load quest file")
|
||||
}
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(id)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint8(0) // Indexer
|
||||
switch questType {
|
||||
case 16:
|
||||
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.RegularRavienteMaxPlayers)
|
||||
case 22:
|
||||
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.ViolentRavienteMaxPlayers)
|
||||
case 40:
|
||||
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.BerserkRavienteMaxPlayers)
|
||||
case 50:
|
||||
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.ExtremeRavienteMaxPlayers)
|
||||
case 51:
|
||||
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.SmallBerserkRavienteMaxPlayers)
|
||||
default:
|
||||
bf.WriteUint8(maxPlayers)
|
||||
}
|
||||
bf.WriteUint8(questType)
|
||||
if questType == 9 {
|
||||
bf.WriteBool(false)
|
||||
} else {
|
||||
bf.WriteBool(true)
|
||||
}
|
||||
bf.WriteUint16(0)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G1 {
|
||||
bf.WriteUint32(mark)
|
||||
}
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(uint16(len(data)))
|
||||
bf.WriteBytes(data)
|
||||
ps.Uint8(bf, "", true) // What is this string for?
|
||||
|
||||
return bf.Data(), nil
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateQuest)
|
||||
var totalCount, returnedCount uint16
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(0)
|
||||
|
||||
rows, _ := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark FROM event_quests ORDER BY quest_id")
|
||||
for rows.Next() {
|
||||
data, err := makeEventQuest(s, rows)
|
||||
if err != nil {
|
||||
continue
|
||||
} else {
|
||||
if len(data) > 896 || len(data) < 352 {
|
||||
continue
|
||||
} else {
|
||||
totalCount++
|
||||
if _config.ErupeConfig.RealClientMode == _config.F5 {
|
||||
if totalCount > pkt.Offset && len(bf.Data()) < 21550 {
|
||||
returnedCount++
|
||||
bf.WriteBytes(data)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if totalCount > pkt.Offset && len(bf.Data()) < 60000 {
|
||||
returnedCount++
|
||||
bf.WriteBytes(data)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type tuneValue struct {
|
||||
ID uint16
|
||||
Value uint16
|
||||
}
|
||||
|
||||
tuneValues := []tuneValue{
|
||||
{ID: 20, Value: 1},
|
||||
{ID: 26, Value: 1},
|
||||
{ID: 27, Value: 1},
|
||||
{ID: 33, Value: 1},
|
||||
{ID: 40, Value: 1},
|
||||
{ID: 49, Value: 1},
|
||||
{ID: 53, Value: 1},
|
||||
{ID: 59, Value: 1},
|
||||
{ID: 67, Value: 1},
|
||||
{ID: 80, Value: 1},
|
||||
{ID: 94, Value: 1},
|
||||
{ID: 1010, Value: 300},
|
||||
{ID: 1011, Value: 300},
|
||||
{ID: 1012, Value: 300},
|
||||
{ID: 1013, Value: 300},
|
||||
{ID: 1014, Value: 200},
|
||||
{ID: 1015, Value: 200},
|
||||
{ID: 1021, Value: 400},
|
||||
{ID: 1023, Value: 8},
|
||||
{ID: 1024, Value: 150},
|
||||
{ID: 1025, Value: 1},
|
||||
{ID: 1026, Value: 999}, // get_grank_cap
|
||||
{ID: 1027, Value: 100},
|
||||
{ID: 1028, Value: 100},
|
||||
{ID: 1030, Value: 8},
|
||||
{ID: 1031, Value: 100},
|
||||
{ID: 1032, Value: 0}, // isValid_partner
|
||||
{ID: 1044, Value: 200}, // get_rate_tload_time_out
|
||||
{ID: 1045, Value: 0}, // get_rate_tower_treasure_preset
|
||||
{ID: 1046, Value: 99},
|
||||
{ID: 1048, Value: 0}, // get_rate_tower_log_disable
|
||||
{ID: 1049, Value: 10}, // get_rate_tower_gem_max
|
||||
{ID: 1050, Value: 1}, // get_rate_tower_gem_set
|
||||
{ID: 1051, Value: 200},
|
||||
{ID: 1052, Value: 200},
|
||||
{ID: 1063, Value: 50000},
|
||||
{ID: 1064, Value: 50000},
|
||||
{ID: 1065, Value: 25000},
|
||||
{ID: 1066, Value: 25000},
|
||||
{ID: 1067, Value: 90}, // get_lobby_member_upper_for_making_room Lv1?
|
||||
{ID: 1068, Value: 80}, // get_lobby_member_upper_for_making_room Lv2?
|
||||
{ID: 1069, Value: 70}, // get_lobby_member_upper_for_making_room Lv3?
|
||||
{ID: 1072, Value: 300}, // get_rate_premium_ravi_tama
|
||||
{ID: 1073, Value: 300}, // get_rate_premium_ravi_ax_tama
|
||||
{ID: 1074, Value: 300}, // get_rate_premium_ravi_g_tama
|
||||
{ID: 1078, Value: 0},
|
||||
{ID: 1079, Value: 1},
|
||||
{ID: 1080, Value: 1},
|
||||
{ID: 1081, Value: 1},
|
||||
{ID: 1082, Value: 4},
|
||||
{ID: 1083, Value: 2},
|
||||
{ID: 1084, Value: 10},
|
||||
{ID: 1085, Value: 1},
|
||||
{ID: 1086, Value: 4},
|
||||
{ID: 1087, Value: 2},
|
||||
{ID: 1088, Value: 10},
|
||||
{ID: 1089, Value: 1},
|
||||
{ID: 1090, Value: 3},
|
||||
{ID: 1091, Value: 2},
|
||||
{ID: 1092, Value: 10},
|
||||
{ID: 1093, Value: 2},
|
||||
{ID: 1094, Value: 5},
|
||||
{ID: 1095, Value: 2},
|
||||
{ID: 1096, Value: 10},
|
||||
{ID: 1097, Value: 2},
|
||||
{ID: 1098, Value: 5},
|
||||
{ID: 1099, Value: 2},
|
||||
{ID: 1100, Value: 10},
|
||||
{ID: 1101, Value: 2},
|
||||
{ID: 1102, Value: 5},
|
||||
{ID: 1103, Value: 2},
|
||||
{ID: 1104, Value: 10},
|
||||
{ID: 1145, Value: 200},
|
||||
{ID: 1146, Value: 0}, // isTower_invisible
|
||||
{ID: 1147, Value: 0}, // isVenom_playable
|
||||
{ID: 1149, Value: 20},
|
||||
{ID: 1152, Value: 1130},
|
||||
{ID: 1154, Value: 0}, // isDisabled_object_season
|
||||
{ID: 1158, Value: 1},
|
||||
{ID: 1160, Value: 300},
|
||||
{ID: 1162, Value: 1},
|
||||
{ID: 1163, Value: 3},
|
||||
{ID: 1164, Value: 5},
|
||||
{ID: 1165, Value: 1},
|
||||
{ID: 1166, Value: 5},
|
||||
{ID: 1167, Value: 1},
|
||||
{ID: 1168, Value: 3},
|
||||
{ID: 1169, Value: 3},
|
||||
{ID: 1170, Value: 5},
|
||||
{ID: 1171, Value: 1},
|
||||
{ID: 1172, Value: 1},
|
||||
{ID: 1173, Value: 1},
|
||||
{ID: 1174, Value: 2},
|
||||
{ID: 1175, Value: 4},
|
||||
{ID: 1176, Value: 10},
|
||||
{ID: 1177, Value: 4},
|
||||
{ID: 1178, Value: 10},
|
||||
{ID: 1179, Value: 2},
|
||||
{ID: 1180, Value: 5},
|
||||
{ID: 3000, Value: 100},
|
||||
{ID: 3001, Value: 100},
|
||||
{ID: 3002, Value: 100},
|
||||
{ID: 3003, Value: 100},
|
||||
{ID: 3004, Value: 100},
|
||||
{ID: 3005, Value: 100},
|
||||
{ID: 3006, Value: 100},
|
||||
{ID: 3007, Value: 100},
|
||||
{ID: 3008, Value: 100},
|
||||
{ID: 3009, Value: 100},
|
||||
{ID: 3010, Value: 100},
|
||||
{ID: 3011, Value: 100},
|
||||
{ID: 3012, Value: 100},
|
||||
{ID: 3013, Value: 100},
|
||||
{ID: 3014, Value: 100},
|
||||
{ID: 3015, Value: 100},
|
||||
{ID: 3016, Value: 100},
|
||||
{ID: 3017, Value: 100},
|
||||
{ID: 3018, Value: 100},
|
||||
{ID: 3019, Value: 100},
|
||||
{ID: 3020, Value: 100},
|
||||
{ID: 3021, Value: 100},
|
||||
{ID: 3022, Value: 100},
|
||||
{ID: 3023, Value: 100},
|
||||
{ID: 3024, Value: 100},
|
||||
{ID: 3025, Value: 100},
|
||||
{ID: 3286, Value: 200},
|
||||
{ID: 3287, Value: 200},
|
||||
{ID: 3288, Value: 200},
|
||||
{ID: 3289, Value: 200},
|
||||
{ID: 3290, Value: 200},
|
||||
{ID: 3291, Value: 200},
|
||||
{ID: 3292, Value: 200},
|
||||
{ID: 3293, Value: 200},
|
||||
{ID: 3294, Value: 200},
|
||||
{ID: 3295, Value: 200},
|
||||
{ID: 3296, Value: 200},
|
||||
{ID: 3297, Value: 200},
|
||||
{ID: 3298, Value: 200},
|
||||
{ID: 3299, Value: 200},
|
||||
{ID: 3300, Value: 200},
|
||||
{ID: 3301, Value: 200},
|
||||
{ID: 3302, Value: 200},
|
||||
{ID: 3303, Value: 200},
|
||||
{ID: 3304, Value: 200},
|
||||
{ID: 3305, Value: 200},
|
||||
{ID: 3306, Value: 200},
|
||||
{ID: 3307, Value: 200},
|
||||
{ID: 3308, Value: 200},
|
||||
{ID: 3309, Value: 200},
|
||||
{ID: 3310, Value: 200},
|
||||
{ID: 3311, Value: 200},
|
||||
{ID: 3312, Value: 300},
|
||||
{ID: 3313, Value: 300},
|
||||
{ID: 3314, Value: 300},
|
||||
{ID: 3315, Value: 300},
|
||||
{ID: 3316, Value: 300},
|
||||
{ID: 3317, Value: 300},
|
||||
{ID: 3318, Value: 300},
|
||||
{ID: 3319, Value: 300},
|
||||
{ID: 3320, Value: 300},
|
||||
{ID: 3321, Value: 300},
|
||||
{ID: 3322, Value: 300},
|
||||
{ID: 3323, Value: 300},
|
||||
{ID: 3324, Value: 300},
|
||||
{ID: 3325, Value: 300},
|
||||
{ID: 3326, Value: 300},
|
||||
{ID: 3327, Value: 300},
|
||||
{ID: 3328, Value: 300},
|
||||
{ID: 3329, Value: 300},
|
||||
{ID: 3330, Value: 300},
|
||||
{ID: 3331, Value: 300},
|
||||
{ID: 3332, Value: 300},
|
||||
{ID: 3333, Value: 300},
|
||||
{ID: 3334, Value: 300},
|
||||
{ID: 3335, Value: 300},
|
||||
{ID: 3336, Value: 300},
|
||||
{ID: 3337, Value: 300},
|
||||
{ID: 3338, Value: 100},
|
||||
{ID: 3339, Value: 100},
|
||||
{ID: 3340, Value: 100},
|
||||
{ID: 3341, Value: 100},
|
||||
{ID: 3342, Value: 100},
|
||||
{ID: 3343, Value: 100},
|
||||
{ID: 3344, Value: 100},
|
||||
{ID: 3345, Value: 100},
|
||||
{ID: 3346, Value: 100},
|
||||
{ID: 3347, Value: 100},
|
||||
{ID: 3348, Value: 100},
|
||||
{ID: 3349, Value: 100},
|
||||
{ID: 3350, Value: 100},
|
||||
{ID: 3351, Value: 100},
|
||||
{ID: 3352, Value: 100},
|
||||
{ID: 3353, Value: 100},
|
||||
{ID: 3354, Value: 100},
|
||||
{ID: 3355, Value: 100},
|
||||
{ID: 3356, Value: 100},
|
||||
{ID: 3357, Value: 100},
|
||||
{ID: 3358, Value: 100},
|
||||
{ID: 3359, Value: 100},
|
||||
{ID: 3360, Value: 100},
|
||||
{ID: 3361, Value: 100},
|
||||
{ID: 3362, Value: 100},
|
||||
{ID: 3363, Value: 100},
|
||||
{ID: 3364, Value: 100},
|
||||
{ID: 3365, Value: 100},
|
||||
{ID: 3366, Value: 100},
|
||||
{ID: 3367, Value: 100},
|
||||
{ID: 3368, Value: 100},
|
||||
{ID: 3369, Value: 100},
|
||||
{ID: 3370, Value: 100},
|
||||
{ID: 3371, Value: 100},
|
||||
{ID: 3372, Value: 100},
|
||||
{ID: 3373, Value: 100},
|
||||
{ID: 3374, Value: 100},
|
||||
{ID: 3375, Value: 100},
|
||||
{ID: 3376, Value: 100},
|
||||
{ID: 3377, Value: 100},
|
||||
{ID: 3378, Value: 100},
|
||||
{ID: 3379, Value: 100},
|
||||
{ID: 3380, Value: 100},
|
||||
{ID: 3381, Value: 100},
|
||||
{ID: 3382, Value: 100},
|
||||
{ID: 3383, Value: 100},
|
||||
{ID: 3384, Value: 100},
|
||||
{ID: 3385, Value: 100},
|
||||
{ID: 3386, Value: 100},
|
||||
{ID: 3387, Value: 100},
|
||||
{ID: 3388, Value: 100},
|
||||
{ID: 3389, Value: 100},
|
||||
{ID: 3390, Value: 100},
|
||||
{ID: 3391, Value: 100},
|
||||
{ID: 3392, Value: 100},
|
||||
{ID: 3393, Value: 100},
|
||||
{ID: 3394, Value: 100},
|
||||
{ID: 3395, Value: 100},
|
||||
{ID: 3396, Value: 100},
|
||||
{ID: 3397, Value: 100},
|
||||
{ID: 3398, Value: 100},
|
||||
{ID: 3399, Value: 100},
|
||||
{ID: 3400, Value: 100},
|
||||
{ID: 3401, Value: 100},
|
||||
{ID: 3402, Value: 100},
|
||||
{ID: 3416, Value: 100},
|
||||
{ID: 3417, Value: 100},
|
||||
{ID: 3418, Value: 100},
|
||||
{ID: 3419, Value: 100},
|
||||
{ID: 3420, Value: 100},
|
||||
{ID: 3421, Value: 100},
|
||||
{ID: 3422, Value: 100},
|
||||
{ID: 3423, Value: 100},
|
||||
{ID: 3424, Value: 100},
|
||||
{ID: 3425, Value: 100},
|
||||
{ID: 3426, Value: 100},
|
||||
{ID: 3427, Value: 100},
|
||||
{ID: 3428, Value: 100},
|
||||
{ID: 3442, Value: 100},
|
||||
{ID: 3443, Value: 100},
|
||||
{ID: 3444, Value: 100},
|
||||
{ID: 3445, Value: 100},
|
||||
{ID: 3446, Value: 100},
|
||||
{ID: 3447, Value: 100},
|
||||
{ID: 3448, Value: 100},
|
||||
{ID: 3449, Value: 100},
|
||||
{ID: 3450, Value: 100},
|
||||
{ID: 3451, Value: 100},
|
||||
{ID: 3452, Value: 100},
|
||||
{ID: 3453, Value: 100},
|
||||
{ID: 3454, Value: 100},
|
||||
{ID: 3468, Value: 100},
|
||||
{ID: 3469, Value: 100},
|
||||
{ID: 3470, Value: 100},
|
||||
{ID: 3471, Value: 100},
|
||||
{ID: 3472, Value: 100},
|
||||
{ID: 3473, Value: 100},
|
||||
{ID: 3474, Value: 100},
|
||||
{ID: 3475, Value: 100},
|
||||
{ID: 3476, Value: 100},
|
||||
{ID: 3477, Value: 100},
|
||||
{ID: 3478, Value: 100},
|
||||
{ID: 3479, Value: 100},
|
||||
{ID: 3480, Value: 100},
|
||||
{ID: 3494, Value: 0},
|
||||
{ID: 3495, Value: 0},
|
||||
{ID: 3496, Value: 0},
|
||||
{ID: 3497, Value: 0},
|
||||
{ID: 3498, Value: 0},
|
||||
{ID: 3499, Value: 0},
|
||||
{ID: 3500, Value: 0},
|
||||
{ID: 3501, Value: 0},
|
||||
{ID: 3502, Value: 0},
|
||||
{ID: 3503, Value: 0},
|
||||
{ID: 3504, Value: 0},
|
||||
{ID: 3505, Value: 0},
|
||||
{ID: 3506, Value: 0},
|
||||
{ID: 3520, Value: 0},
|
||||
{ID: 3521, Value: 0},
|
||||
{ID: 3522, Value: 0},
|
||||
{ID: 3523, Value: 0},
|
||||
{ID: 3524, Value: 0},
|
||||
{ID: 3525, Value: 0},
|
||||
{ID: 3526, Value: 0},
|
||||
{ID: 3527, Value: 0},
|
||||
{ID: 3528, Value: 0},
|
||||
{ID: 3529, Value: 0},
|
||||
{ID: 3530, Value: 0},
|
||||
{ID: 3531, Value: 0},
|
||||
{ID: 3532, Value: 0},
|
||||
{ID: 3546, Value: 0},
|
||||
{ID: 3547, Value: 0},
|
||||
{ID: 3548, Value: 0},
|
||||
{ID: 3549, Value: 0},
|
||||
{ID: 3550, Value: 0},
|
||||
{ID: 3551, Value: 0},
|
||||
{ID: 3552, Value: 0},
|
||||
{ID: 3553, Value: 0},
|
||||
{ID: 3554, Value: 0},
|
||||
{ID: 3555, Value: 0},
|
||||
{ID: 3556, Value: 0},
|
||||
{ID: 3557, Value: 0},
|
||||
{ID: 3558, Value: 0},
|
||||
{ID: 3572, Value: 0},
|
||||
{ID: 3573, Value: 0},
|
||||
{ID: 3574, Value: 0},
|
||||
{ID: 3575, Value: 0},
|
||||
{ID: 3576, Value: 0},
|
||||
{ID: 3577, Value: 0},
|
||||
{ID: 3578, Value: 0},
|
||||
{ID: 3579, Value: 0},
|
||||
{ID: 3580, Value: 0},
|
||||
{ID: 3581, Value: 0},
|
||||
{ID: 3582, Value: 0},
|
||||
{ID: 3583, Value: 0},
|
||||
{ID: 3584, Value: 0},
|
||||
}
|
||||
|
||||
tuneValues = append(tuneValues, tuneValue{1020, uint16(s.server.erupeConfig.GameplayOptions.GCPMultiplier * 100)})
|
||||
|
||||
tuneValues = append(tuneValues, tuneValue{1029, s.server.erupeConfig.GameplayOptions.GUrgentRate})
|
||||
|
||||
if s.server.erupeConfig.GameplayOptions.DisableHunterNavi {
|
||||
tuneValues = append(tuneValues, tuneValue{1037, 1})
|
||||
}
|
||||
|
||||
if s.server.erupeConfig.GameplayOptions.EnableKaijiEvent {
|
||||
tuneValues = append(tuneValues, tuneValue{1106, 1})
|
||||
} else {
|
||||
tuneValues = append(tuneValues, tuneValue{1106, 0})
|
||||
}
|
||||
|
||||
if s.server.erupeConfig.GameplayOptions.EnableHiganjimaEvent {
|
||||
tuneValues = append(tuneValues, tuneValue{1144, 1})
|
||||
} else {
|
||||
tuneValues = append(tuneValues, tuneValue{1144, 0})
|
||||
}
|
||||
|
||||
if s.server.erupeConfig.GameplayOptions.EnableNierEvent {
|
||||
tuneValues = append(tuneValues, tuneValue{1153, 1})
|
||||
} else {
|
||||
tuneValues = append(tuneValues, tuneValue{1153, 0})
|
||||
}
|
||||
|
||||
if s.server.erupeConfig.GameplayOptions.DisableRoad {
|
||||
tuneValues = append(tuneValues, tuneValue{1155, 1})
|
||||
} else {
|
||||
tuneValues = append(tuneValues, tuneValue{1155, 0})
|
||||
}
|
||||
|
||||
for i := uint16(0); i < 13; i++ {
|
||||
tuneValues = append(tuneValues, tuneValue{i + 3026, uint16(s.server.erupeConfig.GameplayOptions.GRPMultiplier * 100)})
|
||||
}
|
||||
|
||||
for i := uint16(0); i < 13; i++ {
|
||||
tuneValues = append(tuneValues, tuneValue{i + 3039, uint16(s.server.erupeConfig.GameplayOptions.GSRPMultiplier * 100)})
|
||||
}
|
||||
|
||||
for i := uint16(0); i < 13; i++ {
|
||||
tuneValues = append(tuneValues, tuneValue{i + 3052, uint16(s.server.erupeConfig.GameplayOptions.GZennyMultiplier * 100)})
|
||||
}
|
||||
for i := uint16(0); i < 13; i++ {
|
||||
tuneValues = append(tuneValues, tuneValue{i + 3078, uint16(s.server.erupeConfig.GameplayOptions.GZennyMultiplier * 100)})
|
||||
}
|
||||
|
||||
for i := uint16(0); i < 13; i++ {
|
||||
tuneValues = append(tuneValues, tuneValue{i + 3104, uint16(s.server.erupeConfig.GameplayOptions.MaterialMultiplier * 100)})
|
||||
}
|
||||
for i := uint16(0); i < 13; i++ {
|
||||
tuneValues = append(tuneValues, tuneValue{i + 3130, uint16(s.server.erupeConfig.GameplayOptions.MaterialMultiplier * 100)})
|
||||
}
|
||||
|
||||
for i := uint16(0); i < 13; i++ {
|
||||
tuneValues = append(tuneValues, tuneValue{i + 3156, s.server.erupeConfig.GameplayOptions.ExtraCarves})
|
||||
}
|
||||
for i := uint16(0); i < 13; i++ {
|
||||
tuneValues = append(tuneValues, tuneValue{i + 3182, s.server.erupeConfig.GameplayOptions.ExtraCarves})
|
||||
}
|
||||
for i := uint16(0); i < 13; i++ {
|
||||
tuneValues = append(tuneValues, tuneValue{i + 3208, s.server.erupeConfig.GameplayOptions.ExtraCarves})
|
||||
}
|
||||
for i := uint16(0); i < 13; i++ {
|
||||
tuneValues = append(tuneValues, tuneValue{i + 3234, s.server.erupeConfig.GameplayOptions.ExtraCarves})
|
||||
}
|
||||
|
||||
offset := uint16(time.Now().Unix())
|
||||
bf.WriteUint16(offset)
|
||||
|
||||
tuneLimit := 770
|
||||
if _config.ErupeConfig.RealClientMode <= _config.F5 {
|
||||
tuneLimit = 256
|
||||
} else if _config.ErupeConfig.RealClientMode <= _config.G3 {
|
||||
tuneLimit = 283
|
||||
} else if _config.ErupeConfig.RealClientMode <= _config.GG {
|
||||
tuneLimit = 315
|
||||
} else if _config.ErupeConfig.RealClientMode <= _config.G61 {
|
||||
tuneLimit = 332
|
||||
} else if _config.ErupeConfig.RealClientMode <= _config.G7 {
|
||||
tuneLimit = 339
|
||||
} else if _config.ErupeConfig.RealClientMode <= _config.G81 {
|
||||
tuneLimit = 396
|
||||
} else if _config.ErupeConfig.RealClientMode <= _config.G91 {
|
||||
tuneLimit = 694
|
||||
} else if _config.ErupeConfig.RealClientMode <= _config.G101 {
|
||||
tuneLimit = 704
|
||||
} else if _config.ErupeConfig.RealClientMode <= _config.Z2 {
|
||||
tuneLimit = 750
|
||||
}
|
||||
if len(tuneValues) > tuneLimit {
|
||||
tuneValues = tuneValues[:tuneLimit]
|
||||
}
|
||||
|
||||
bf.WriteUint16(uint16(len(tuneValues)))
|
||||
for i := range tuneValues {
|
||||
bf.WriteUint16(tuneValues[i].ID ^ offset)
|
||||
bf.WriteUint16(offset)
|
||||
bf.WriteBytes(make([]byte, 4))
|
||||
bf.WriteUint16(tuneValues[i].Value ^ offset)
|
||||
}
|
||||
|
||||
vsQuestItems := []uint16{1580, 1581, 1582, 1583, 1584, 1585, 1587, 1588, 1589, 1595, 1596, 1597, 1598, 1599, 1600, 1601, 1602, 1603, 1604}
|
||||
vsQuestBets := []struct {
|
||||
IsTicket bool
|
||||
Quantity uint32
|
||||
}{
|
||||
{true, 5},
|
||||
{false, 1000},
|
||||
{false, 5000},
|
||||
{false, 10000},
|
||||
}
|
||||
bf.WriteUint16(uint16(len(vsQuestItems)))
|
||||
bf.WriteUint16(0) // Unk array of uint16s
|
||||
bf.WriteUint16(uint16(len(vsQuestBets)))
|
||||
bf.WriteUint16(0) // Unk
|
||||
|
||||
for i := range vsQuestItems {
|
||||
bf.WriteUint16(vsQuestItems[i])
|
||||
}
|
||||
for i := range vsQuestBets {
|
||||
bf.WriteBool(vsQuestBets[i].IsTicket)
|
||||
bf.WriteUint8(9)
|
||||
bf.WriteUint16(7)
|
||||
bf.WriteUint32(vsQuestBets[i].Quantity)
|
||||
}
|
||||
|
||||
bf.WriteUint16(totalCount)
|
||||
bf.WriteUint16(pkt.Offset)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
bf.WriteUint16(returnedCount)
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.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())
|
||||
}
|
||||
152
server/channelserver/handlers_register.go
Normal file
152
server/channelserver/handlers_register.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func handleMsgMhfRegisterEvent(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfRegisterEvent)
|
||||
bf := byteframe.NewByteFrame()
|
||||
// Some kind of check if there's already a session
|
||||
if pkt.Unk3 > 0 && s.server.getRaviSemaphore() == nil {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
bf.WriteUint8(uint8(pkt.WorldID))
|
||||
bf.WriteUint8(uint8(pkt.LandID))
|
||||
bf.WriteUint16(s.server.raviente.id)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfReleaseEvent(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReleaseEvent)
|
||||
|
||||
// Do this ack manually because it uses a non-(0|1) error code
|
||||
/*
|
||||
_ACK_SUCCESS = 0
|
||||
_ACK_ERROR = 1
|
||||
|
||||
_ACK_EINPROGRESS = 16
|
||||
_ACK_ENOENT = 17
|
||||
_ACK_ENOSPC = 18
|
||||
_ACK_ETIMEOUT = 19
|
||||
|
||||
_ACK_EINVALID = 64
|
||||
_ACK_EFAILED = 65
|
||||
_ACK_ENOMEM = 66
|
||||
_ACK_ENOTEXIT = 67
|
||||
_ACK_ENOTREADY = 68
|
||||
_ACK_EALREADY = 69
|
||||
_ACK_DISABLE_WORK = 71
|
||||
*/
|
||||
s.QueueSendMHF(&mhfpacket.MsgSysAck{
|
||||
AckHandle: pkt.AckHandle,
|
||||
IsBufferResponse: false,
|
||||
ErrorCode: 0x41,
|
||||
AckData: []byte{0x00, 0x00, 0x00, 0x00},
|
||||
})
|
||||
}
|
||||
|
||||
type RaviUpdate struct {
|
||||
Op uint8
|
||||
Dest uint8
|
||||
Data uint32
|
||||
}
|
||||
|
||||
func handleMsgSysOperateRegister(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysOperateRegister)
|
||||
|
||||
var raviUpdates []RaviUpdate
|
||||
var raviUpdate RaviUpdate
|
||||
// Strip null terminator
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload[:len(pkt.RawDataPayload)-1])
|
||||
for i := len(pkt.RawDataPayload) / 6; i > 0; i-- {
|
||||
raviUpdate.Op = bf.ReadUint8()
|
||||
raviUpdate.Dest = bf.ReadUint8()
|
||||
raviUpdate.Data = bf.ReadUint32()
|
||||
raviUpdates = append(raviUpdates, raviUpdate)
|
||||
}
|
||||
bf = byteframe.NewByteFrame()
|
||||
|
||||
var _old, _new uint32
|
||||
s.server.raviente.Lock()
|
||||
for _, update := range raviUpdates {
|
||||
switch update.Op {
|
||||
case 2:
|
||||
_old, _new = s.server.UpdateRavi(pkt.SemaphoreID, update.Dest, update.Data, true)
|
||||
case 13, 14:
|
||||
_old, _new = s.server.UpdateRavi(pkt.SemaphoreID, update.Dest, update.Data, false)
|
||||
}
|
||||
bf.WriteUint8(1)
|
||||
bf.WriteUint8(update.Dest)
|
||||
bf.WriteUint32(_old)
|
||||
bf.WriteUint32(_new)
|
||||
}
|
||||
s.server.raviente.Unlock()
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
|
||||
if s.server.erupeConfig.GameplayOptions.LowLatencyRaviente {
|
||||
s.notifyRavi()
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgSysLoadRegister(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysLoadRegister)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(0)
|
||||
bf.WriteUint8(pkt.Values)
|
||||
for i := uint8(0); i < pkt.Values; i++ {
|
||||
switch pkt.RegisterID {
|
||||
case 0x40000:
|
||||
bf.WriteUint32(s.server.raviente.state[i])
|
||||
case 0x50000:
|
||||
bf.WriteUint32(s.server.raviente.support[i])
|
||||
case 0x60000:
|
||||
bf.WriteUint32(s.server.raviente.register[i])
|
||||
}
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func (s *Session) notifyRavi() {
|
||||
sema := s.server.getRaviSemaphore()
|
||||
if sema == nil {
|
||||
return
|
||||
}
|
||||
var temp mhfpacket.MHFPacket
|
||||
raviNotif := byteframe.NewByteFrame()
|
||||
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 0x40000}
|
||||
raviNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(raviNotif, s.clientContext)
|
||||
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 0x50000}
|
||||
raviNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(raviNotif, s.clientContext)
|
||||
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 0x60000}
|
||||
raviNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(raviNotif, s.clientContext)
|
||||
raviNotif.WriteUint16(0x0010) // End it.
|
||||
if s.server.erupeConfig.GameplayOptions.LowLatencyRaviente {
|
||||
for session := range sema.clients {
|
||||
session.QueueSend(raviNotif.Data())
|
||||
}
|
||||
} else {
|
||||
for session := range sema.clients {
|
||||
if session.charID == s.charID {
|
||||
session.QueueSend(raviNotif.Data())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) getRaviSemaphore() *Semaphore {
|
||||
for _, semaphore := range s.semaphore {
|
||||
if strings.HasPrefix(semaphore.name, "hs_l0") && strings.HasSuffix(semaphore.name, "3") {
|
||||
return semaphore
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleMsgSysNotifyRegister(s *Session, p mhfpacket.MHFPacket) {}
|
||||
188
server/channelserver/handlers_rengoku.go
Normal file
188
server/channelserver/handlers_rengoku.go
Normal file
@@ -0,0 +1,188 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
"fmt"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"os"
|
||||
"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)
|
||||
dumpSaveData(s, pkt.RawDataPayload, "rengoku")
|
||||
_, err := s.server.db.Exec("UPDATE characters SET rengokudata=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to save rengokudata", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
|
||||
bf.Seek(71, 0)
|
||||
maxStageMp := bf.ReadUint32()
|
||||
maxScoreMp := bf.ReadUint32()
|
||||
bf.Seek(4, 1)
|
||||
maxStageSp := bf.ReadUint32()
|
||||
maxScoreSp := bf.ReadUint32()
|
||||
var t int
|
||||
err = s.server.db.QueryRow("SELECT character_id FROM rengoku_score WHERE character_id=$1", s.charID).Scan(&t)
|
||||
if err != nil {
|
||||
s.server.db.Exec("INSERT INTO rengoku_score (character_id) VALUES ($1)", s.charID)
|
||||
}
|
||||
s.server.db.Exec("UPDATE rengoku_score SET max_stages_mp=$1, max_points_mp=$2, max_stages_sp=$3, max_points_sp=$4 WHERE character_id=$5", maxStageMp, maxScoreMp, maxStageSp, maxScoreSp, s.charID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
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.Error("Failed to load rengokudata", 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 := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "rengoku_data.bin"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
const rengokuScoreQuery = `, c.name FROM rengoku_score rs
|
||||
LEFT JOIN characters c ON c.id = rs.character_id
|
||||
LEFT JOIN guild_characters gc ON gc.character_id = rs.character_id `
|
||||
|
||||
type RengokuScore struct {
|
||||
Name string `db:"name"`
|
||||
Score uint32 `db:"score"`
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateRengokuRanking(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateRengokuRanking)
|
||||
|
||||
guild, _ := GetGuildInfoByCharacterId(s, s.charID)
|
||||
isApplicant, _ := guild.HasApplicationForCharID(s, s.charID)
|
||||
if isApplicant {
|
||||
guild = nil
|
||||
}
|
||||
|
||||
if pkt.Leaderboard == 2 || pkt.Leaderboard == 3 || pkt.Leaderboard == 6 || pkt.Leaderboard == 7 {
|
||||
if guild == nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 11))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var score RengokuScore
|
||||
var selfExist bool
|
||||
i := uint32(1)
|
||||
bf := byteframe.NewByteFrame()
|
||||
scoreData := byteframe.NewByteFrame()
|
||||
|
||||
var rows *sqlx.Rows
|
||||
switch pkt.Leaderboard {
|
||||
case 0:
|
||||
rows, _ = s.server.db.Queryx(fmt.Sprintf("SELECT max_stages_mp AS score %s ORDER BY max_stages_mp DESC", rengokuScoreQuery))
|
||||
case 1:
|
||||
rows, _ = s.server.db.Queryx(fmt.Sprintf("SELECT max_points_mp AS score %s ORDER BY max_points_mp DESC", rengokuScoreQuery))
|
||||
case 2:
|
||||
rows, _ = s.server.db.Queryx(fmt.Sprintf("SELECT max_stages_mp AS score %s WHERE guild_id=$1 ORDER BY max_stages_mp DESC", rengokuScoreQuery), guild.ID)
|
||||
case 3:
|
||||
rows, _ = s.server.db.Queryx(fmt.Sprintf("SELECT max_points_mp AS score %s WHERE guild_id=$1 ORDER BY max_points_mp DESC", rengokuScoreQuery), guild.ID)
|
||||
case 4:
|
||||
rows, _ = s.server.db.Queryx(fmt.Sprintf("SELECT max_stages_sp AS score %s ORDER BY max_stages_sp DESC", rengokuScoreQuery))
|
||||
case 5:
|
||||
rows, _ = s.server.db.Queryx(fmt.Sprintf("SELECT max_points_sp AS score %s ORDER BY max_points_sp DESC", rengokuScoreQuery))
|
||||
case 6:
|
||||
rows, _ = s.server.db.Queryx(fmt.Sprintf("SELECT max_stages_sp AS score %s WHERE guild_id=$1 ORDER BY max_stages_sp DESC", rengokuScoreQuery), guild.ID)
|
||||
case 7:
|
||||
rows, _ = s.server.db.Queryx(fmt.Sprintf("SELECT max_points_sp AS score %s WHERE guild_id=$1 ORDER BY max_points_sp DESC", rengokuScoreQuery), guild.ID)
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
rows.StructScan(&score)
|
||||
if score.Name == s.Name {
|
||||
bf.WriteUint32(i)
|
||||
bf.WriteUint32(score.Score)
|
||||
ps.Uint8(bf, s.Name, true)
|
||||
ps.Uint8(bf, "", false)
|
||||
selfExist = true
|
||||
}
|
||||
if i > 100 {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
scoreData.WriteUint32(i)
|
||||
scoreData.WriteUint32(score.Score)
|
||||
ps.Uint8(scoreData, score.Name, true)
|
||||
ps.Uint8(scoreData, "", false)
|
||||
i++
|
||||
}
|
||||
|
||||
if !selfExist {
|
||||
bf.WriteBytes(make([]byte, 10))
|
||||
}
|
||||
bf.WriteUint8(uint8(i) - 1)
|
||||
bf.WriteBytes(scoreData.Data())
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfGetRengokuRankingRank(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetRengokuRankingRank)
|
||||
// What is this for?
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(0) // Max stage overall MP rank
|
||||
bf.WriteUint32(0) // Max RdP overall MP rank
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.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) {}
|
||||
45
server/channelserver/handlers_reward.go
Normal file
45
server/channelserver/handlers_reward.go
Normal file
@@ -0,0 +1,45 @@
|
||||
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) {}
|
||||
106
server/channelserver/handlers_seibattle.go
Normal file
106
server/channelserver/handlers_seibattle.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
func handleMsgMhfGetBreakSeibatuLevelReward(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetBreakSeibatuLevelReward)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
type WeeklySeibatuRankingReward struct {
|
||||
Unk0 int32
|
||||
Unk1 int32
|
||||
Unk2 uint32
|
||||
Unk3 int32
|
||||
Unk4 int32
|
||||
Unk5 int32
|
||||
}
|
||||
|
||||
func handleMsgMhfGetWeeklySeibatuRankingReward(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetWeeklySeibatuRankingReward)
|
||||
var data []*byteframe.ByteFrame
|
||||
weeklySeibatuRankingRewards := []WeeklySeibatuRankingReward{
|
||||
{0, 0, 0, 0, 0, 0},
|
||||
}
|
||||
for _, reward := range weeklySeibatuRankingRewards {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteInt32(reward.Unk0)
|
||||
bf.WriteInt32(reward.Unk1)
|
||||
bf.WriteUint32(reward.Unk2)
|
||||
bf.WriteInt32(reward.Unk3)
|
||||
bf.WriteInt32(reward.Unk4)
|
||||
bf.WriteInt32(reward.Unk5)
|
||||
data = append(data, bf)
|
||||
}
|
||||
doAckEarthSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfGetFixedSeibatuRankingTable(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetFixedSeibatuRankingTable)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteBytes(make([]byte, 32))
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfReadBeatLevel(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReadBeatLevel)
|
||||
|
||||
// This response is fixed and will never change on JP,
|
||||
// but I've left it dynamic for possible other client differences.
|
||||
resp := byteframe.NewByteFrame()
|
||||
for i := 0; i < int(pkt.ValidIDCount); i++ {
|
||||
resp.WriteUint32(pkt.IDs[i])
|
||||
resp.WriteUint32(1)
|
||||
resp.WriteUint32(1)
|
||||
resp.WriteUint32(1)
|
||||
}
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfReadLastWeekBeatRanking(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReadLastWeekBeatRanking)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfUpdateBeatLevel(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfUpdateBeatLevel)
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfReadBeatLevelAllRanking(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReadBeatLevelAllRanking)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteBytes(make([]byte, 32))
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfReadBeatLevelMyRanking(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReadBeatLevelMyRanking)
|
||||
bf := byteframe.NewByteFrame()
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
128
server/channelserver/handlers_semaphore.go
Normal file
128
server/channelserver/handlers_semaphore.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
"go.uber.org/zap"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
func removeSessionFromSemaphore(s *Session) {
|
||||
s.server.semaphoreLock.Lock()
|
||||
for _, semaphore := range s.server.semaphore {
|
||||
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.clients) == 0 {
|
||||
delete(s.server.semaphore, id)
|
||||
if strings.HasPrefix(id, "hs_l0") {
|
||||
s.server.resetRaviente()
|
||||
}
|
||||
s.logger.Debug("Destructed semaphore", zap.String("sema.name", id))
|
||||
}
|
||||
}
|
||||
s.server.semaphoreLock.Unlock()
|
||||
}
|
||||
|
||||
func handleMsgSysDeleteSemaphore(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysDeleteSemaphore)
|
||||
destructEmptySemaphores(s)
|
||||
s.server.semaphoreLock.Lock()
|
||||
for id, sema := range s.server.semaphore {
|
||||
if sema.id == pkt.SemaphoreID {
|
||||
for session := range sema.clients {
|
||||
if s == session {
|
||||
delete(sema.clients, s)
|
||||
}
|
||||
}
|
||||
if len(sema.clients) == 0 {
|
||||
delete(s.server.semaphore, id)
|
||||
if strings.HasPrefix(id, "hs_l0") {
|
||||
s.server.resetRaviente()
|
||||
}
|
||||
s.logger.Debug("Destructed semaphore", zap.String("sema.name", id))
|
||||
}
|
||||
}
|
||||
}
|
||||
s.server.semaphoreLock.Unlock()
|
||||
}
|
||||
|
||||
func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysCreateAcquireSemaphore)
|
||||
SemaphoreID := pkt.SemaphoreID
|
||||
|
||||
newSemaphore, exists := s.server.semaphore[SemaphoreID]
|
||||
if !exists {
|
||||
s.server.semaphoreLock.Lock()
|
||||
if strings.HasPrefix(SemaphoreID, "hs_l0") {
|
||||
suffix, _ := strconv.Atoi(pkt.SemaphoreID[len(pkt.SemaphoreID)-1:])
|
||||
s.server.semaphore[SemaphoreID] = &Semaphore{
|
||||
name: pkt.SemaphoreID,
|
||||
id: uint32((suffix + 1) * 0x10000),
|
||||
clients: make(map[*Session]uint32),
|
||||
maxPlayers: 127,
|
||||
}
|
||||
} else {
|
||||
s.server.semaphore[SemaphoreID] = NewSemaphore(s.server, SemaphoreID, 1)
|
||||
}
|
||||
newSemaphore = s.server.semaphore[SemaphoreID]
|
||||
s.server.semaphoreLock.Unlock()
|
||||
}
|
||||
|
||||
newSemaphore.Lock()
|
||||
defer newSemaphore.Unlock()
|
||||
bf := byteframe.NewByteFrame()
|
||||
if _, exists := newSemaphore.clients[s]; exists {
|
||||
bf.WriteUint32(newSemaphore.id)
|
||||
} else if uint16(len(newSemaphore.clients)) < newSemaphore.maxPlayers {
|
||||
newSemaphore.clients[s] = s.charID
|
||||
s.Lock()
|
||||
s.semaphore = newSemaphore
|
||||
s.Unlock()
|
||||
bf.WriteUint32(newSemaphore.id)
|
||||
} else {
|
||||
bf.WriteUint32(0)
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgSysAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysAcquireSemaphore)
|
||||
if sema, exists := s.server.semaphore[pkt.SemaphoreID]; exists {
|
||||
sema.clients[s] = s.charID
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(sema.id)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
} else {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
690
server/channelserver/handlers_shop_gacha.go
Normal file
690
server/channelserver/handlers_shop_gacha.go
Normal file
@@ -0,0 +1,690 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
_config "erupe-ce/config"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
type ShopItem struct {
|
||||
ID uint32 `db:"id"`
|
||||
ItemID uint16 `db:"item_id"`
|
||||
Cost uint32 `db:"cost"`
|
||||
Quantity uint16 `db:"quantity"`
|
||||
MinHR uint16 `db:"min_hr"`
|
||||
MinSR uint16 `db:"min_sr"`
|
||||
MinGR uint16 `db:"min_gr"`
|
||||
StoreLevel uint16 `db:"store_level"`
|
||||
MaxQuantity uint16 `db:"max_quantity"`
|
||||
UsedQuantity uint16 `db:"used_quantity"`
|
||||
RoadFloors uint16 `db:"road_floors"`
|
||||
RoadFatalis uint16 `db:"road_fatalis"`
|
||||
}
|
||||
|
||||
type Gacha struct {
|
||||
ID uint32 `db:"id"`
|
||||
MinGR uint32 `db:"min_gr"`
|
||||
MinHR uint32 `db:"min_hr"`
|
||||
Name string `db:"name"`
|
||||
URLBanner string `db:"url_banner"`
|
||||
URLFeature string `db:"url_feature"`
|
||||
URLThumbnail string `db:"url_thumbnail"`
|
||||
Wide bool `db:"wide"`
|
||||
Recommended bool `db:"recommended"`
|
||||
GachaType uint8 `db:"gacha_type"`
|
||||
Hidden bool `db:"hidden"`
|
||||
}
|
||||
|
||||
type GachaEntry struct {
|
||||
EntryType uint8 `db:"entry_type"`
|
||||
ID uint32 `db:"id"`
|
||||
ItemType uint8 `db:"item_type"`
|
||||
ItemNumber uint32 `db:"item_number"`
|
||||
ItemQuantity uint16 `db:"item_quantity"`
|
||||
Weight float64 `db:"weight"`
|
||||
Rarity uint8 `db:"rarity"`
|
||||
Rolls uint8 `db:"rolls"`
|
||||
FrontierPoints uint16 `db:"frontier_points"`
|
||||
DailyLimit uint8 `db:"daily_limit"`
|
||||
Name string `db:"name"`
|
||||
}
|
||||
|
||||
type GachaItem struct {
|
||||
ItemType uint8 `db:"item_type"`
|
||||
ItemID uint16 `db:"item_id"`
|
||||
Quantity uint16 `db:"quantity"`
|
||||
}
|
||||
|
||||
func writeShopItems(bf *byteframe.ByteFrame, items []ShopItem) {
|
||||
bf.WriteUint16(uint16(len(items)))
|
||||
bf.WriteUint16(uint16(len(items)))
|
||||
for _, item := range items {
|
||||
bf.WriteUint32(item.ID)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(item.ItemID)
|
||||
bf.WriteUint32(item.Cost)
|
||||
bf.WriteUint16(item.Quantity)
|
||||
bf.WriteUint16(item.MinHR)
|
||||
bf.WriteUint16(item.MinSR)
|
||||
bf.WriteUint16(item.MinGR)
|
||||
bf.WriteUint16(item.StoreLevel)
|
||||
bf.WriteUint16(item.MaxQuantity)
|
||||
bf.WriteUint16(item.UsedQuantity)
|
||||
bf.WriteUint16(item.RoadFloors)
|
||||
bf.WriteUint16(item.RoadFatalis)
|
||||
}
|
||||
}
|
||||
|
||||
func getShopItems(s *Session, shopType uint8, shopID uint32) []ShopItem {
|
||||
var items []ShopItem
|
||||
var temp ShopItem
|
||||
rows, err := s.server.db.Queryx(`SELECT id, item_id, cost, quantity, min_hr, min_sr, min_gr, store_level, max_quantity,
|
||||
COALESCE((SELECT bought FROM shop_items_bought WHERE shop_item_id=si.id AND character_id=$3), 0) as used_quantity,
|
||||
road_floors, road_fatalis FROM shop_items si WHERE shop_type=$1 AND shop_id=$2
|
||||
`, shopType, shopID, s.charID)
|
||||
if err == nil {
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&temp)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
items = append(items, temp)
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateShop)
|
||||
// Generic Shop IDs
|
||||
// 0: basic item
|
||||
// 1: gatherables
|
||||
// 2: hr1-4 materials
|
||||
// 3: hr5-7 materials
|
||||
// 4: decos
|
||||
// 5: other item
|
||||
// 6: g mats
|
||||
// 7: limited item
|
||||
// 8: special item
|
||||
switch pkt.ShopType {
|
||||
case 1: // Running gachas
|
||||
// Fundamentally, gacha works completely differently, just hide it for now.
|
||||
if _config.ErupeConfig.RealClientMode <= _config.G7 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
|
||||
var count uint16
|
||||
shopEntries, err := s.server.db.Queryx("SELECT id, min_gr, min_hr, name, url_banner, url_feature, url_thumbnail, wide, recommended, gacha_type, hidden FROM gacha_shop")
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(0)
|
||||
var gacha Gacha
|
||||
for shopEntries.Next() {
|
||||
err = shopEntries.StructScan(&gacha)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
resp.WriteUint32(gacha.ID)
|
||||
resp.WriteBytes(make([]byte, 16)) // Rank restriction
|
||||
resp.WriteUint32(gacha.MinGR)
|
||||
resp.WriteUint32(gacha.MinHR)
|
||||
resp.WriteUint32(0) // only 0 in known packet
|
||||
ps.Uint8(resp, gacha.Name, true)
|
||||
ps.Uint8(resp, gacha.URLBanner, false)
|
||||
ps.Uint8(resp, gacha.URLFeature, false)
|
||||
resp.WriteBool(gacha.Wide)
|
||||
ps.Uint8(resp, gacha.URLThumbnail, false)
|
||||
resp.WriteUint8(0) // Unk
|
||||
if gacha.Recommended {
|
||||
resp.WriteUint8(2)
|
||||
} else {
|
||||
resp.WriteUint8(0)
|
||||
}
|
||||
resp.WriteUint8(gacha.GachaType)
|
||||
resp.WriteBool(gacha.Hidden)
|
||||
count++
|
||||
}
|
||||
resp.Seek(0, 0)
|
||||
resp.WriteUint16(count)
|
||||
resp.WriteUint16(count)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
case 2: // Actual gacha
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(pkt.ShopID)
|
||||
var gachaType int
|
||||
s.server.db.QueryRow(`SELECT gacha_type FROM gacha_shop WHERE id = $1`, pkt.ShopID).Scan(&gachaType)
|
||||
entries, err := s.server.db.Queryx(`SELECT entry_type, id, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points, name FROM gacha_entries WHERE gacha_id = $1 ORDER BY weight DESC`, pkt.ShopID)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
var divisor float64
|
||||
s.server.db.QueryRow(`SELECT COALESCE(SUM(weight) / 100000.0, 0) AS chance FROM gacha_entries WHERE gacha_id = $1`, pkt.ShopID).Scan(&divisor)
|
||||
var entryCount uint16
|
||||
bf.WriteUint16(0)
|
||||
gachaEntry := GachaEntry{}
|
||||
gachaItem := GachaItem{}
|
||||
for entries.Next() {
|
||||
entryCount++
|
||||
entries.StructScan(&gachaEntry)
|
||||
bf.WriteUint8(gachaEntry.EntryType)
|
||||
bf.WriteUint32(gachaEntry.ID)
|
||||
bf.WriteUint8(gachaEntry.ItemType)
|
||||
bf.WriteUint32(gachaEntry.ItemNumber)
|
||||
bf.WriteUint16(gachaEntry.ItemQuantity)
|
||||
if gachaType >= 4 { // If box
|
||||
bf.WriteUint16(1)
|
||||
} else {
|
||||
bf.WriteUint16(uint16(gachaEntry.Weight / divisor))
|
||||
}
|
||||
bf.WriteUint8(gachaEntry.Rarity)
|
||||
bf.WriteUint8(gachaEntry.Rolls)
|
||||
|
||||
var itemCount uint8
|
||||
temp := byteframe.NewByteFrame()
|
||||
items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id=$1`, gachaEntry.ID)
|
||||
if err != nil {
|
||||
bf.WriteUint8(0)
|
||||
} else {
|
||||
for items.Next() {
|
||||
itemCount++
|
||||
items.StructScan(&gachaItem)
|
||||
temp.WriteUint16(uint16(gachaItem.ItemType))
|
||||
temp.WriteUint16(gachaItem.ItemID)
|
||||
temp.WriteUint16(gachaItem.Quantity)
|
||||
}
|
||||
bf.WriteUint8(itemCount)
|
||||
}
|
||||
|
||||
bf.WriteUint16(gachaEntry.FrontierPoints)
|
||||
bf.WriteUint8(gachaEntry.DailyLimit)
|
||||
if gachaEntry.EntryType < 10 {
|
||||
ps.Uint8(bf, gachaEntry.Name, true)
|
||||
} else {
|
||||
bf.WriteUint8(0)
|
||||
}
|
||||
bf.WriteBytes(temp.Data())
|
||||
}
|
||||
bf.Seek(4, 0)
|
||||
bf.WriteUint16(entryCount)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
case 3: // Hunting Festival Exchange
|
||||
fallthrough
|
||||
case 4: // N Points, 0-6
|
||||
fallthrough
|
||||
case 5: // GCP->Item, 0-6
|
||||
fallthrough
|
||||
case 6: // Gacha coin->Item
|
||||
fallthrough
|
||||
case 7: // Item->GCP
|
||||
fallthrough
|
||||
case 8: // Diva
|
||||
fallthrough
|
||||
case 9: // Diva song shop
|
||||
fallthrough
|
||||
case 10: // Item shop, 0-8
|
||||
bf := byteframe.NewByteFrame()
|
||||
items := getShopItems(s, pkt.ShopType, pkt.ShopID)
|
||||
writeShopItems(bf, items)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireExchangeShop(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAcquireExchangeShop)
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
|
||||
exchanges := int(bf.ReadUint16())
|
||||
for i := 0; i < exchanges; i++ {
|
||||
itemHash := bf.ReadUint32()
|
||||
if itemHash == 0 {
|
||||
continue
|
||||
}
|
||||
buyCount := bf.ReadUint32()
|
||||
s.server.db.Exec(`INSERT INTO shop_items_bought (character_id, shop_item_id, bought)
|
||||
VALUES ($1,$2,$3) ON CONFLICT (character_id, shop_item_id)
|
||||
DO UPDATE SET bought = bought + $3
|
||||
WHERE EXCLUDED.character_id=$1 AND EXCLUDED.shop_item_id=$2
|
||||
`, s.charID, itemHash, buyCount)
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfGetGachaPlayHistory(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGachaPlayHistory)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(1)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
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_premium, 0), COALESCE(gacha_trial, 0) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)", s.charID).Scan(&fp, &gp, >)
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(gp)
|
||||
resp.WriteUint32(gt)
|
||||
resp.WriteUint32(fp)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfUseGachaPoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfUseGachaPoint)
|
||||
if pkt.TrialCoins > 0 {
|
||||
s.server.db.Exec(`UPDATE users u SET gacha_trial=gacha_trial-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, pkt.TrialCoins, s.charID)
|
||||
}
|
||||
if pkt.PremiumCoins > 0 {
|
||||
s.server.db.Exec(`UPDATE users u SET gacha_premium=gacha_premium-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, pkt.PremiumCoins, s.charID)
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func spendGachaCoin(s *Session, quantity uint16) {
|
||||
var gt uint16
|
||||
s.server.db.QueryRow(`SELECT COALESCE(gacha_trial, 0) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(>)
|
||||
if quantity <= gt {
|
||||
s.server.db.Exec(`UPDATE users u SET gacha_trial=gacha_trial-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, quantity, s.charID)
|
||||
} else {
|
||||
s.server.db.Exec(`UPDATE users u SET gacha_premium=gacha_premium-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, quantity, s.charID)
|
||||
}
|
||||
}
|
||||
|
||||
func transactGacha(s *Session, gachaID uint32, rollID uint8) (error, int) {
|
||||
var itemType uint8
|
||||
var itemNumber uint16
|
||||
var rolls int
|
||||
err := s.server.db.QueryRowx(`SELECT item_type, item_number, rolls FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2`, gachaID, rollID).Scan(&itemType, &itemNumber, &rolls)
|
||||
if err != nil {
|
||||
return err, 0
|
||||
}
|
||||
switch itemType {
|
||||
/*
|
||||
valid types that need manual savedata manipulation:
|
||||
- Ryoudan Points
|
||||
- Bond Points
|
||||
- Image Change Points
|
||||
valid types that work (no additional code needed):
|
||||
- Tore Points
|
||||
- Festa Points
|
||||
*/
|
||||
case 17:
|
||||
_ = addPointNetcafe(s, int(itemNumber)*-1)
|
||||
case 19:
|
||||
fallthrough
|
||||
case 20:
|
||||
spendGachaCoin(s, itemNumber)
|
||||
case 21:
|
||||
s.server.db.Exec("UPDATE users u SET frontier_points=frontier_points-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", itemNumber, s.charID)
|
||||
}
|
||||
return nil, rolls
|
||||
}
|
||||
|
||||
func getGuaranteedItems(s *Session, gachaID uint32, rollID uint8) []GachaItem {
|
||||
var rewards []GachaItem
|
||||
var reward GachaItem
|
||||
items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = (SELECT id FROM gacha_entries WHERE entry_type = $1 AND gacha_id = $2)`, rollID, gachaID)
|
||||
if err == nil {
|
||||
for items.Next() {
|
||||
items.StructScan(&reward)
|
||||
rewards = append(rewards, reward)
|
||||
}
|
||||
}
|
||||
return rewards
|
||||
}
|
||||
|
||||
func addGachaItem(s *Session, items []GachaItem) {
|
||||
var data []byte
|
||||
s.server.db.QueryRow(`SELECT gacha_items FROM characters WHERE id = $1`, s.charID).Scan(&data)
|
||||
if len(data) > 0 {
|
||||
numItems := int(data[0])
|
||||
data = data[1:]
|
||||
oldItem := byteframe.NewByteFrameFromBytes(data)
|
||||
for i := 0; i < numItems; i++ {
|
||||
items = append(items, GachaItem{
|
||||
ItemType: oldItem.ReadUint8(),
|
||||
ItemID: oldItem.ReadUint16(),
|
||||
Quantity: oldItem.ReadUint16(),
|
||||
})
|
||||
}
|
||||
}
|
||||
newItem := byteframe.NewByteFrame()
|
||||
newItem.WriteUint8(uint8(len(items)))
|
||||
for i := range items {
|
||||
newItem.WriteUint8(items[i].ItemType)
|
||||
newItem.WriteUint16(items[i].ItemID)
|
||||
newItem.WriteUint16(items[i].Quantity)
|
||||
}
|
||||
s.server.db.Exec(`UPDATE characters SET gacha_items = $1 WHERE id = $2`, newItem.Data(), s.charID)
|
||||
}
|
||||
|
||||
func getRandomEntries(entries []GachaEntry, rolls int, isBox bool) ([]GachaEntry, error) {
|
||||
var chosen []GachaEntry
|
||||
var totalWeight float64
|
||||
for i := range entries {
|
||||
totalWeight += entries[i].Weight
|
||||
}
|
||||
for {
|
||||
if rolls == len(chosen) {
|
||||
break
|
||||
}
|
||||
if !isBox {
|
||||
result := rand.Float64() * totalWeight
|
||||
for _, entry := range entries {
|
||||
result -= entry.Weight
|
||||
if result < 0 {
|
||||
chosen = append(chosen, entry)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result := rand.Intn(len(entries))
|
||||
chosen = append(chosen, entries[result])
|
||||
entries[result] = entries[len(entries)-1]
|
||||
entries = entries[:len(entries)-1]
|
||||
}
|
||||
}
|
||||
return chosen, nil
|
||||
}
|
||||
|
||||
func handleMsgMhfReceiveGachaItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReceiveGachaItem)
|
||||
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 {
|
||||
data = []byte{0x00}
|
||||
}
|
||||
|
||||
// I think there are still some edge cases where rewards can be nulled via overflow
|
||||
if data[0] > 36 || len(data) > 181 {
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(36)
|
||||
resp.WriteBytes(data[1:181])
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
if !pkt.Freeze {
|
||||
if data[0] > 36 || len(data) > 181 {
|
||||
update := byteframe.NewByteFrame()
|
||||
update.WriteUint8(uint8(len(data[181:]) / 5))
|
||||
update.WriteBytes(data[181:])
|
||||
s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", update.Data(), s.charID)
|
||||
} else {
|
||||
s.server.db.Exec("UPDATE characters SET gacha_items = null WHERE id = $1", s.charID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPlayNormalGacha)
|
||||
bf := byteframe.NewByteFrame()
|
||||
var gachaEntries []GachaEntry
|
||||
var entry GachaEntry
|
||||
var rewards []GachaItem
|
||||
var reward GachaItem
|
||||
err, rolls := transactGacha(s, pkt.GachaID, pkt.RollType)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
||||
return
|
||||
}
|
||||
temp := byteframe.NewByteFrame()
|
||||
entries, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
||||
return
|
||||
}
|
||||
for entries.Next() {
|
||||
entries.StructScan(&entry)
|
||||
gachaEntries = append(gachaEntries, entry)
|
||||
}
|
||||
rewardEntries, err := getRandomEntries(gachaEntries, rolls, false)
|
||||
for i := range rewardEntries {
|
||||
items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = $1`, rewardEntries[i].ID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for items.Next() {
|
||||
items.StructScan(&reward)
|
||||
rewards = append(rewards, reward)
|
||||
temp.WriteUint8(reward.ItemType)
|
||||
temp.WriteUint16(reward.ItemID)
|
||||
temp.WriteUint16(reward.Quantity)
|
||||
temp.WriteUint8(entry.Rarity)
|
||||
}
|
||||
}
|
||||
bf.WriteUint8(uint8(len(rewards)))
|
||||
bf.WriteBytes(temp.Data())
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
addGachaItem(s, rewards)
|
||||
}
|
||||
|
||||
func handleMsgMhfPlayStepupGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPlayStepupGacha)
|
||||
bf := byteframe.NewByteFrame()
|
||||
var gachaEntries []GachaEntry
|
||||
var entry GachaEntry
|
||||
var rewards []GachaItem
|
||||
var reward GachaItem
|
||||
err, rolls := transactGacha(s, pkt.GachaID, pkt.RollType)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
||||
return
|
||||
}
|
||||
s.server.db.Exec("UPDATE users u SET frontier_points=frontier_points+(SELECT frontier_points FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2) WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$3)", pkt.GachaID, pkt.RollType, s.charID)
|
||||
s.server.db.Exec(`DELETE FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID)
|
||||
s.server.db.Exec(`INSERT INTO gacha_stepup (gacha_id, step, character_id) VALUES ($1, $2, $3)`, pkt.GachaID, pkt.RollType+1, s.charID)
|
||||
temp := byteframe.NewByteFrame()
|
||||
guaranteedItems := getGuaranteedItems(s, pkt.GachaID, pkt.RollType)
|
||||
for _, item := range guaranteedItems {
|
||||
temp.WriteUint8(item.ItemType)
|
||||
temp.WriteUint16(item.ItemID)
|
||||
temp.WriteUint16(item.Quantity)
|
||||
temp.WriteUint8(0)
|
||||
}
|
||||
entries, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
||||
return
|
||||
}
|
||||
for entries.Next() {
|
||||
entries.StructScan(&entry)
|
||||
gachaEntries = append(gachaEntries, entry)
|
||||
}
|
||||
rewardEntries, err := getRandomEntries(gachaEntries, rolls, false)
|
||||
for i := range rewardEntries {
|
||||
items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = $1`, rewardEntries[i].ID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for items.Next() {
|
||||
items.StructScan(&reward)
|
||||
rewards = append(rewards, reward)
|
||||
temp.WriteUint8(reward.ItemType)
|
||||
temp.WriteUint16(reward.ItemID)
|
||||
temp.WriteUint16(reward.Quantity)
|
||||
temp.WriteUint8(entry.Rarity)
|
||||
}
|
||||
}
|
||||
bf.WriteUint8(uint8(len(rewards) + len(guaranteedItems)))
|
||||
bf.WriteUint8(uint8(len(rewards)))
|
||||
bf.WriteBytes(temp.Data())
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
addGachaItem(s, rewards)
|
||||
addGachaItem(s, guaranteedItems)
|
||||
}
|
||||
|
||||
func handleMsgMhfGetStepupStatus(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetStepupStatus)
|
||||
// TODO: Reset daily (noon)
|
||||
var step uint8
|
||||
s.server.db.QueryRow(`SELECT step FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID).Scan(&step)
|
||||
var stepCheck int
|
||||
s.server.db.QueryRow(`SELECT COUNT(1) FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2`, pkt.GachaID, step).Scan(&stepCheck)
|
||||
if stepCheck == 0 {
|
||||
s.server.db.Exec(`DELETE FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID)
|
||||
step = 0
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(step)
|
||||
bf.WriteUint32(uint32(TimeAdjusted().Unix()))
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfGetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetBoxGachaInfo)
|
||||
entries, err := s.server.db.Queryx(`SELECT entry_id FROM gacha_box WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
||||
return
|
||||
}
|
||||
var entryIDs []uint32
|
||||
for entries.Next() {
|
||||
var entryID uint32
|
||||
entries.Scan(&entryID)
|
||||
entryIDs = append(entryIDs, entryID)
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(uint8(len(entryIDs)))
|
||||
for i := range entryIDs {
|
||||
bf.WriteUint32(entryIDs[i])
|
||||
bf.WriteBool(true)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfPlayBoxGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPlayBoxGacha)
|
||||
bf := byteframe.NewByteFrame()
|
||||
var gachaEntries []GachaEntry
|
||||
var entry GachaEntry
|
||||
var rewards []GachaItem
|
||||
var reward GachaItem
|
||||
err, rolls := transactGacha(s, pkt.GachaID, pkt.RollType)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
||||
return
|
||||
}
|
||||
temp := byteframe.NewByteFrame()
|
||||
entries, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
||||
return
|
||||
}
|
||||
for entries.Next() {
|
||||
entries.StructScan(&entry)
|
||||
gachaEntries = append(gachaEntries, entry)
|
||||
}
|
||||
rewardEntries, err := getRandomEntries(gachaEntries, rolls, true)
|
||||
for i := range rewardEntries {
|
||||
items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = $1`, rewardEntries[i].ID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
s.server.db.Exec(`INSERT INTO gacha_box (gacha_id, entry_id, character_id) VALUES ($1, $2, $3)`, pkt.GachaID, rewardEntries[i].ID, s.charID)
|
||||
for items.Next() {
|
||||
items.StructScan(&reward)
|
||||
rewards = append(rewards, reward)
|
||||
temp.WriteUint8(reward.ItemType)
|
||||
temp.WriteUint16(reward.ItemID)
|
||||
temp.WriteUint16(reward.Quantity)
|
||||
temp.WriteUint8(0)
|
||||
}
|
||||
}
|
||||
bf.WriteUint8(uint8(len(rewards)))
|
||||
bf.WriteBytes(temp.Data())
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
addGachaItem(s, rewards)
|
||||
}
|
||||
|
||||
func handleMsgMhfResetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfResetBoxGachaInfo)
|
||||
s.server.db.Exec("DELETE FROM gacha_box WHERE gacha_id = $1 AND character_id = $2", pkt.GachaID, s.charID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfExchangeFpoint2Item(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfExchangeFpoint2Item)
|
||||
var balance uint32
|
||||
var itemValue, quantity int
|
||||
s.server.db.QueryRow("SELECT quantity, fpoints FROM fpoint_items WHERE id=$1", pkt.TradeID).Scan(&quantity, &itemValue)
|
||||
cost := (int(pkt.Quantity) * quantity) * itemValue
|
||||
s.server.db.QueryRow("UPDATE users u SET frontier_points=frontier_points::int - $1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2) RETURNING frontier_points", cost, s.charID).Scan(&balance)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(balance)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfExchangeItem2Fpoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfExchangeItem2Fpoint)
|
||||
var balance uint32
|
||||
var itemValue, quantity int
|
||||
s.server.db.QueryRow("SELECT quantity, fpoints FROM fpoint_items WHERE id=$1", pkt.TradeID).Scan(&quantity, &itemValue)
|
||||
cost := (int(pkt.Quantity) / quantity) * itemValue
|
||||
s.server.db.QueryRow("UPDATE users u SET frontier_points=COALESCE(frontier_points::int + $1, $1) WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2) RETURNING frontier_points", cost, s.charID).Scan(&balance)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(balance)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfGetFpointExchangeList(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetFpointExchangeList)
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(0)
|
||||
var buyables, sellables uint16
|
||||
var id uint32
|
||||
var itemType uint8
|
||||
var itemID, quantity, fPoints uint16
|
||||
|
||||
buyRows, err := s.server.db.Query("SELECT id,item_type,item_id,quantity,fpoints FROM fpoint_items WHERE trade_type=0")
|
||||
if err == nil {
|
||||
for buyRows.Next() {
|
||||
err = buyRows.Scan(&id, &itemType, &itemID, &quantity, &fPoints)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
resp.WriteUint32(id)
|
||||
resp.WriteUint16(0)
|
||||
resp.WriteUint16(0)
|
||||
resp.WriteUint16(0)
|
||||
resp.WriteUint8(itemType)
|
||||
resp.WriteUint16(itemID)
|
||||
resp.WriteUint16(quantity)
|
||||
resp.WriteUint16(fPoints)
|
||||
buyables++
|
||||
}
|
||||
}
|
||||
|
||||
sellRows, err := s.server.db.Query("SELECT id,item_type,item_id,quantity,fpoints FROM fpoint_items WHERE trade_type=1")
|
||||
if err == nil {
|
||||
for sellRows.Next() {
|
||||
err = sellRows.Scan(&id, &itemType, &itemID, &quantity, &fPoints)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
resp.WriteUint32(id)
|
||||
resp.WriteUint16(0)
|
||||
resp.WriteUint16(0)
|
||||
resp.WriteUint16(0)
|
||||
resp.WriteUint8(itemType)
|
||||
resp.WriteUint16(itemID)
|
||||
resp.WriteUint16(quantity)
|
||||
resp.WriteUint16(fPoints)
|
||||
sellables++
|
||||
}
|
||||
}
|
||||
resp.Seek(0, 0)
|
||||
resp.WriteUint16(buyables)
|
||||
resp.WriteUint16(sellables)
|
||||
|
||||
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
|
||||
}
|
||||
405
server/channelserver/handlers_stage.go
Normal file
405
server/channelserver/handlers_stage.go
Normal file
@@ -0,0 +1,405 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"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.host = s
|
||||
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.host = s
|
||||
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 = 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})
|
||||
|
||||
var temp mhfpacket.MHFPacket
|
||||
newNotif := byteframe.NewByteFrame()
|
||||
|
||||
// Cast existing user data to new user
|
||||
if !s.userEnteredStage {
|
||||
s.userEnteredStage = true
|
||||
|
||||
for _, session := range s.server.sessions {
|
||||
if s == session {
|
||||
continue
|
||||
}
|
||||
temp = &mhfpacket.MsgSysInsertUser{CharID: session.charID}
|
||||
newNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(newNotif, s.clientContext)
|
||||
for i := 0; i < 3; i++ {
|
||||
temp = &mhfpacket.MsgSysNotifyUserBinary{
|
||||
CharID: session.charID,
|
||||
BinaryType: uint8(i + 1),
|
||||
}
|
||||
newNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(newNotif, s.clientContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if s.stage != nil { // avoids lock up when using bed for dream quests
|
||||
// Notify the client to duplicate the existing objects.
|
||||
s.logger.Info(fmt.Sprintf("Sending existing stage objects to %s", s.Name))
|
||||
s.stage.RLock()
|
||||
var temp mhfpacket.MHFPacket
|
||||
for _, obj := range s.stage.objects {
|
||||
if obj.ownerCharID == s.charID {
|
||||
continue
|
||||
}
|
||||
temp = &mhfpacket.MsgSysDuplicateObject{
|
||||
ObjID: obj.id,
|
||||
X: obj.x,
|
||||
Y: obj.y,
|
||||
Z: obj.z,
|
||||
Unk0: 0,
|
||||
OwnerCharID: obj.ownerCharID,
|
||||
}
|
||||
newNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(newNotif, s.clientContext)
|
||||
}
|
||||
s.stage.RUnlock()
|
||||
}
|
||||
|
||||
newNotif.WriteUint16(0x0010) // End it.
|
||||
if len(newNotif.Data()) > 2 {
|
||||
s.QueueSend(newNotif.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" || stage.id[3:5] == "Ls" {
|
||||
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.
|
||||
delete(s.stage.clients, s)
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
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.stage.Lock()
|
||||
s.stage.reservedClientSlots[s.charID] = false
|
||||
s.stage.Unlock()
|
||||
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)
|
||||
}
|
||||
|
||||
if _, exists := s.stage.reservedClientSlots[s.charID]; exists {
|
||||
delete(s.stage.reservedClientSlots, s.charID)
|
||||
}
|
||||
|
||||
if _, exists := s.server.stages[backStage].reservedClientSlots[s.charID]; exists {
|
||||
delete(s.server.stages[backStage].reservedClientSlots, s.charID)
|
||||
}
|
||||
|
||||
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?
|
||||
// I think this is supposed to mark a stage as no longer able to accept client reservations
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgSysUnlockStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
if s.reservationStage != nil {
|
||||
s.reservationStage.RLock()
|
||||
defer s.reservationStage.RUnlock()
|
||||
|
||||
for charID := range s.reservationStage.reservedClientSlots {
|
||||
session := s.server.FindSessionByCharID(charID)
|
||||
session.QueueSendMHF(&mhfpacket.MsgSysStageDestruct{})
|
||||
}
|
||||
|
||||
delete(s.server.stages, s.reservationStage.id)
|
||||
}
|
||||
|
||||
destructEmptyStages(s)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if !strings.Contains(stage.id, pkt.StagePrefix) {
|
||||
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
|
||||
}
|
||||
65
server/channelserver/handlers_tactics.go
Normal file
65
server/channelserver/handlers_tactics.go
Normal file
File diff suppressed because one or more lines are too long
130
server/channelserver/handlers_tournament.go
Normal file
130
server/channelserver/handlers_tournament.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TournamentInfo0 struct {
|
||||
ID uint32
|
||||
MaxPlayers uint32
|
||||
CurrentPlayers uint32
|
||||
Unk1 uint16
|
||||
TextColor uint16
|
||||
Unk2 uint32
|
||||
Time1 time.Time
|
||||
Time2 time.Time
|
||||
Time3 time.Time
|
||||
Time4 time.Time
|
||||
Time5 time.Time
|
||||
Time6 time.Time
|
||||
Unk3 uint8
|
||||
Unk4 uint8
|
||||
MinHR uint32
|
||||
MaxHR uint32
|
||||
Unk5 string
|
||||
Unk6 string
|
||||
}
|
||||
|
||||
type TournamentInfo21 struct {
|
||||
Unk0 uint32
|
||||
Unk1 uint32
|
||||
Unk2 uint32
|
||||
Unk3 uint8
|
||||
}
|
||||
|
||||
type TournamentInfo22 struct {
|
||||
Unk0 uint32
|
||||
Unk1 uint32
|
||||
Unk2 uint32
|
||||
Unk3 uint8
|
||||
Unk4 string
|
||||
}
|
||||
|
||||
func handleMsgMhfInfoTournament(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfInfoTournament)
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
tournamentInfo0 := []TournamentInfo0{}
|
||||
tournamentInfo21 := []TournamentInfo21{}
|
||||
tournamentInfo22 := []TournamentInfo22{}
|
||||
|
||||
switch pkt.Unk0 {
|
||||
case 0:
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(uint32(len(tournamentInfo0)))
|
||||
for _, tinfo := range tournamentInfo0 {
|
||||
bf.WriteUint32(tinfo.ID)
|
||||
bf.WriteUint32(tinfo.MaxPlayers)
|
||||
bf.WriteUint32(tinfo.CurrentPlayers)
|
||||
bf.WriteUint16(tinfo.Unk1)
|
||||
bf.WriteUint16(tinfo.TextColor)
|
||||
bf.WriteUint32(tinfo.Unk2)
|
||||
bf.WriteUint32(uint32(tinfo.Time1.Unix()))
|
||||
bf.WriteUint32(uint32(tinfo.Time2.Unix()))
|
||||
bf.WriteUint32(uint32(tinfo.Time3.Unix()))
|
||||
bf.WriteUint32(uint32(tinfo.Time4.Unix()))
|
||||
bf.WriteUint32(uint32(tinfo.Time5.Unix()))
|
||||
bf.WriteUint32(uint32(tinfo.Time6.Unix()))
|
||||
bf.WriteUint8(tinfo.Unk3)
|
||||
bf.WriteUint8(tinfo.Unk4)
|
||||
bf.WriteUint32(tinfo.MinHR)
|
||||
bf.WriteUint32(tinfo.MaxHR)
|
||||
ps.Uint8(bf, tinfo.Unk5, true)
|
||||
ps.Uint16(bf, tinfo.Unk6, true)
|
||||
}
|
||||
case 1:
|
||||
bf.WriteUint32(uint32(TimeAdjusted().Unix()))
|
||||
bf.WriteUint32(0) // Registered ID
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint8(0)
|
||||
bf.WriteUint32(0)
|
||||
ps.Uint8(bf, "", true)
|
||||
case 2:
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(uint32(len(tournamentInfo21)))
|
||||
for _, info := range tournamentInfo21 {
|
||||
bf.WriteUint32(info.Unk0)
|
||||
bf.WriteUint32(info.Unk1)
|
||||
bf.WriteUint32(info.Unk2)
|
||||
bf.WriteUint8(info.Unk3)
|
||||
}
|
||||
bf.WriteUint32(uint32(len(tournamentInfo22)))
|
||||
for _, info := range tournamentInfo22 {
|
||||
bf.WriteUint32(info.Unk0)
|
||||
bf.WriteUint32(info.Unk1)
|
||||
bf.WriteUint32(info.Unk2)
|
||||
bf.WriteUint8(info.Unk3)
|
||||
ps.Uint8(bf, info.Unk4, true)
|
||||
}
|
||||
}
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfEntryTournament(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEntryTournament)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
type TournamentReward struct {
|
||||
Unk0 uint16
|
||||
Unk1 uint16
|
||||
Unk2 uint16
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireTournament(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAcquireTournament)
|
||||
rewards := []TournamentReward{}
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(uint8(len(rewards)))
|
||||
for _, reward := range rewards {
|
||||
bf.WriteUint16(reward.Unk0)
|
||||
bf.WriteUint16(reward.Unk1)
|
||||
bf.WriteUint16(reward.Unk2)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
477
server/channelserver/handlers_tower.go
Normal file
477
server/channelserver/handlers_tower.go
Normal file
@@ -0,0 +1,477 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/stringsupport"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
type TowerInfoTRP struct {
|
||||
TR int32
|
||||
TRP int32
|
||||
}
|
||||
|
||||
type TowerInfoSkill struct {
|
||||
TSP int32
|
||||
Unk1 []int16 // 40
|
||||
}
|
||||
|
||||
type TowerInfoHistory struct {
|
||||
Unk0 []int16 // 5
|
||||
Unk1 []int16 // 5
|
||||
}
|
||||
|
||||
type TowerInfoLevel struct {
|
||||
Floors int32
|
||||
Unk1 int32
|
||||
Unk2 int32
|
||||
Unk3 int32
|
||||
}
|
||||
|
||||
func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetTowerInfo)
|
||||
var data []*byteframe.ByteFrame
|
||||
type TowerInfo struct {
|
||||
TRP []TowerInfoTRP
|
||||
Skill []TowerInfoSkill
|
||||
History []TowerInfoHistory
|
||||
Level []TowerInfoLevel
|
||||
}
|
||||
|
||||
towerInfo := TowerInfo{
|
||||
TRP: []TowerInfoTRP{{0, 0}},
|
||||
Skill: []TowerInfoSkill{{0, make([]int16, 40)}},
|
||||
History: []TowerInfoHistory{{make([]int16, 5), make([]int16, 5)}},
|
||||
Level: []TowerInfoLevel{{0, 0, 0, 0}, {0, 0, 0, 0}},
|
||||
}
|
||||
|
||||
tempSkills := "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
|
||||
|
||||
err := s.server.db.QueryRow(`SELECT COALESCE(tr, 0), COALESCE(trp, 0), COALESCE(tsp, 0), COALESCE(block1, 0), COALESCE(block2, 0), skills FROM tower WHERE char_id=$1
|
||||
`, s.charID).Scan(&towerInfo.TRP[0].TR, &towerInfo.TRP[0].TRP, &towerInfo.Skill[0].TSP, &towerInfo.Level[0].Floors, &towerInfo.Level[1].Floors, &tempSkills)
|
||||
if err != nil {
|
||||
s.server.db.Exec(`INSERT INTO tower (char_id) VALUES ($1)`, s.charID)
|
||||
}
|
||||
|
||||
for i, skill := range stringsupport.CSVElems(tempSkills) {
|
||||
towerInfo.Skill[0].Unk1[i] = int16(skill)
|
||||
}
|
||||
|
||||
switch pkt.InfoType {
|
||||
case 1:
|
||||
for _, trp := range towerInfo.TRP {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteInt32(trp.TR)
|
||||
bf.WriteInt32(trp.TRP)
|
||||
data = append(data, bf)
|
||||
}
|
||||
case 2:
|
||||
for _, skills := range towerInfo.Skill {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteInt32(skills.TSP)
|
||||
for i := range skills.Unk1 {
|
||||
bf.WriteInt16(skills.Unk1[i])
|
||||
}
|
||||
data = append(data, bf)
|
||||
}
|
||||
case 4:
|
||||
for _, history := range towerInfo.History {
|
||||
bf := byteframe.NewByteFrame()
|
||||
for i := range history.Unk0 {
|
||||
bf.WriteInt16(history.Unk0[i])
|
||||
}
|
||||
for i := range history.Unk1 {
|
||||
bf.WriteInt16(history.Unk1[i])
|
||||
}
|
||||
data = append(data, bf)
|
||||
}
|
||||
case 5:
|
||||
for _, level := range towerInfo.Level {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteInt32(level.Floors)
|
||||
bf.WriteInt32(level.Unk1)
|
||||
bf.WriteInt32(level.Unk2)
|
||||
bf.WriteInt32(level.Unk3)
|
||||
data = append(data, bf)
|
||||
}
|
||||
}
|
||||
doAckEarthSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfPostTowerInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostTowerInfo)
|
||||
|
||||
if s.server.erupeConfig.DevModeOptions.QuestDebugTools {
|
||||
s.logger.Debug(
|
||||
p.Opcode().String(),
|
||||
zap.Uint32("InfoType", pkt.InfoType),
|
||||
zap.Uint32("Unk1", pkt.Unk1),
|
||||
zap.Int32("Skill", pkt.Skill),
|
||||
zap.Int32("TR", pkt.TR),
|
||||
zap.Int32("TRP", pkt.TRP),
|
||||
zap.Int32("Cost", pkt.Cost),
|
||||
zap.Int32("Unk6", pkt.Unk6),
|
||||
zap.Int32("Unk7", pkt.Unk7),
|
||||
zap.Int32("Block1", pkt.Block1),
|
||||
zap.Int64("Unk9", pkt.Unk9),
|
||||
)
|
||||
}
|
||||
|
||||
switch pkt.InfoType {
|
||||
case 2:
|
||||
skills := "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
|
||||
s.server.db.QueryRow(`SELECT skills FROM tower WHERE char_id=$1`, s.charID).Scan(&skills)
|
||||
s.server.db.Exec(`UPDATE tower SET skills=$1, tsp=tsp-$2 WHERE char_id=$3`, stringsupport.CSVSetIndex(skills, int(pkt.Skill), stringsupport.CSVGetIndex(skills, int(pkt.Skill))+1), pkt.Cost, s.charID)
|
||||
case 7:
|
||||
s.server.db.Exec(`UPDATE tower SET tr=$1, trp=trp+$2, block1=block1+$3 WHERE char_id=$4`, pkt.TR, pkt.TRP, pkt.Block1, s.charID)
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
// Default missions
|
||||
var tenrouiraiData = []TenrouiraiData{
|
||||
{1, 1, 80, 0, 2, 2, 1, 1, 2, 2},
|
||||
{1, 4, 16, 0, 2, 2, 1, 1, 2, 2},
|
||||
{1, 6, 50, 0, 2, 2, 1, 0, 2, 2},
|
||||
{1, 4, 12, 50, 2, 2, 1, 1, 2, 2},
|
||||
{1, 3, 50, 0, 2, 2, 1, 1, 2, 2},
|
||||
{2, 5, 40000, 0, 2, 2, 1, 0, 2, 2},
|
||||
{1, 5, 50000, 50, 2, 2, 1, 1, 2, 2},
|
||||
{2, 1, 60, 0, 2, 2, 1, 1, 2, 2},
|
||||
{2, 3, 50, 0, 2, 1, 1, 0, 1, 2},
|
||||
{2, 3, 40, 50, 2, 1, 1, 1, 1, 2},
|
||||
{2, 4, 12, 0, 2, 1, 1, 1, 1, 2},
|
||||
{2, 6, 40, 0, 2, 1, 1, 0, 1, 2},
|
||||
{1, 1, 60, 50, 2, 1, 2, 1, 1, 2},
|
||||
{1, 5, 50000, 0, 3, 1, 2, 1, 1, 2},
|
||||
{1, 6, 50, 0, 3, 1, 2, 0, 1, 2},
|
||||
{1, 4, 16, 50, 3, 1, 2, 1, 1, 2},
|
||||
{1, 5, 50000, 0, 3, 1, 2, 1, 1, 2},
|
||||
{2, 3, 40, 0, 3, 1, 2, 0, 1, 2},
|
||||
{1, 3, 50, 50, 3, 1, 2, 1, 1, 2},
|
||||
{2, 5, 40000, 0, 3, 1, 2, 1, 1, 1},
|
||||
{2, 6, 40, 0, 3, 1, 2, 0, 1, 1},
|
||||
{2, 1, 60, 50, 3, 1, 2, 1, 1, 1},
|
||||
{2, 6, 50, 0, 3, 1, 2, 1, 1, 1},
|
||||
{2, 4, 12, 0, 3, 1, 2, 0, 1, 1},
|
||||
{1, 1, 80, 50, 3, 1, 2, 1, 1, 1},
|
||||
{1, 5, 40000, 0, 3, 1, 2, 1, 1, 1},
|
||||
{1, 3, 50, 0, 3, 1, 2, 0, 1, 1},
|
||||
{1, 4, 16, 50, 3, 1, 0, 1, 1, 1},
|
||||
{1, 6, 50, 0, 3, 1, 0, 1, 1, 1},
|
||||
{2, 3, 40, 0, 3, 1, 0, 1, 1, 1},
|
||||
{1, 1, 80, 50, 3, 1, 0, 0, 1, 1},
|
||||
{2, 5, 40000, 0, 3, 1, 0, 0, 1, 1},
|
||||
{2, 6, 40, 0, 3, 1, 0, 0, 1, 1},
|
||||
}
|
||||
|
||||
type TenrouiraiProgress struct {
|
||||
Page uint8
|
||||
Mission1 uint16
|
||||
Mission2 uint16
|
||||
Mission3 uint16
|
||||
}
|
||||
|
||||
type TenrouiraiReward struct {
|
||||
Index uint8
|
||||
Item []uint16 // 5
|
||||
Quantity []uint8 // 5
|
||||
}
|
||||
|
||||
type TenrouiraiKeyScore struct {
|
||||
Unk0 uint8
|
||||
Unk1 int32
|
||||
}
|
||||
|
||||
type TenrouiraiData struct {
|
||||
Block uint8
|
||||
Mission uint8
|
||||
// 1 = Floors climbed
|
||||
// 2 = Collect antiques
|
||||
// 3 = Open chests
|
||||
// 4 = Cats saved
|
||||
// 5 = TRP acquisition
|
||||
// 6 = Monster slays
|
||||
Goal uint16
|
||||
Cost uint16
|
||||
Skill1 uint8 // 80
|
||||
Skill2 uint8 // 40
|
||||
Skill3 uint8 // 40
|
||||
Skill4 uint8 // 20
|
||||
Skill5 uint8 // 40
|
||||
Skill6 uint8 // 50
|
||||
}
|
||||
|
||||
type TenrouiraiCharScore struct {
|
||||
Score int32
|
||||
Name string
|
||||
}
|
||||
|
||||
type TenrouiraiTicket struct {
|
||||
Unk0 uint8
|
||||
RP uint32
|
||||
Unk2 uint32
|
||||
}
|
||||
|
||||
type Tenrouirai struct {
|
||||
Progress []TenrouiraiProgress
|
||||
Reward []TenrouiraiReward
|
||||
KeyScore []TenrouiraiKeyScore
|
||||
Data []TenrouiraiData
|
||||
CharScore []TenrouiraiCharScore
|
||||
Ticket []TenrouiraiTicket
|
||||
}
|
||||
|
||||
func handleMsgMhfGetTenrouirai(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetTenrouirai)
|
||||
var data []*byteframe.ByteFrame
|
||||
|
||||
tenrouirai := Tenrouirai{
|
||||
Progress: []TenrouiraiProgress{{1, 0, 0, 0}},
|
||||
Data: tenrouiraiData,
|
||||
Ticket: []TenrouiraiTicket{{0, 0, 0}},
|
||||
}
|
||||
|
||||
switch pkt.Unk1 {
|
||||
case 1:
|
||||
for _, tdata := range tenrouirai.Data {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(tdata.Block)
|
||||
bf.WriteUint8(tdata.Mission)
|
||||
bf.WriteUint16(tdata.Goal)
|
||||
bf.WriteUint16(tdata.Cost)
|
||||
bf.WriteUint8(tdata.Skill1)
|
||||
bf.WriteUint8(tdata.Skill2)
|
||||
bf.WriteUint8(tdata.Skill3)
|
||||
bf.WriteUint8(tdata.Skill4)
|
||||
bf.WriteUint8(tdata.Skill5)
|
||||
bf.WriteUint8(tdata.Skill6)
|
||||
data = append(data, bf)
|
||||
}
|
||||
case 2:
|
||||
for _, reward := range tenrouirai.Reward {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(reward.Index)
|
||||
bf.WriteUint16(reward.Item[0])
|
||||
bf.WriteUint16(reward.Item[1])
|
||||
bf.WriteUint16(reward.Item[2])
|
||||
bf.WriteUint16(reward.Item[3])
|
||||
bf.WriteUint16(reward.Item[4])
|
||||
bf.WriteUint8(reward.Quantity[0])
|
||||
bf.WriteUint8(reward.Quantity[1])
|
||||
bf.WriteUint8(reward.Quantity[2])
|
||||
bf.WriteUint8(reward.Quantity[3])
|
||||
bf.WriteUint8(reward.Quantity[4])
|
||||
data = append(data, bf)
|
||||
}
|
||||
case 4:
|
||||
s.server.db.QueryRow(`SELECT tower_mission_page FROM guilds WHERE id=$1`, pkt.GuildID).Scan(&tenrouirai.Progress[0].Page)
|
||||
s.server.db.QueryRow(`SELECT SUM(tower_mission_1) AS _, SUM(tower_mission_2) AS _, SUM(tower_mission_3) AS _ FROM guild_characters WHERE guild_id=$1
|
||||
`, pkt.GuildID).Scan(&tenrouirai.Progress[0].Mission1, &tenrouirai.Progress[0].Mission2, &tenrouirai.Progress[0].Mission3)
|
||||
|
||||
if tenrouirai.Progress[0].Mission1 > tenrouiraiData[(tenrouirai.Progress[0].Page*3)-3].Goal {
|
||||
tenrouirai.Progress[0].Mission1 = tenrouiraiData[(tenrouirai.Progress[0].Page*3)-3].Goal
|
||||
}
|
||||
if tenrouirai.Progress[0].Mission2 > tenrouiraiData[(tenrouirai.Progress[0].Page*3)-2].Goal {
|
||||
tenrouirai.Progress[0].Mission2 = tenrouiraiData[(tenrouirai.Progress[0].Page*3)-2].Goal
|
||||
}
|
||||
if tenrouirai.Progress[0].Mission3 > tenrouiraiData[(tenrouirai.Progress[0].Page*3)-1].Goal {
|
||||
tenrouirai.Progress[0].Mission3 = tenrouiraiData[(tenrouirai.Progress[0].Page*3)-1].Goal
|
||||
}
|
||||
|
||||
for _, progress := range tenrouirai.Progress {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(progress.Page)
|
||||
bf.WriteUint16(progress.Mission1)
|
||||
bf.WriteUint16(progress.Mission2)
|
||||
bf.WriteUint16(progress.Mission3)
|
||||
data = append(data, bf)
|
||||
}
|
||||
case 5:
|
||||
if pkt.Unk3 > 3 {
|
||||
pkt.Unk3 %= 3
|
||||
if pkt.Unk3 == 0 {
|
||||
pkt.Unk3 = 3
|
||||
}
|
||||
}
|
||||
rows, _ := s.server.db.Query(fmt.Sprintf(`SELECT name, tower_mission_%d FROM guild_characters gc INNER JOIN characters c ON gc.character_id = c.id WHERE guild_id=$1 AND tower_mission_%d IS NOT NULL ORDER BY tower_mission_%d DESC`, pkt.Unk3, pkt.Unk3, pkt.Unk3), pkt.GuildID)
|
||||
for rows.Next() {
|
||||
temp := TenrouiraiCharScore{}
|
||||
rows.Scan(&temp.Name, &temp.Score)
|
||||
tenrouirai.CharScore = append(tenrouirai.CharScore, temp)
|
||||
}
|
||||
for _, charScore := range tenrouirai.CharScore {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteInt32(charScore.Score)
|
||||
bf.WriteBytes(stringsupport.PaddedString(charScore.Name, 14, true))
|
||||
data = append(data, bf)
|
||||
}
|
||||
case 6:
|
||||
s.server.db.QueryRow(`SELECT tower_rp FROM guilds WHERE id=$1`, pkt.GuildID).Scan(&tenrouirai.Ticket[0].RP)
|
||||
for _, ticket := range tenrouirai.Ticket {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(ticket.Unk0)
|
||||
bf.WriteUint32(ticket.RP)
|
||||
bf.WriteUint32(ticket.Unk2)
|
||||
data = append(data, bf)
|
||||
}
|
||||
}
|
||||
|
||||
doAckEarthSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfPostTenrouirai(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostTenrouirai)
|
||||
|
||||
if s.server.erupeConfig.DevModeOptions.QuestDebugTools {
|
||||
s.logger.Debug(
|
||||
p.Opcode().String(),
|
||||
zap.Uint8("Unk0", pkt.Unk0),
|
||||
zap.Uint8("Op", pkt.Op),
|
||||
zap.Uint32("GuildID", pkt.GuildID),
|
||||
zap.Uint8("Unk1", pkt.Unk1),
|
||||
zap.Uint16("Floors", pkt.Floors),
|
||||
zap.Uint16("Antiques", pkt.Antiques),
|
||||
zap.Uint16("Chests", pkt.Chests),
|
||||
zap.Uint16("Cats", pkt.Cats),
|
||||
zap.Uint16("TRP", pkt.TRP),
|
||||
zap.Uint16("Slays", pkt.Slays),
|
||||
)
|
||||
}
|
||||
|
||||
if pkt.Op == 2 {
|
||||
var page, requirement, donated int
|
||||
s.server.db.QueryRow(`SELECT tower_mission_page, tower_rp FROM guilds WHERE id=$1`, pkt.GuildID).Scan(&page, &donated)
|
||||
|
||||
for i := 0; i < (page*3)+1; i++ {
|
||||
requirement += int(tenrouiraiData[i].Cost)
|
||||
}
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
sd, err := GetCharacterSaveData(s, s.charID)
|
||||
if err == nil && sd != nil {
|
||||
sd.RP -= pkt.DonatedRP
|
||||
sd.Save(s)
|
||||
if donated+int(pkt.DonatedRP) >= requirement {
|
||||
s.server.db.Exec(`UPDATE guilds SET tower_mission_page=tower_mission_page+1 WHERE id=$1`, pkt.GuildID)
|
||||
s.server.db.Exec(`UPDATE guild_characters SET tower_mission_1=NULL, tower_mission_2=NULL, tower_mission_3=NULL WHERE guild_id=$1`, pkt.GuildID)
|
||||
pkt.DonatedRP = uint16(requirement - donated)
|
||||
}
|
||||
bf.WriteUint32(uint32(pkt.DonatedRP))
|
||||
s.server.db.Exec(`UPDATE guilds SET tower_rp=tower_rp+$1 WHERE id=$2`, pkt.DonatedRP, pkt.GuildID)
|
||||
} else {
|
||||
bf.WriteUint32(0)
|
||||
}
|
||||
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
} else {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfPresentBox(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPresentBox)
|
||||
var data []*byteframe.ByteFrame
|
||||
/*
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
*/
|
||||
doAckEarthSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
type GemInfo struct {
|
||||
Gem uint16
|
||||
Quantity uint16
|
||||
}
|
||||
|
||||
type GemHistory struct {
|
||||
Gem uint16
|
||||
Message uint16
|
||||
Timestamp time.Time
|
||||
Sender string
|
||||
}
|
||||
|
||||
func handleMsgMhfGetGemInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGemInfo)
|
||||
var data []*byteframe.ByteFrame
|
||||
gemInfo := []GemInfo{}
|
||||
gemHistory := []GemHistory{}
|
||||
|
||||
tempGems := "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
|
||||
s.server.db.QueryRow(`SELECT gems FROM tower WHERE char_id=$1`, s.charID).Scan(&tempGems)
|
||||
for i, v := range stringsupport.CSVElems(tempGems) {
|
||||
gemInfo = append(gemInfo, GemInfo{uint16(((i / 5) * 256) + ((i % 5) + 1)), uint16(v)})
|
||||
}
|
||||
|
||||
switch pkt.Unk0 {
|
||||
case 1:
|
||||
for _, info := range gemInfo {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(info.Gem)
|
||||
bf.WriteUint16(info.Quantity)
|
||||
data = append(data, bf)
|
||||
}
|
||||
case 2:
|
||||
for _, history := range gemHistory {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(history.Gem)
|
||||
bf.WriteUint16(history.Message)
|
||||
bf.WriteUint32(uint32(history.Timestamp.Unix()))
|
||||
bf.WriteBytes(stringsupport.PaddedString(history.Sender, 14, true))
|
||||
data = append(data, bf)
|
||||
}
|
||||
}
|
||||
doAckEarthSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfPostGemInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostGemInfo)
|
||||
|
||||
if s.server.erupeConfig.DevModeOptions.QuestDebugTools {
|
||||
s.logger.Debug(
|
||||
p.Opcode().String(),
|
||||
zap.Uint32("Op", pkt.Op),
|
||||
zap.Uint32("Unk1", pkt.Unk1),
|
||||
zap.Int32("Gem", pkt.Gem),
|
||||
zap.Int32("Quantity", pkt.Quantity),
|
||||
zap.Int32("CID", pkt.CID),
|
||||
zap.Int32("Message", pkt.Message),
|
||||
zap.Int32("Unk6", pkt.Unk6),
|
||||
)
|
||||
}
|
||||
|
||||
gems := "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
|
||||
s.server.db.QueryRow(`SELECT gems FROM tower WHERE char_id=$1`, s.charID).Scan(&gems)
|
||||
switch pkt.Op {
|
||||
case 1: // Add gem
|
||||
i := int(((pkt.Gem / 256) * 5) + (((pkt.Gem - ((pkt.Gem / 256) * 256)) - 1) % 5))
|
||||
s.server.db.Exec(`UPDATE tower SET gems=$1 WHERE char_id=$2`, stringsupport.CSVSetIndex(gems, i, stringsupport.CSVGetIndex(gems, i)+int(pkt.Quantity)), s.charID)
|
||||
case 2: // Transfer gem
|
||||
// no way im doing this for now
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfGetNotice(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetNotice)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfPostNotice(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostNotice)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
56
server/channelserver/handlers_users.go
Normal file
56
server/channelserver/handlers_users.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
func handleMsgSysInsertUser(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysDeleteUser(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
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()
|
||||
|
||||
var exists []byte
|
||||
err := s.server.db.QueryRow("SELECT type2 FROM user_binary WHERE id=$1", s.charID).Scan(&exists)
|
||||
if err != nil {
|
||||
s.server.db.Exec("INSERT INTO user_binary (id) VALUES ($1)", s.charID)
|
||||
}
|
||||
|
||||
s.server.db.Exec(fmt.Sprintf("UPDATE user_binary SET type%d=$1 WHERE id=$2", pkt.BinaryType), pkt.RawDataPayload, s.charID)
|
||||
|
||||
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}]
|
||||
|
||||
// If we can't get the real data, try to get it from the database.
|
||||
if !ok {
|
||||
err := s.server.db.QueryRow(fmt.Sprintf("SELECT type%d FROM user_binary WHERE id=$1", pkt.BinaryType), pkt.CharID).Scan(&data)
|
||||
if err != nil {
|
||||
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgSysNotifyUserBinary(s *Session, p mhfpacket.MHFPacket) {}
|
||||
424
server/channelserver/sys_channel_server.go
Normal file
424
server/channelserver/sys_channel_server.go
Normal file
@@ -0,0 +1,424 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"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"
|
||||
)
|
||||
|
||||
// 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
|
||||
GlobalID string
|
||||
IP string
|
||||
Port uint16
|
||||
logger *zap.Logger
|
||||
db *sqlx.DB
|
||||
erupeConfig *_config.Config
|
||||
acceptConns chan net.Conn
|
||||
deleteConns chan net.Conn
|
||||
sessions map[net.Conn]*Session
|
||||
objectIDs map[*Session]uint16
|
||||
listener net.Listener // Listener that is created when Server.Start is called.
|
||||
isShuttingDown bool
|
||||
|
||||
stagesLock sync.RWMutex
|
||||
stages map[string]*Stage
|
||||
|
||||
// Used to map different languages
|
||||
dict map[string]string
|
||||
|
||||
// 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
|
||||
|
||||
raviente *Raviente
|
||||
}
|
||||
|
||||
type Raviente struct {
|
||||
sync.Mutex
|
||||
id uint16
|
||||
register []uint32
|
||||
state []uint32
|
||||
support []uint32
|
||||
}
|
||||
|
||||
func (s *Server) resetRaviente() {
|
||||
for _, semaphore := range s.semaphore {
|
||||
if strings.HasPrefix(semaphore.name, "hs_l0") {
|
||||
return
|
||||
}
|
||||
}
|
||||
s.logger.Debug("All Raviente Semaphores empty, resetting")
|
||||
s.raviente.id = s.raviente.id + 1
|
||||
s.raviente.register = make([]uint32, 30)
|
||||
s.raviente.state = make([]uint32, 30)
|
||||
s.raviente.support = make([]uint32, 30)
|
||||
}
|
||||
|
||||
func (s *Server) GetRaviMultiplier() float64 {
|
||||
raviSema := s.getRaviSemaphore()
|
||||
if raviSema != nil {
|
||||
var minPlayers int
|
||||
if s.raviente.register[9] > 8 {
|
||||
minPlayers = 24
|
||||
} else {
|
||||
minPlayers = 4
|
||||
}
|
||||
if len(raviSema.clients) > minPlayers {
|
||||
return 1
|
||||
}
|
||||
return float64(minPlayers / len(raviSema.clients))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *Server) UpdateRavi(semaID uint32, index uint8, value uint32, update bool) (uint32, uint32) {
|
||||
var prev uint32
|
||||
switch semaID {
|
||||
case 0x40000:
|
||||
switch index {
|
||||
case 17, 28: // Ignore res and poison
|
||||
break
|
||||
default:
|
||||
value = uint32(float64(value) * s.GetRaviMultiplier())
|
||||
}
|
||||
prev = s.raviente.state[index]
|
||||
if prev != 0 && !update {
|
||||
return prev, prev
|
||||
}
|
||||
s.raviente.state[index] += value
|
||||
return prev, s.raviente.state[index]
|
||||
case 0x50000:
|
||||
prev = s.raviente.support[index]
|
||||
if prev != 0 && !update {
|
||||
return prev, prev
|
||||
}
|
||||
s.raviente.support[index] += value
|
||||
return prev, s.raviente.support[index]
|
||||
case 0x60000:
|
||||
prev = s.raviente.register[index]
|
||||
if prev != 0 && !update {
|
||||
return prev, prev
|
||||
}
|
||||
s.raviente.register[index] += value
|
||||
return prev, s.raviente.register[index]
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// 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),
|
||||
objectIDs: make(map[*Session]uint16),
|
||||
stages: make(map[string]*Stage),
|
||||
userBinaryParts: make(map[userBinaryPartID][]byte),
|
||||
semaphore: make(map[string]*Semaphore),
|
||||
semaphoreIndex: 7,
|
||||
discordBot: config.DiscordBot,
|
||||
name: config.Name,
|
||||
raviente: &Raviente{
|
||||
id: 1,
|
||||
register: make([]uint32, 30),
|
||||
state: make([]uint32, 30),
|
||||
support: make([]uint32, 30),
|
||||
},
|
||||
}
|
||||
|
||||
// Mezeporta
|
||||
s.stages["sl1Ns200p0a0u0"] = NewStage("sl1Ns200p0a0u0")
|
||||
|
||||
// Rasta bar stage
|
||||
s.stages["sl1Ns211p0a0u0"] = NewStage("sl1Ns211p0a0u0")
|
||||
|
||||
// Pallone Carvan
|
||||
s.stages["sl1Ns260p0a0u0"] = NewStage("sl1Ns260p0a0u0")
|
||||
|
||||
// Pallone Guest House 1st Floor
|
||||
s.stages["sl1Ns262p0a0u0"] = NewStage("sl1Ns262p0a0u0")
|
||||
|
||||
// Pallone Guest House 2nd Floor
|
||||
s.stages["sl1Ns263p0a0u0"] = NewStage("sl1Ns263p0a0u0")
|
||||
|
||||
// Diva fountain / prayer fountain.
|
||||
s.stages["sl2Ns379p0a0u0"] = NewStage("sl2Ns379p0a0u0")
|
||||
|
||||
// MezFes
|
||||
s.stages["sl1Ns462p0a0u0"] = NewStage("sl1Ns462p0a0u0")
|
||||
|
||||
s.dict = getLangStrings(s)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Start starts the server in a new goroutine.
|
||||
func (s *Server) Start() error {
|
||||
l, err := net.Listen("tcp", fmt.Sprintf(":%d", s.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.
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
for _, session := range s.sessions {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) WorldcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session, ignoredChannel *Server) {
|
||||
for _, c := range s.Channels {
|
||||
if c == ignoredChannel {
|
||||
continue
|
||||
}
|
||||
c.BroadcastMHF(pkt, ignoredSession)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 = s.dict["ravienteBerserk"]
|
||||
case 3:
|
||||
text = s.dict["ravienteExtreme"]
|
||||
case 4:
|
||||
text = s.dict["ravienteExtremeLimited"]
|
||||
case 5:
|
||||
text = s.dict["ravienteBerserkSmall"]
|
||||
default:
|
||||
s.logger.Error("Unk raviente type", zap.Uint8("_type", _type))
|
||||
}
|
||||
ps.Uint16(bf, text, true)
|
||||
bf.WriteBytes([]byte{0x5F, 0x53, 0x00})
|
||||
bf.WriteUint32(ip) // IP address
|
||||
bf.WriteUint16(port) // Port
|
||||
bf.WriteUint16(0) // Unk
|
||||
bf.WriteBytes(stage)
|
||||
s.WorldcastMHF(&mhfpacket.MsgSysCastedBinary{
|
||||
CharID: 0x00000000,
|
||||
BroadcastType: BroadcastTypeServer,
|
||||
MessageType: BinaryMessageTypeChat,
|
||||
RawDataPayload: bf.Data(),
|
||||
}, nil, s)
|
||||
}
|
||||
|
||||
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 {
|
||||
for _, session := range c.sessions {
|
||||
if session.charID == charID {
|
||||
return session
|
||||
}
|
||||
}
|
||||
}
|
||||
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 {
|
||||
for {
|
||||
exists := false
|
||||
s.semaphoreIndex = s.semaphoreIndex + 1
|
||||
if s.semaphoreIndex > 0xFFFF {
|
||||
s.semaphoreIndex = 1
|
||||
}
|
||||
for _, semaphore := range s.semaphore {
|
||||
if semaphore.id == s.semaphoreIndex {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
break
|
||||
}
|
||||
}
|
||||
return s.semaphoreIndex
|
||||
}
|
||||
|
||||
func (s *Server) Season() uint8 {
|
||||
sid := int64(((s.ID & 0xFF00) - 4096) / 256)
|
||||
return uint8(((TimeAdjusted().Unix() / 86400) + sid) % 3)
|
||||
}
|
||||
112
server/channelserver/sys_language.go
Normal file
112
server/channelserver/sys_language.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package channelserver
|
||||
|
||||
func getLangStrings(s *Server) map[string]string {
|
||||
strings := make(map[string]string)
|
||||
switch s.erupeConfig.Language {
|
||||
case "jp":
|
||||
strings["language"] = "日本語"
|
||||
strings["cafeReset"] = "%d/%dにリセット"
|
||||
|
||||
strings["commandDisabled"] = "%sのコマンドは無効です"
|
||||
strings["commandReload"] = "リロードします"
|
||||
strings["commandKqfGet"] = "現在のキークエストフラグ:%x"
|
||||
strings["commandKqfSetError"] = "キークエコマンドエラー 例:%s set xxxxxxxxxxxxxxxx"
|
||||
strings["commandKqfSetSuccess"] = "キークエストのフラグが更新されました。ワールド/ランドを移動してください"
|
||||
strings["commandRightsError"] = "コース更新コマンドエラー 例:%s x"
|
||||
strings["commandRightsSuccess"] = "コース情報を更新しました:%d"
|
||||
strings["commandCourseError"] = "コース確認コマンドエラー 例:%s <name>"
|
||||
strings["commandCourseDisabled"] = "%sコースは無効です"
|
||||
strings["commandCourseEnabled"] = "%sコースは有効です"
|
||||
strings["commandCourseLocked"] = "%sコースはロックされています"
|
||||
strings["commandTeleportError"] = "テレポートコマンドエラー 構文:%s x y"
|
||||
strings["commandTeleportSuccess"] = "%d %dにテレポート"
|
||||
strings["commandPSNError"] = "PSN連携コマンドエラー 例:%s <psn id>"
|
||||
strings["commandPSNSuccess"] = "PSN「%s」が連携されています"
|
||||
strings["commandPSNExists"] = "PSNは既存のユーザに接続されています"
|
||||
|
||||
strings["commandRaviNoCommand"] = "ラヴィコマンドが指定されていません"
|
||||
strings["commandRaviStartSuccess"] = "大討伐を開始します"
|
||||
strings["commandRaviStartError"] = "大討伐は既に開催されています"
|
||||
strings["commandRaviMultiplier"] = "ラヴィダメージ倍率:x%.2f"
|
||||
strings["commandRaviResSuccess"] = "復活支援を実行します"
|
||||
strings["commandRaviResError"] = "復活支援は実行されませんでした"
|
||||
strings["commandRaviSedSuccess"] = "鎮静支援を実行します"
|
||||
strings["commandRaviRequest"] = "鎮静支援を要請します"
|
||||
strings["commandRaviError"] = "ラヴィコマンドが認識されません"
|
||||
strings["commandRaviNoPlayers"] = "誰も大討伐に参加していません"
|
||||
strings["commandRaviVersion"] = "This command is disabled outside of MHFZZ"
|
||||
|
||||
strings["ravienteBerserk"] = "<大討伐:猛狂期>が開催されました!"
|
||||
strings["ravienteExtreme"] = "<大討伐:猛狂期【極】>が開催されました!"
|
||||
strings["ravienteExtremeLimited"] = "<大討伐:猛狂期【極】(制限付)>が開催されました!"
|
||||
strings["ravienteBerserkSmall"] = "<大討伐:猛狂期(小数)>が開催されました!"
|
||||
|
||||
strings["guildInviteName"] = "猟団勧誘のご案内"
|
||||
strings["guildInvite"] = "猟団「%s」からの勧誘通知です。\n「勧誘に返答」より、返答を行ってください。"
|
||||
|
||||
strings["guildInviteSuccessName"] = "成功"
|
||||
strings["guildInviteSuccess"] = "あなたは「%s」に参加できました。"
|
||||
|
||||
strings["guildInviteAcceptedName"] = "承諾されました"
|
||||
strings["guildInviteAccepted"] = "招待した狩人が「%s」への招待を承諾しました。"
|
||||
|
||||
strings["guildInviteRejectName"] = "却下しました"
|
||||
strings["guildInviteReject"] = "あなたは「%s」への参加を却下しました。"
|
||||
|
||||
strings["guildInviteDeclinedName"] = "辞退しました"
|
||||
strings["guildInviteDeclined"] = "招待した狩人が「%s」への招待を辞退しました。"
|
||||
default:
|
||||
strings["language"] = "English"
|
||||
strings["cafeReset"] = "Resets on %d/%d"
|
||||
|
||||
strings["commandDisabled"] = "%s command is disabled"
|
||||
strings["commandReload"] = "Reloading players..."
|
||||
strings["commandKqfGet"] = "KQF: %x"
|
||||
strings["commandKqfSetError"] = "Error in command. Format: %s set xxxxxxxxxxxxxxxx"
|
||||
strings["commandKqfSetSuccess"] = "KQF set, please switch Land/World"
|
||||
strings["commandRightsError"] = "Error in command. Format: %s x"
|
||||
strings["commandRightsSuccess"] = "Set rights integer: %d"
|
||||
strings["commandCourseError"] = "Error in command. Format: %s <name>"
|
||||
strings["commandCourseDisabled"] = "%s Course disabled"
|
||||
strings["commandCourseEnabled"] = "%s Course enabled"
|
||||
strings["commandCourseLocked"] = "%s Course is locked"
|
||||
strings["commandTeleportError"] = "Error in command. Format: %s x y"
|
||||
strings["commandTeleportSuccess"] = "Teleporting to %d %d"
|
||||
strings["commandPSNError"] = "Error in command. Format: %s <psn id>"
|
||||
strings["commandPSNSuccess"] = "Connected PSN ID: %s"
|
||||
strings["commandPSNExists"] = "PSN ID is connected to another account!"
|
||||
|
||||
strings["commandRaviNoCommand"] = "No Raviente command specified!"
|
||||
strings["commandRaviStartSuccess"] = "The Great Slaying will begin in a moment"
|
||||
strings["commandRaviStartError"] = "The Great Slaying has already begun!"
|
||||
strings["commandRaviMultiplier"] = "Raviente multiplier is currently %.2fx"
|
||||
strings["commandRaviResSuccess"] = "Sending resurrection support!"
|
||||
strings["commandRaviResError"] = "Resurrection support has not been requested!"
|
||||
strings["commandRaviSedSuccess"] = "Sending sedation support if requested!"
|
||||
strings["commandRaviRequest"] = "Requesting sedation support!"
|
||||
strings["commandRaviError"] = "Raviente command not recognised!"
|
||||
strings["commandRaviNoPlayers"] = "No one has joined the Great Slaying!"
|
||||
strings["commandRaviVersion"] = "This command is disabled outside of MHFZZ"
|
||||
|
||||
strings["ravienteBerserk"] = "<Great Slaying: Berserk> is being held!"
|
||||
strings["ravienteExtreme"] = "<Great Slaying: Extreme> is being held!"
|
||||
strings["ravienteExtremeLimited"] = "<Great Slaying: Extreme (Limited)> is being held!"
|
||||
strings["ravienteBerserkSmall"] = "<Great Slaying: Berserk (Small)> is being held!"
|
||||
|
||||
strings["guildInviteName"] = "Invitation!"
|
||||
strings["guildInvite"] = "You have been invited to join\n「%s」\nDo you want to accept?"
|
||||
|
||||
strings["guildInviteSuccessName"] = "Success!"
|
||||
strings["guildInviteSuccess"] = "You have successfully joined\n「%s」."
|
||||
|
||||
strings["guildInviteAcceptedName"] = "Accepted"
|
||||
strings["guildInviteAccepted"] = "The recipient accepted your invitation to join\n「%s」."
|
||||
|
||||
strings["guildInviteRejectName"] = "Rejected"
|
||||
strings["guildInviteReject"] = "You rejected the invitation to join\n「%s」."
|
||||
|
||||
strings["guildInviteDeclinedName"] = "Declined"
|
||||
strings["guildInviteDeclined"] = "The recipient declined your invitation to join\n「%s」."
|
||||
}
|
||||
return strings
|
||||
}
|
||||
56
server/channelserver/sys_semaphore.go
Normal file
56
server/channelserver/sys_semaphore.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Semaphore holds Semaphore-specific information
|
||||
type Semaphore struct {
|
||||
sync.RWMutex
|
||||
|
||||
// Semaphore ID string
|
||||
name string
|
||||
|
||||
id uint32
|
||||
|
||||
// Map of session -> charID.
|
||||
// These are clients that are registered to the Semaphore
|
||||
clients map[*Session]uint32
|
||||
|
||||
// Max Players for Semaphore
|
||||
maxPlayers uint16
|
||||
}
|
||||
|
||||
// NewSemaphore creates a new Semaphore with intialized values
|
||||
func NewSemaphore(s *Server, ID string, MaxPlayers uint16) *Semaphore {
|
||||
sema := &Semaphore{
|
||||
name: ID,
|
||||
id: s.NextSemaphoreID(),
|
||||
clients: make(map[*Session]uint32),
|
||||
maxPlayers: MaxPlayers,
|
||||
}
|
||||
return sema
|
||||
}
|
||||
|
||||
// BroadcastMHF queues a MHFPacket to be sent to all sessions in the Semaphore
|
||||
func (s *Semaphore) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) {
|
||||
// Broadcast the data.
|
||||
for session := range s.clients {
|
||||
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())
|
||||
}
|
||||
}
|
||||
298
server/channelserver/sys_session.go
Normal file
298
server/channelserver/sys_session.go
Normal file
@@ -0,0 +1,298 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"erupe-ce/common/mhfcourse"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/stringstack"
|
||||
"erupe-ce/network"
|
||||
"erupe-ce/network/clientctx"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type packet struct {
|
||||
data []byte
|
||||
nonBlocking bool
|
||||
}
|
||||
|
||||
// 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 packet
|
||||
clientContext *clientctx.ClientContext
|
||||
lastPacket time.Time
|
||||
|
||||
objectIndex uint16
|
||||
userEnteredStage bool // If the user has entered a stage before
|
||||
stageID string
|
||||
stage *Stage
|
||||
reservationStage *Stage // Required for the stateful MsgSysUnreserveStage packet.
|
||||
stagePass string // Temporary storage
|
||||
prevGuildID uint32 // Stores the last GuildID used in InfoGuild
|
||||
charID uint32
|
||||
logKey []byte
|
||||
sessionStart int64
|
||||
courses []mhfcourse.Course
|
||||
token string
|
||||
kqf []byte
|
||||
kqfOverride bool
|
||||
|
||||
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
|
||||
closed bool
|
||||
}
|
||||
|
||||
// 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 packet, 20),
|
||||
clientContext: &clientctx.ClientContext{}, // Unused
|
||||
lastPacket: time.Now(),
|
||||
sessionStart: TimeAdjusted().Unix(),
|
||||
stageMoveStack: stringstack.New(),
|
||||
}
|
||||
s.SetObjectID()
|
||||
return s
|
||||
}
|
||||
|
||||
// Start starts the session packet send and recv loop(s).
|
||||
func (s *Session) Start() {
|
||||
go func() {
|
||||
s.logger.Debug("New 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) {
|
||||
s.logMessage(binary.BigEndian.Uint16(data[0:2]), data, "Server", s.Name)
|
||||
select {
|
||||
case s.sendPackets <- packet{data, false}:
|
||||
// Enqueued data
|
||||
default:
|
||||
s.logger.Warn("Packet queue too full, flushing!")
|
||||
var tempPackets []packet
|
||||
for len(s.sendPackets) > 0 {
|
||||
tempPacket := <-s.sendPackets
|
||||
if !tempPacket.nonBlocking {
|
||||
tempPackets = append(tempPackets, tempPacket)
|
||||
}
|
||||
}
|
||||
for _, tempPacket := range tempPackets {
|
||||
s.sendPackets <- tempPacket
|
||||
}
|
||||
s.sendPackets <- packet{data, false}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 <- packet{data, true}:
|
||||
s.logMessage(binary.BigEndian.Uint16(data[0:2]), data, "Server", s.Name)
|
||||
default:
|
||||
s.logger.Warn("Packet queue too full, dropping!")
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if s.closed {
|
||||
return
|
||||
}
|
||||
pkt := <-s.sendPackets
|
||||
err := s.cryptConn.SendPacket(append(pkt.data, []byte{0x00, 0x10}...))
|
||||
if err != nil {
|
||||
s.logger.Warn("Failed to send packet")
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) recvLoop() {
|
||||
for {
|
||||
if time.Now().Add(-30 * time.Second).After(s.lastPacket) {
|
||||
logoutPlayer(s)
|
||||
return
|
||||
}
|
||||
if s.closed {
|
||||
logoutPlayer(s)
|
||||
return
|
||||
}
|
||||
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)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) handlePacketGroup(pktGroup []byte) {
|
||||
s.lastPacket = time.Now()
|
||||
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.closed = true
|
||||
return
|
||||
}
|
||||
// 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_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))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) SetObjectID() {
|
||||
for i := uint16(1); i < 127; i++ {
|
||||
exists := false
|
||||
for _, j := range s.server.objectIDs {
|
||||
if i == j {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
s.server.objectIDs[s] = i
|
||||
return
|
||||
}
|
||||
}
|
||||
s.server.objectIDs[s] = 0
|
||||
}
|
||||
|
||||
func (s *Session) NextObjectID() uint32 {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(s.server.objectIDs[s])
|
||||
s.objectIndex++
|
||||
bf.WriteUint16(s.objectIndex)
|
||||
bf.Seek(0, 0)
|
||||
return bf.ReadUint32()
|
||||
}
|
||||
97
server/channelserver/sys_stage.go
Normal file
97
server/channelserver/sys_stage.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"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
|
||||
}
|
||||
|
||||
// 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 uint8
|
||||
|
||||
// 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
|
||||
|
||||
host *Session
|
||||
maxPlayers uint16
|
||||
password 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,
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// BroadcastMHF queues a MHFPacket to be sent to all sessions in the stage.
|
||||
func (s *Stage) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
29
server/channelserver/sys_time.go
Normal file
29
server/channelserver/sys_time.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func TimeAdjusted() time.Time {
|
||||
baseTime := time.Now().In(time.FixedZone("UTC+9", 9*60*60))
|
||||
return time.Date(baseTime.Year(), baseTime.Month(), baseTime.Day(), baseTime.Hour(), baseTime.Minute(), baseTime.Second(), baseTime.Nanosecond(), baseTime.Location())
|
||||
}
|
||||
|
||||
func TimeMidnight() time.Time {
|
||||
baseTime := time.Now().In(time.FixedZone("UTC+9", 9*60*60))
|
||||
return time.Date(baseTime.Year(), baseTime.Month(), baseTime.Day(), 0, 0, 0, 0, baseTime.Location())
|
||||
}
|
||||
|
||||
func TimeWeekStart() time.Time {
|
||||
midnight := TimeMidnight()
|
||||
offset := (int(midnight.Weekday()) - 1) * -24
|
||||
return midnight.Add(time.Hour * time.Duration(offset))
|
||||
}
|
||||
|
||||
func TimeWeekNext() time.Time {
|
||||
return TimeWeekStart().Add(time.Hour * 24 * 7)
|
||||
}
|
||||
|
||||
func TimeGameAbsolute() uint32 {
|
||||
return uint32((TimeAdjusted().Unix() - 2160) % 5760)
|
||||
}
|
||||
Reference in New Issue
Block a user