mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-04-01 21:43:08 +02:00
Merge branch 'main' into feature/hunting-tournament
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -94,7 +94,7 @@ func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
bf.WriteUint32(cafeTime) // Total cafe time
|
||||
bf.WriteUint16(0)
|
||||
ps.Uint16(bf, fmt.Sprintf(s.server.dict["cafeReset"], int(cafeReset.Month()), cafeReset.Day()), true)
|
||||
ps.Uint16(bf, fmt.Sprintf(s.server.i18n.cafe.reset, int(cafeReset.Month()), cafeReset.Day()), true)
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
@@ -220,7 +220,7 @@ func addPointNetcafe(s *Session, p int) error {
|
||||
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)
|
||||
boostLimit := TimeAdjusted().Add(time.Duration(s.server.erupeConfig.GameplayOptions.BoostTimeDuration) * time.Second)
|
||||
if s.server.erupeConfig.GameplayOptions.DisableBoostTime {
|
||||
bf.WriteUint32(0)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/mhfcid"
|
||||
"erupe-ce/common/mhfcourse"
|
||||
"erupe-ce/common/token"
|
||||
"erupe-ce/config"
|
||||
"erupe-ce/network"
|
||||
"erupe-ce/network/binpacket"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"fmt"
|
||||
@@ -57,7 +60,7 @@ func init() {
|
||||
}
|
||||
|
||||
func sendDisabledCommandMessage(s *Session, cmd _config.Command) {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandDisabled"], cmd.Name))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.disabled, cmd.Name))
|
||||
}
|
||||
|
||||
func sendServerChatMessage(s *Session, message string) {
|
||||
@@ -74,7 +77,7 @@ func sendServerChatMessage(s *Session, message string) {
|
||||
msgBinChat.Build(bf)
|
||||
|
||||
castedBin := &mhfpacket.MsgSysCastedBinary{
|
||||
CharID: s.charID,
|
||||
CharID: 0,
|
||||
MessageType: BinaryMessageTypeChat,
|
||||
RawDataPayload: bf.Data(),
|
||||
}
|
||||
@@ -83,30 +86,99 @@ func sendServerChatMessage(s *Session, message string) {
|
||||
}
|
||||
|
||||
func parseChatCommand(s *Session, command string) {
|
||||
args := strings.Split(command[1:], " ")
|
||||
args := strings.Split(command[len(s.server.erupeConfig.CommandPrefix):], " ")
|
||||
switch args[0] {
|
||||
case commands["Ban"].Prefix:
|
||||
if s.isOp() {
|
||||
if len(args) > 1 {
|
||||
var expiry time.Time
|
||||
if len(args) > 2 {
|
||||
var length int
|
||||
var unit string
|
||||
n, err := fmt.Sscanf(args[2], `%d%s`, &length, &unit)
|
||||
if err == nil && n == 2 {
|
||||
switch unit {
|
||||
case "s", "second", "seconds":
|
||||
expiry = time.Now().Add(time.Duration(length) * time.Second)
|
||||
case "m", "mi", "minute", "minutes":
|
||||
expiry = time.Now().Add(time.Duration(length) * time.Minute)
|
||||
case "h", "hour", "hours":
|
||||
expiry = time.Now().Add(time.Duration(length) * time.Hour)
|
||||
case "d", "day", "days":
|
||||
expiry = time.Now().Add(time.Duration(length) * time.Hour * 24)
|
||||
case "mo", "month", "months":
|
||||
expiry = time.Now().Add(time.Duration(length) * time.Hour * 24 * 30)
|
||||
case "y", "year", "years":
|
||||
expiry = time.Now().Add(time.Duration(length) * time.Hour * 24 * 365)
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.i18n.commands.ban.error)
|
||||
return
|
||||
}
|
||||
}
|
||||
cid := mhfcid.ConvertCID(args[1])
|
||||
if cid > 0 {
|
||||
var uid uint32
|
||||
var uname string
|
||||
err := s.server.db.QueryRow(`SELECT id, username FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, cid).Scan(&uid, &uname)
|
||||
if err == nil {
|
||||
if expiry.IsZero() {
|
||||
s.server.db.Exec(`INSERT INTO bans VALUES ($1)
|
||||
ON CONFLICT (user_id) DO UPDATE SET expires=NULL`, uid)
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.ban.success, uname))
|
||||
} else {
|
||||
s.server.db.Exec(`INSERT INTO bans VALUES ($1, $2)
|
||||
ON CONFLICT (user_id) DO UPDATE SET expires=$2`, uid, expiry)
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.ban.success, uname)+fmt.Sprintf(s.server.i18n.commands.ban.length, expiry.Format(time.DateTime)))
|
||||
}
|
||||
s.server.DisconnectUser(uid)
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.i18n.commands.ban.noUser)
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.i18n.commands.ban.invalid)
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.i18n.commands.ban.error)
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.i18n.commands.noOp)
|
||||
}
|
||||
case commands["Timer"].Prefix:
|
||||
if commands["Timer"].Enabled || s.isOp() {
|
||||
var state bool
|
||||
s.server.db.QueryRow(`SELECT COALESCE(timer, false) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&state)
|
||||
s.server.db.Exec(`UPDATE users u SET timer=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, !state, s.charID)
|
||||
if state {
|
||||
sendServerChatMessage(s, s.server.i18n.commands.timer.disabled)
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.i18n.commands.timer.enabled)
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Timer"])
|
||||
}
|
||||
case commands["PSN"].Prefix:
|
||||
if commands["PSN"].Enabled {
|
||||
if commands["PSN"].Enabled || s.isOp() {
|
||||
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]))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.psn.success, args[1]))
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandPSNExists"])
|
||||
sendServerChatMessage(s, s.server.i18n.commands.psn.exists)
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandPSNError"], commands["PSN"].Prefix))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.psn.error, commands["PSN"].Prefix))
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["PSN"])
|
||||
}
|
||||
case commands["Reload"].Prefix:
|
||||
if commands["Reload"].Enabled {
|
||||
sendServerChatMessage(s, s.server.dict["commandReload"])
|
||||
if commands["Reload"].Enabled || s.isOp() {
|
||||
sendServerChatMessage(s, s.server.i18n.commands.reload)
|
||||
var temp mhfpacket.MHFPacket
|
||||
deleteNotif := byteframe.NewByteFrame()
|
||||
for _, object := range s.stage.objects {
|
||||
@@ -125,7 +197,7 @@ func parseChatCommand(s *Session, command string) {
|
||||
deleteNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(deleteNotif, s.clientContext)
|
||||
}
|
||||
deleteNotif.WriteUint16(0x0010)
|
||||
deleteNotif.WriteUint16(uint16(network.MSG_SYS_END))
|
||||
s.QueueSend(deleteNotif.Data())
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
reloadNotif := byteframe.NewByteFrame()
|
||||
@@ -160,24 +232,28 @@ func parseChatCommand(s *Session, command string) {
|
||||
reloadNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(reloadNotif, s.clientContext)
|
||||
}
|
||||
reloadNotif.WriteUint16(0x0010)
|
||||
reloadNotif.WriteUint16(uint16(network.MSG_SYS_END))
|
||||
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))
|
||||
if commands["KeyQuest"].Enabled || s.isOp() {
|
||||
if s.server.erupeConfig.RealClientMode < _config.G10 {
|
||||
sendServerChatMessage(s, s.server.i18n.commands.kqf.version)
|
||||
} else {
|
||||
if len(args) > 1 {
|
||||
if args[1] == "get" {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.kqf.get, 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.i18n.commands.kqf.set.success)
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.kqf.set.error, commands["KeyQuest"].Prefix))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -185,23 +261,23 @@ func parseChatCommand(s *Session, command string) {
|
||||
sendDisabledCommandMessage(s, commands["KeyQuest"])
|
||||
}
|
||||
case commands["Rights"].Prefix:
|
||||
if commands["Rights"].Enabled {
|
||||
if commands["Rights"].Enabled || s.isOp() {
|
||||
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))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.rights.success, v))
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsError"], commands["Rights"].Prefix))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.rights.error, commands["Rights"].Prefix))
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsError"], commands["Rights"].Prefix))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.rights.error, commands["Rights"].Prefix))
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Rights"])
|
||||
}
|
||||
case commands["Course"].Prefix:
|
||||
if commands["Course"].Enabled {
|
||||
if commands["Course"].Enabled || s.isOp() {
|
||||
if len(args) > 1 {
|
||||
for _, course := range mhfcourse.Courses() {
|
||||
for _, alias := range course.Aliases() {
|
||||
@@ -219,11 +295,11 @@ func parseChatCommand(s *Session, command string) {
|
||||
})
|
||||
if ei != -1 {
|
||||
delta = uint32(-1 * math.Pow(2, float64(course.ID)))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseDisabled"], course.Aliases()[0]))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.disabled, course.Aliases()[0]))
|
||||
}
|
||||
} else {
|
||||
delta = uint32(math.Pow(2, float64(course.ID)))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseEnabled"], course.Aliases()[0]))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.enabled, 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 {
|
||||
@@ -231,67 +307,74 @@ func parseChatCommand(s *Session, command string) {
|
||||
}
|
||||
updateRights(s)
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseLocked"], course.Aliases()[0]))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.locked, course.Aliases()[0]))
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseError"], commands["Course"].Prefix))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.error, commands["Course"].Prefix))
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Course"])
|
||||
}
|
||||
case commands["Raviente"].Prefix:
|
||||
if commands["Raviente"].Enabled {
|
||||
if commands["Raviente"].Enabled || s.isOp() {
|
||||
if len(args) > 1 {
|
||||
if getRaviSemaphore(s.server) != nil {
|
||||
if s.server.getRaviSemaphore() != nil {
|
||||
switch args[1] {
|
||||
case "start":
|
||||
if s.server.raviente.register.startTime == 0 {
|
||||
s.server.raviente.register.startTime = s.server.raviente.register.postTime
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviStartSuccess"])
|
||||
if s.server.raviente.register[1] == 0 {
|
||||
s.server.raviente.register[1] = s.server.raviente.register[3]
|
||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.start.success)
|
||||
s.notifyRavi()
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviStartError"])
|
||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.start.error)
|
||||
}
|
||||
case "cm", "check", "checkmultiplier", "multiplier":
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRaviMultiplier"], s.server.raviente.GetRaviMultiplier(s.server)))
|
||||
case "sr", "sendres", "resurrection":
|
||||
if s.server.raviente.state.stateData[28] > 0 {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviResSuccess"])
|
||||
s.server.raviente.state.stateData[28] = 0
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.ravi.multiplier, 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.i18n.commands.ravi.res.success)
|
||||
s.server.raviente.state[28] = 0
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.res.error)
|
||||
}
|
||||
case "ss", "sendsed":
|
||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.sed.success)
|
||||
// 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.i18n.commands.ravi.request)
|
||||
// Total BerRavi HP
|
||||
HP := s.server.raviente.state[0] + s.server.raviente.state[1] + s.server.raviente.state[2] + s.server.raviente.state[3] + s.server.raviente.state[4]
|
||||
s.server.raviente.support[1] = HP + 1
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviResError"])
|
||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.version)
|
||||
}
|
||||
case "ss", "sendsed":
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviSedSuccess"])
|
||||
// Total BerRavi HP
|
||||
HP := s.server.raviente.state.stateData[0] + s.server.raviente.state.stateData[1] + s.server.raviente.state.stateData[2] + s.server.raviente.state.stateData[3] + s.server.raviente.state.stateData[4]
|
||||
s.server.raviente.support.supportData[1] = HP
|
||||
case "rs", "reqsed":
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviRequest"])
|
||||
// Total BerRavi HP
|
||||
HP := s.server.raviente.state.stateData[0] + s.server.raviente.state.stateData[1] + s.server.raviente.state.stateData[2] + s.server.raviente.state.stateData[3] + s.server.raviente.state.stateData[4]
|
||||
s.server.raviente.support.supportData[1] = HP + 12
|
||||
default:
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviError"])
|
||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.error)
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviNoPlayers"])
|
||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.noPlayers)
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviError"])
|
||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.error)
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Raviente"])
|
||||
}
|
||||
case commands["Teleport"].Prefix:
|
||||
if commands["Teleport"].Enabled {
|
||||
if commands["Teleport"].Enabled || s.isOp() {
|
||||
if len(args) > 2 {
|
||||
x, _ := strconv.Atoi(args[1])
|
||||
y, _ := strconv.Atoi(args[2])
|
||||
x, _ := strconv.ParseInt(args[1], 10, 16)
|
||||
y, _ := strconv.ParseInt(args[2], 10, 16)
|
||||
payload := byteframe.NewByteFrame()
|
||||
payload.SetLE()
|
||||
payload.WriteUint8(2) // SetState type(position == 2)
|
||||
@@ -303,13 +386,37 @@ func parseChatCommand(s *Session, command string) {
|
||||
MessageType: BinaryMessageTypeState,
|
||||
RawDataPayload: payloadBytes,
|
||||
})
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandTeleportSuccess"], x, y))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.teleport.success, x, y))
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandTeleportError"], commands["Teleport"].Prefix))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.teleport.error, commands["Teleport"].Prefix))
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Teleport"])
|
||||
}
|
||||
case commands["Discord"].Prefix:
|
||||
if commands["Discord"].Enabled || s.isOp() {
|
||||
var _token string
|
||||
err := s.server.db.QueryRow(`SELECT discord_token FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&_token)
|
||||
if err != nil {
|
||||
randToken := make([]byte, 4)
|
||||
rand.Read(randToken)
|
||||
_token = fmt.Sprintf("%x-%x", randToken[:2], randToken[2:])
|
||||
s.server.db.Exec(`UPDATE users u SET discord_token = $1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, _token, s.charID)
|
||||
}
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.discord.success, _token))
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Discord"])
|
||||
}
|
||||
case commands["Help"].Prefix:
|
||||
if commands["Help"].Enabled || s.isOp() {
|
||||
for _, command := range commands {
|
||||
if command.Enabled || s.isOp() {
|
||||
sendServerChatMessage(s, fmt.Sprintf("%s%s: %s", s.server.erupeConfig.CommandPrefix, command.Prefix, command.Description))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Help"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,14 +426,18 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
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))
|
||||
var timer bool
|
||||
s.server.db.QueryRow(`SELECT COALESCE(timer, false) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&timer)
|
||||
if timer {
|
||||
_ = tmp.ReadBytes(9)
|
||||
tmp.SetLE()
|
||||
frame := tmp.ReadUint32()
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.timer, frame/30/60/60, 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 s.server.erupeConfig.DebugOptions.QuestTools {
|
||||
if pkt.BroadcastType == 0x03 && pkt.MessageType == 0x02 && len(pkt.RawDataPayload) > 32 {
|
||||
// This is only correct most of the time
|
||||
tmp.ReadBytes(20)
|
||||
@@ -340,24 +451,20 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
// Parse out the real casted binary payload
|
||||
var msgBinTargeted *binpacket.MsgBinTargeted
|
||||
var authorLen, msgLen uint16
|
||||
var msg []byte
|
||||
|
||||
isDiceCommand := false
|
||||
var message, author string
|
||||
var returnToSender bool
|
||||
if pkt.MessageType == BinaryMessageTypeChat {
|
||||
tmp.SetLE()
|
||||
tmp.Seek(int64(0), 0)
|
||||
_ = tmp.ReadUint32()
|
||||
authorLen = tmp.ReadUint16()
|
||||
msgLen = tmp.ReadUint16()
|
||||
msg = tmp.ReadNullTerminatedBytes()
|
||||
tmp.Seek(8, 0)
|
||||
message = string(tmp.ReadNullTerminatedBytes())
|
||||
author = string(tmp.ReadNullTerminatedBytes())
|
||||
}
|
||||
|
||||
// Customise payload
|
||||
realPayload := pkt.RawDataPayload
|
||||
if pkt.BroadcastType == BroadcastTypeTargeted {
|
||||
tmp.SetBE()
|
||||
tmp.Seek(int64(0), 0)
|
||||
tmp.Seek(0, 0)
|
||||
msgBinTargeted = &binpacket.MsgBinTargeted{}
|
||||
err := msgBinTargeted.Parse(tmp)
|
||||
if err != nil {
|
||||
@@ -366,24 +473,24 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
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()
|
||||
if message == "@dice" {
|
||||
returnToSender = true
|
||||
m := binpacket.MsgBinChat{
|
||||
Type: BinaryMessageTypeChat,
|
||||
Flags: 4,
|
||||
Message: fmt.Sprintf(`%d`, token.RNG().Intn(100)+1),
|
||||
SenderName: author,
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.SetLE()
|
||||
m.Build(bf)
|
||||
realPayload = bf.Data()
|
||||
} else {
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
|
||||
bf.SetLE()
|
||||
chatMessage := &binpacket.MsgBinChat{}
|
||||
chatMessage.Parse(bf)
|
||||
if strings.HasPrefix(chatMessage.Message, "!") {
|
||||
if strings.HasPrefix(chatMessage.Message, s.server.erupeConfig.CommandPrefix) {
|
||||
parseChatCommand(s, chatMessage.Message)
|
||||
return
|
||||
}
|
||||
@@ -406,15 +513,16 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
case BroadcastTypeWorld:
|
||||
s.server.WorldcastMHF(resp, s, nil)
|
||||
case BroadcastTypeStage:
|
||||
if isDiceCommand {
|
||||
s.stage.BroadcastMHF(resp, nil) // send dice result back to caller
|
||||
if returnToSender {
|
||||
s.stage.BroadcastMHF(resp, nil)
|
||||
} else {
|
||||
s.stage.BroadcastMHF(resp, s)
|
||||
}
|
||||
case BroadcastTypeServer:
|
||||
if pkt.MessageType == 1 {
|
||||
if getRaviSemaphore(s.server) != nil {
|
||||
s.server.BroadcastMHF(resp, s)
|
||||
raviSema := s.server.getRaviSemaphore()
|
||||
if raviSema != nil {
|
||||
raviSema.BroadcastMHF(resp, s)
|
||||
}
|
||||
} else {
|
||||
s.server.BroadcastMHF(resp, s)
|
||||
|
||||
@@ -19,7 +19,7 @@ const (
|
||||
pRP // +2
|
||||
pHouseTier // +5
|
||||
pHouseData // +195
|
||||
pBookshelfData // +5576
|
||||
pBookshelfData // +lBookshelfData
|
||||
pGalleryData // +1748
|
||||
pToreData // +240
|
||||
pGardenData // +68
|
||||
@@ -28,6 +28,7 @@ const (
|
||||
pHRP // +2
|
||||
pGRP // +4
|
||||
pKQF // +8
|
||||
lBookshelfData
|
||||
)
|
||||
|
||||
type CharacterSaveData struct {
|
||||
@@ -55,7 +56,7 @@ type CharacterSaveData struct {
|
||||
}
|
||||
|
||||
func getPointers() map[SavePointer]int {
|
||||
pointers := map[SavePointer]int{pGender: 81}
|
||||
pointers := map[SavePointer]int{pGender: 81, lBookshelfData: 5576}
|
||||
switch _config.ErupeConfig.RealClientMode {
|
||||
case _config.ZZ:
|
||||
pointers[pWeaponID] = 128522
|
||||
@@ -70,7 +71,9 @@ func getPointers() map[SavePointer]int {
|
||||
pointers[pGardenData] = 142424
|
||||
pointers[pRP] = 142614
|
||||
pointers[pKQF] = 146720
|
||||
case _config.Z2, _config.Z1, _config.G101, _config.G10:
|
||||
case _config.Z2, _config.Z1, _config.G101, _config.G10, _config.G91, _config.G9, _config.G81, _config.G8,
|
||||
_config.G7, _config.G61, _config.G6, _config.G52, _config.G51, _config.G5, _config.GG, _config.G32, _config.G31,
|
||||
_config.G3, _config.G2, _config.G1:
|
||||
pointers[pWeaponID] = 92522
|
||||
pointers[pWeaponType] = 92789
|
||||
pointers[pHouseTier] = 93900
|
||||
@@ -83,6 +86,33 @@ func getPointers() map[SavePointer]int {
|
||||
pointers[pGardenData] = 106424
|
||||
pointers[pRP] = 106614
|
||||
pointers[pKQF] = 110720
|
||||
case _config.F5, _config.F4:
|
||||
pointers[pWeaponID] = 60522
|
||||
pointers[pWeaponType] = 60789
|
||||
pointers[pHouseTier] = 61900
|
||||
pointers[pToreData] = 62228
|
||||
pointers[pHRP] = 62550
|
||||
pointers[pHouseData] = 62561
|
||||
pointers[pBookshelfData] = 57118 // This pointer only half works
|
||||
pointers[pGalleryData] = 72064
|
||||
pointers[pGardenData] = 74424
|
||||
pointers[pRP] = 74614
|
||||
case _config.S6:
|
||||
pointers[pWeaponID] = 12522
|
||||
pointers[pWeaponType] = 12789
|
||||
pointers[pHouseTier] = 13900
|
||||
pointers[pToreData] = 14228
|
||||
pointers[pHRP] = 14550
|
||||
pointers[pHouseData] = 14561
|
||||
pointers[pBookshelfData] = 9118 // Probably same here
|
||||
pointers[pGalleryData] = 24064
|
||||
pointers[pGardenData] = 26424
|
||||
pointers[pRP] = 26614
|
||||
}
|
||||
if _config.ErupeConfig.RealClientMode == _config.G5 {
|
||||
pointers[lBookshelfData] = 5548
|
||||
} else if _config.ErupeConfig.RealClientMode <= _config.GG {
|
||||
pointers[lBookshelfData] = 4520
|
||||
}
|
||||
return pointers
|
||||
}
|
||||
@@ -176,8 +206,10 @@ func (save *CharacterSaveData) Decompress() error {
|
||||
func (save *CharacterSaveData) updateSaveDataWithStruct() {
|
||||
rpBytes := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(rpBytes, save.RP)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G10 {
|
||||
if _config.ErupeConfig.RealClientMode >= _config.F4 {
|
||||
copy(save.decompSave[save.Pointers[pRP]:save.Pointers[pRP]+2], rpBytes)
|
||||
}
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G10 {
|
||||
copy(save.decompSave[save.Pointers[pKQF]:save.Pointers[pKQF]+8], save.KQF)
|
||||
}
|
||||
}
|
||||
@@ -191,21 +223,25 @@ func (save *CharacterSaveData) updateStructWithSaveData() {
|
||||
save.Gender = false
|
||||
}
|
||||
if !save.IsNewCharacter {
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G10 {
|
||||
if _config.ErupeConfig.RealClientMode >= _config.S6 {
|
||||
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.BookshelfData = save.decompSave[save.Pointers[pBookshelfData] : save.Pointers[pBookshelfData]+save.Pointers[lBookshelfData]]
|
||||
save.GalleryData = save.decompSave[save.Pointers[pGalleryData] : save.Pointers[pGalleryData]+1748]
|
||||
save.ToreData = save.decompSave[save.Pointers[pToreData] : save.Pointers[pToreData]+240]
|
||||
save.GardenData = save.decompSave[save.Pointers[pGardenData] : save.Pointers[pGardenData]+68]
|
||||
save.WeaponType = save.decompSave[save.Pointers[pWeaponType]]
|
||||
save.WeaponID = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pWeaponID] : save.Pointers[pWeaponID]+2])
|
||||
save.HRP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pHRP] : save.Pointers[pHRP]+2])
|
||||
if save.HRP == uint16(999) {
|
||||
save.GR = grpToGR(binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pGRP] : save.Pointers[pGRP]+4]))
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G1 {
|
||||
if save.HRP == uint16(999) {
|
||||
save.GR = grpToGR(int(binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pGRP] : save.Pointers[pGRP]+4])))
|
||||
}
|
||||
}
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G10 {
|
||||
save.KQF = save.decompSave[save.Pointers[pKQF] : save.Pointers[pKQF]+8]
|
||||
}
|
||||
save.KQF = save.decompSave[save.Pointers[pKQF] : save.Pointers[pKQF]+8]
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
@@ -29,6 +29,9 @@ func handleMsgSysEnumerateClient(s *Session, p mhfpacket.MHFPacket) {
|
||||
for _, cid := range stage.clients {
|
||||
clients = append(clients, cid)
|
||||
}
|
||||
for cid := range stage.reservedClientSlots {
|
||||
clients = append(clients, cid)
|
||||
}
|
||||
case 1: // Not ready
|
||||
for cid, ready := range stage.reservedClientSlots {
|
||||
if !ready {
|
||||
@@ -60,20 +63,19 @@ func handleMsgMhfListMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
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
|
||||
if err == nil {
|
||||
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))
|
||||
}
|
||||
count++
|
||||
resp.WriteUint32(uint32(cid))
|
||||
resp.WriteUint32(16)
|
||||
resp.WriteBytes(stringsupport.PaddedString(name, 16, true))
|
||||
}
|
||||
resp.Seek(0, 0)
|
||||
resp.WriteUint32(count)
|
||||
@@ -83,28 +85,28 @@ func handleMsgMhfListMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
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)
|
||||
for _, cid := range pkt.CharIDs {
|
||||
if pkt.Blacklist {
|
||||
err := s.server.db.QueryRow("SELECT blocked FROM characters WHERE id=$1", s.charID).Scan(&csv)
|
||||
if err == nil {
|
||||
if pkt.Operation {
|
||||
csv = stringsupport.CSVRemove(csv, int(cid))
|
||||
} else {
|
||||
csv = stringsupport.CSVAdd(csv, int(cid))
|
||||
}
|
||||
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 {
|
||||
if pkt.Operation {
|
||||
csv = stringsupport.CSVRemove(csv, int(cid))
|
||||
} else {
|
||||
csv = stringsupport.CSVAdd(csv, int(cid))
|
||||
}
|
||||
s.server.db.Exec("UPDATE characters SET friends=$1 WHERE id=$2", csv, s.charID)
|
||||
}
|
||||
}
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/mhfmon"
|
||||
"erupe-ce/common/stringsupport"
|
||||
_config "erupe-ce/config"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -44,6 +46,9 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
if s.server.erupeConfig.SaveDumps.RawEnabled {
|
||||
dumpSaveData(s, saveData, "raw-savedata")
|
||||
}
|
||||
s.logger.Info("Updating save with blob")
|
||||
characterSaveData.decompSave = saveData
|
||||
}
|
||||
@@ -54,7 +59,7 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) {
|
||||
s.Name = characterSaveData.Name
|
||||
}
|
||||
|
||||
if characterSaveData.Name == s.Name {
|
||||
if characterSaveData.Name == s.Name || _config.ErupeConfig.RealClientMode <= _config.S10 {
|
||||
characterSaveData.Save(s)
|
||||
s.logger.Info("Wrote recompressed savedata back to DB.")
|
||||
} else {
|
||||
@@ -72,170 +77,47 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func grpToGR(n uint32) uint16 {
|
||||
var gr uint16
|
||||
gr = 1
|
||||
switch grp := int(n); {
|
||||
case grp < 208750: // Up to 50
|
||||
i := 0
|
||||
for {
|
||||
grp -= 500
|
||||
if grp <= 500 {
|
||||
if grp < 0 {
|
||||
i--
|
||||
func grpToGR(n int) uint16 {
|
||||
var gr int
|
||||
a := []int{208750, 593400, 993400, 1400900, 2315900, 3340900, 4505900, 5850900, 7415900, 9230900, 11345900, 100000000}
|
||||
b := []int{7850, 8000, 8150, 9150, 10250, 11650, 13450, 15650, 18150, 21150, 23950}
|
||||
c := []int{51, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900}
|
||||
|
||||
for i := 0; i < len(a); i++ {
|
||||
if n < a[i] {
|
||||
if i == 0 {
|
||||
for {
|
||||
n -= 500
|
||||
if n <= 500 {
|
||||
if n < 0 {
|
||||
i--
|
||||
}
|
||||
break
|
||||
} else {
|
||||
i++
|
||||
for j := 0; j < i; j++ {
|
||||
n -= 150
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
gr = i + 2
|
||||
} else {
|
||||
i++
|
||||
for j := 0; j < i; j++ {
|
||||
grp -= 150
|
||||
}
|
||||
n -= a[i-1]
|
||||
gr = c[i-1]
|
||||
gr += n / b[i-1]
|
||||
}
|
||||
break
|
||||
}
|
||||
gr = uint16(i + 2)
|
||||
break
|
||||
case grp < 593400: // 51-99
|
||||
grp -= 208750
|
||||
i := 51
|
||||
for {
|
||||
if grp < 7850 {
|
||||
break
|
||||
}
|
||||
i++
|
||||
grp -= 7850
|
||||
}
|
||||
gr = uint16(i)
|
||||
break
|
||||
case grp < 993400: // 100-149
|
||||
grp -= 593400
|
||||
i := 100
|
||||
for {
|
||||
if grp < 8000 {
|
||||
break
|
||||
}
|
||||
i++
|
||||
grp -= 8000
|
||||
}
|
||||
gr = uint16(i)
|
||||
break
|
||||
case grp < 1400900: // 150-199
|
||||
grp -= 993400
|
||||
i := 150
|
||||
for {
|
||||
if grp < 8150 {
|
||||
break
|
||||
}
|
||||
i++
|
||||
grp -= 8150
|
||||
}
|
||||
gr = uint16(i)
|
||||
break
|
||||
case grp < 2315900: // 200-299
|
||||
grp -= 1400900
|
||||
i := 200
|
||||
for {
|
||||
if grp < 9150 {
|
||||
break
|
||||
}
|
||||
i++
|
||||
grp -= 9150
|
||||
}
|
||||
gr = uint16(i)
|
||||
break
|
||||
case grp < 3340900: // 300-399
|
||||
grp -= 2315900
|
||||
i := 300
|
||||
for {
|
||||
if grp < 10250 {
|
||||
break
|
||||
}
|
||||
i++
|
||||
grp -= 10250
|
||||
}
|
||||
gr = uint16(i)
|
||||
break
|
||||
case grp < 4505900: // 400-499
|
||||
grp -= 3340900
|
||||
i := 400
|
||||
for {
|
||||
if grp < 11650 {
|
||||
break
|
||||
}
|
||||
i++
|
||||
grp -= 11650
|
||||
}
|
||||
gr = uint16(i)
|
||||
break
|
||||
case grp < 5850900: // 500-599
|
||||
grp -= 4505900
|
||||
i := 500
|
||||
for {
|
||||
if grp < 13450 {
|
||||
break
|
||||
}
|
||||
i++
|
||||
grp -= 13450
|
||||
}
|
||||
gr = uint16(i)
|
||||
break
|
||||
case grp < 7415900: // 600-699
|
||||
grp -= 5850900
|
||||
i := 600
|
||||
for {
|
||||
if grp < 15650 {
|
||||
break
|
||||
}
|
||||
i++
|
||||
grp -= 15650
|
||||
}
|
||||
gr = uint16(i)
|
||||
break
|
||||
case grp < 9230900: // 700-799
|
||||
grp -= 7415900
|
||||
i := 700
|
||||
for {
|
||||
if grp < 18150 {
|
||||
break
|
||||
}
|
||||
i++
|
||||
grp -= 18150
|
||||
}
|
||||
gr = uint16(i)
|
||||
break
|
||||
case grp < 11345900: // 800-899
|
||||
grp -= 9230900
|
||||
i := 800
|
||||
for {
|
||||
if grp < 21150 {
|
||||
break
|
||||
}
|
||||
i++
|
||||
grp -= 21150
|
||||
}
|
||||
gr = uint16(i)
|
||||
break
|
||||
default: // 900+
|
||||
grp -= 11345900
|
||||
i := 900
|
||||
for {
|
||||
if grp < 23950 {
|
||||
break
|
||||
}
|
||||
i++
|
||||
grp -= 23950
|
||||
}
|
||||
gr = uint16(i)
|
||||
break
|
||||
}
|
||||
return gr
|
||||
return uint16(gr)
|
||||
}
|
||||
|
||||
func dumpSaveData(s *Session, data []byte, suffix string) {
|
||||
if !s.server.erupeConfig.DevModeOptions.SaveDumps.Enabled {
|
||||
if !s.server.erupeConfig.SaveDumps.Enabled {
|
||||
return
|
||||
} else {
|
||||
dir := filepath.Join(s.server.erupeConfig.DevModeOptions.SaveDumps.OutputDir, fmt.Sprintf("%d", s.charID))
|
||||
path := filepath.Join(s.server.erupeConfig.DevModeOptions.SaveDumps.OutputDir, fmt.Sprintf("%d", s.charID), fmt.Sprintf("%d_%s.bin", s.charID, suffix))
|
||||
dir := filepath.Join(s.server.erupeConfig.SaveDumps.OutputDir, fmt.Sprintf("%d", s.charID))
|
||||
path := filepath.Join(s.server.erupeConfig.SaveDumps.OutputDir, fmt.Sprintf("%d", s.charID), fmt.Sprintf("%d_%s.bin", s.charID, suffix))
|
||||
_, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
@@ -301,7 +183,7 @@ func handleMsgMhfLoadScenarioData(s *Session, p mhfpacket.MHFPacket) {
|
||||
var scenarioData []byte
|
||||
bf := byteframe.NewByteFrame()
|
||||
err := s.server.db.QueryRow("SELECT scenariodata FROM characters WHERE id = $1", s.charID).Scan(&scenarioData)
|
||||
if err != nil {
|
||||
if err != nil || len(scenarioData) < 10 {
|
||||
s.logger.Error("Failed to load scenariodata", zap.Error(err))
|
||||
bf.WriteBytes(make([]byte, 10))
|
||||
} else {
|
||||
@@ -1161,34 +1043,34 @@ func handleMsgMhfGetPaperData(s *Session, p mhfpacket.MHFPacket) {
|
||||
{1105, 1, 10, 500, 0, 0, 0},
|
||||
{1105, 2, 10, 500, 0, 0, 0},
|
||||
// setServerBoss
|
||||
{2001, 1, 17, 58, 0, 6, 700},
|
||||
{2001, 1, 20, 58, 0, 3, 200},
|
||||
{2001, 1, 22, 58, 0, 7, 250},
|
||||
{2001, 1, 27, 58, 0, 1, 100},
|
||||
{2001, 1, 53, 58, 0, 8, 1000},
|
||||
{2001, 1, 67, 58, 0, 9, 500},
|
||||
{2001, 1, 68, 58, 0, 2, 150},
|
||||
{2001, 1, 74, 58, 0, 4, 200},
|
||||
{2001, 1, 75, 58, 0, 5, 500},
|
||||
{2001, 1, 76, 58, 0, 10, 800},
|
||||
{2001, 1, 80, 58, 0, 11, 900},
|
||||
{2001, 1, 89, 58, 0, 12, 600},
|
||||
{2001, 2, 17, 60, 0, 6, 700},
|
||||
{2001, 2, 20, 60, 0, 3, 200},
|
||||
{2001, 2, 22, 60, 0, 7, 350},
|
||||
{2001, 2, 27, 60, 0, 1, 100},
|
||||
{2001, 2, 39, 60, 0, 13, 200},
|
||||
{2001, 2, 40, 60, 0, 15, 600},
|
||||
{2001, 2, 53, 60, 0, 8, 1000},
|
||||
{2001, 2, 67, 60, 0, 2, 500},
|
||||
{2001, 2, 68, 60, 0, 9, 150},
|
||||
{2001, 2, 74, 60, 0, 4, 200},
|
||||
{2001, 2, 75, 60, 0, 5, 500},
|
||||
{2001, 2, 76, 60, 0, 10, 800},
|
||||
{2001, 2, 80, 60, 0, 11, 900},
|
||||
{2001, 2, 81, 60, 0, 14, 900},
|
||||
{2001, 2, 89, 60, 0, 12, 600},
|
||||
{2001, 2, 94, 60, 0, 16, 1000},
|
||||
{2001, 1, mhfmon.Gravios, 58, 0, 6, 700},
|
||||
{2001, 1, mhfmon.Gypceros, 58, 0, 3, 200},
|
||||
{2001, 1, mhfmon.Basarios, 58, 0, 7, 250},
|
||||
{2001, 1, mhfmon.Velocidrome, 58, 0, 1, 100},
|
||||
{2001, 1, mhfmon.Rajang, 58, 0, 8, 1000},
|
||||
{2001, 1, mhfmon.ShogunCeanataur, 58, 0, 9, 500},
|
||||
{2001, 1, mhfmon.Bulldrome, 58, 0, 2, 150},
|
||||
{2001, 1, mhfmon.Hypnocatrice, 58, 0, 4, 200},
|
||||
{2001, 1, mhfmon.Lavasioth, 58, 0, 5, 500},
|
||||
{2001, 1, mhfmon.Tigrex, 58, 0, 10, 800},
|
||||
{2001, 1, mhfmon.Espinas, 58, 0, 11, 900},
|
||||
{2001, 1, mhfmon.Pariapuria, 58, 0, 12, 600},
|
||||
{2001, 2, mhfmon.Gravios, 60, 0, 6, 700},
|
||||
{2001, 2, mhfmon.Gypceros, 60, 0, 3, 200},
|
||||
{2001, 2, mhfmon.Basarios, 60, 0, 7, 350},
|
||||
{2001, 2, mhfmon.Velocidrome, 60, 0, 1, 100},
|
||||
{2001, 2, mhfmon.PurpleGypceros, 60, 0, 13, 200},
|
||||
{2001, 2, mhfmon.YianGaruga, 60, 0, 15, 600},
|
||||
{2001, 2, mhfmon.Rajang, 60, 0, 8, 1000},
|
||||
{2001, 2, mhfmon.ShogunCeanataur, 60, 0, 2, 500},
|
||||
{2001, 2, mhfmon.Bulldrome, 60, 0, 9, 150},
|
||||
{2001, 2, mhfmon.Hypnocatrice, 60, 0, 4, 200},
|
||||
{2001, 2, mhfmon.Lavasioth, 60, 0, 5, 500},
|
||||
{2001, 2, mhfmon.Tigrex, 60, 0, 10, 800},
|
||||
{2001, 2, mhfmon.Espinas, 60, 0, 11, 900},
|
||||
{2001, 2, mhfmon.BurningEspinas, 60, 0, 14, 900},
|
||||
{2001, 2, mhfmon.Pariapuria, 60, 0, 12, 600},
|
||||
{2001, 2, mhfmon.Dyuragaua, 60, 0, 16, 1000},
|
||||
}
|
||||
case 6:
|
||||
paperData = []PaperData{
|
||||
|
||||
@@ -3,6 +3,7 @@ package channelserver
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
@@ -66,10 +67,56 @@ func getCharacterList(s *Server) string {
|
||||
return message
|
||||
}
|
||||
|
||||
// onInteraction handles slash commands
|
||||
func (s *Server) onInteraction(ds *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
switch i.Interaction.ApplicationCommandData().Name {
|
||||
case "link":
|
||||
var temp string
|
||||
err := s.db.QueryRow(`UPDATE users SET discord_id = $1 WHERE discord_token = $2 RETURNING discord_id`, i.Member.User.ID, i.ApplicationCommandData().Options[0].StringValue()).Scan(&temp)
|
||||
if err == nil {
|
||||
ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Your Erupe account was linked successfully.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Failed to link Erupe account.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
}
|
||||
case "password":
|
||||
password, _ := bcrypt.GenerateFromPassword([]byte(i.ApplicationCommandData().Options[0].StringValue()), 10)
|
||||
_, err := s.db.Exec(`UPDATE users SET password = $1 WHERE discord_id = $2`, password, i.Member.User.ID)
|
||||
if err == nil {
|
||||
ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Your Erupe account password has been updated.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Failed to update Erupe account password.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// Ignore messages from bots, or messages that are not in the correct channel.
|
||||
if m.Author.Bot || m.ChannelID != s.erupeConfig.Discord.RelayChannel.RelayChannelID {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -79,11 +126,24 @@ func (s *Server) onDiscordMessage(ds *discordgo.Session, m *discordgo.MessageCre
|
||||
}
|
||||
return r
|
||||
}, m.Author.Username))
|
||||
|
||||
for i := 0; i < 8-len(m.Author.Username); i++ {
|
||||
paddedName += " "
|
||||
}
|
||||
message := s.discordBot.NormalizeDiscordMessage(fmt.Sprintf("[D] %s > %s", paddedName, m.Content))
|
||||
if len(message) > s.erupeConfig.Discord.RelayChannel.MaxMessageLength {
|
||||
return
|
||||
}
|
||||
|
||||
message := fmt.Sprintf("[D] %s > %s", paddedName, m.Content)
|
||||
s.BroadcastChatMessage(s.discordBot.NormalizeDiscordMessage(message))
|
||||
var messages []string
|
||||
lineLength := 61
|
||||
for i := 0; i < len(message); i += lineLength {
|
||||
end := i + lineLength
|
||||
if end > len(message) {
|
||||
end = len(message)
|
||||
}
|
||||
messages = append(messages, message[i:end])
|
||||
}
|
||||
for i := range messages {
|
||||
s.BroadcastChatMessage(messages[i])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,144 +3,183 @@ package channelserver
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
_config "erupe-ce/config"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"time"
|
||||
|
||||
"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"`
|
||||
type Distribution struct {
|
||||
ID uint32 `db:"id"`
|
||||
Deadline time.Time `db:"deadline"`
|
||||
TimesAcceptable uint16 `db:"times_acceptable"`
|
||||
TimesAccepted uint16 `db:"times_accepted"`
|
||||
MinHR int16 `db:"min_hr"`
|
||||
MaxHR int16 `db:"max_hr"`
|
||||
MinSR int16 `db:"min_sr"`
|
||||
MaxSR int16 `db:"max_sr"`
|
||||
MinGR int16 `db:"min_gr"`
|
||||
MaxGR int16 `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)
|
||||
|
||||
var itemDists []Distribution
|
||||
bf := byteframe.NewByteFrame()
|
||||
distCount := 0
|
||||
dists, err := s.server.db.Queryx(`
|
||||
rows, 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,
|
||||
COALESCE(min_hr, -1) AS min_hr, COALESCE(max_hr, -1) AS max_hr,
|
||||
COALESCE(min_sr, -1) AS min_sr, COALESCE(max_sr, -1) AS max_sr,
|
||||
COALESCE(min_gr, -1) AS min_gr, COALESCE(max_gr, -1) AS max_gr,
|
||||
(
|
||||
SELECT count(*)
|
||||
FROM distributions_accepted da
|
||||
WHERE d.id = da.distribution_id
|
||||
AND da.character_id = $1
|
||||
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
|
||||
COALESCE(deadline, TO_TIMESTAMP(0)) AS 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)
|
||||
WHERE character_id = $1 AND type = $2 OR character_id IS NULL AND type = $2 ORDER BY id DESC
|
||||
`, s.charID, pkt.DistType)
|
||||
|
||||
if err == nil {
|
||||
var itemDist Distribution
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&itemDist)
|
||||
if err != nil {
|
||||
s.logger.Error("Error parsing item distribution data", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
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))
|
||||
itemDists = append(itemDists, itemDist)
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint16(uint16(distCount))
|
||||
resp.WriteBytes(bf.Data())
|
||||
resp.WriteUint8(0)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
bf.WriteUint16(uint16(len(itemDists)))
|
||||
for _, dist := range itemDists {
|
||||
bf.WriteUint32(dist.ID)
|
||||
bf.WriteUint32(uint32(dist.Deadline.Unix()))
|
||||
bf.WriteUint32(0) // Unk
|
||||
bf.WriteUint16(dist.TimesAcceptable)
|
||||
bf.WriteUint16(dist.TimesAccepted)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G9 {
|
||||
bf.WriteUint16(0) // Unk
|
||||
}
|
||||
bf.WriteInt16(dist.MinHR)
|
||||
bf.WriteInt16(dist.MaxHR)
|
||||
bf.WriteInt16(dist.MinSR)
|
||||
bf.WriteInt16(dist.MaxSR)
|
||||
bf.WriteInt16(dist.MinGR)
|
||||
bf.WriteInt16(dist.MaxGR)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G7 {
|
||||
bf.WriteUint8(0) // Unk
|
||||
}
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G6 {
|
||||
bf.WriteUint16(0) // Unk
|
||||
}
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G8 {
|
||||
bf.WriteUint8(0) // Unk
|
||||
}
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G7 {
|
||||
bf.WriteUint16(0) // Unk
|
||||
bf.WriteUint16(0) // Unk
|
||||
}
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G10 {
|
||||
bf.WriteUint8(0) // Unk
|
||||
}
|
||||
ps.Uint8(bf, dist.EventName, true)
|
||||
k := 6
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G8 {
|
||||
k = 13
|
||||
}
|
||||
for i := 0; i < 6; i++ {
|
||||
for j := 0; j < k; j++ {
|
||||
bf.WriteUint8(0)
|
||||
bf.WriteUint32(0)
|
||||
}
|
||||
}
|
||||
if _config.ErupeConfig.RealClientMode >= _config.Z2 {
|
||||
i := uint8(0)
|
||||
bf.WriteUint8(i)
|
||||
if i <= 10 {
|
||||
for j := uint8(0); j < i; j++ {
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
type DistributionItem struct {
|
||||
ItemType uint8 `db:"item_type"`
|
||||
ID uint32 `db:"id"`
|
||||
ItemID uint32 `db:"item_id"`
|
||||
Quantity uint32 `db:"quantity"`
|
||||
}
|
||||
|
||||
func getDistributionItems(s *Session, i uint32) []DistributionItem {
|
||||
var distItems []DistributionItem
|
||||
rows, err := s.server.db.Queryx(`SELECT id, item_type, COALESCE(item_id, 0) AS item_id, COALESCE(quantity, 0) AS quantity FROM distribution_items WHERE distribution_id=$1`, i)
|
||||
if err == nil {
|
||||
var distItem DistributionItem
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&distItem)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
distItems = append(distItems, distItem)
|
||||
}
|
||||
}
|
||||
return distItems
|
||||
}
|
||||
|
||||
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))
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(pkt.DistributionID)
|
||||
distItems := getDistributionItems(s, pkt.DistributionID)
|
||||
bf.WriteUint16(uint16(len(distItems)))
|
||||
for _, item := range distItems {
|
||||
bf.WriteUint8(item.ItemType)
|
||||
bf.WriteUint32(item.ItemID)
|
||||
bf.WriteUint32(item.Quantity)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G8 {
|
||||
bf.WriteUint32(item.ID)
|
||||
}
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireDistItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAcquireDistItem)
|
||||
if pkt.DistributionID > 0 {
|
||||
_, err := s.server.db.Exec(`INSERT INTO public.distributions_accepted VALUES ($1, $2)`, pkt.DistributionID, s.charID)
|
||||
if err == nil {
|
||||
distItems := getDistributionItems(s, pkt.DistributionID)
|
||||
for _, item := range distItems {
|
||||
switch item.ItemType {
|
||||
case 17:
|
||||
_ = addPointNetcafe(s, int(item.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)", item.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)", item.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)", item.Quantity, s.charID)
|
||||
case 23:
|
||||
saveData, err := GetCharacterSaveData(s, s.charID)
|
||||
if err == nil {
|
||||
saveData.RP += uint16(item.Quantity)
|
||||
saveData.Save(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
|
||||
@@ -70,21 +70,21 @@ func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
|
||||
var timestamps []uint32
|
||||
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.DivaEvent >= 0 {
|
||||
if s.server.erupeConfig.DevModeOptions.DivaEvent == 0 {
|
||||
if s.server.erupeConfig.RealClientMode <= _config.Z1 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 32))
|
||||
} else {
|
||||
if s.server.erupeConfig.DebugOptions.DivaOverride >= 0 {
|
||||
if s.server.erupeConfig.DebugOptions.DivaOverride == 0 {
|
||||
if s.server.erupeConfig.RealClientMode >= _config.Z2 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 36))
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 32))
|
||||
}
|
||||
return
|
||||
}
|
||||
timestamps = generateDivaTimestamps(s, uint32(s.server.erupeConfig.DevModeOptions.DivaEvent), true)
|
||||
timestamps = generateDivaTimestamps(s, uint32(s.server.erupeConfig.DebugOptions.DivaOverride), true)
|
||||
} else {
|
||||
timestamps = generateDivaTimestamps(s, start, false)
|
||||
}
|
||||
|
||||
if s.server.erupeConfig.RealClientMode <= _config.Z1 {
|
||||
if s.server.erupeConfig.RealClientMode >= _config.Z2 {
|
||||
bf.WriteUint32(id)
|
||||
}
|
||||
for i := range timestamps {
|
||||
|
||||
@@ -10,44 +10,6 @@ import (
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
func handleMsgMhfRegisterEvent(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfRegisterEvent)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(pkt.Unk2)
|
||||
bf.WriteUint8(pkt.Unk4)
|
||||
bf.WriteUint16(0x1142)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfReleaseEvent(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReleaseEvent)
|
||||
|
||||
// Do this ack manually because it uses a non-(0|1) error code
|
||||
/*
|
||||
_ACK_SUCCESS = 0
|
||||
_ACK_ERROR = 1
|
||||
|
||||
_ACK_EINPROGRESS = 16
|
||||
_ACK_ENOENT = 17
|
||||
_ACK_ENOSPC = 18
|
||||
_ACK_ETIMEOUT = 19
|
||||
|
||||
_ACK_EINVALID = 64
|
||||
_ACK_EFAILED = 65
|
||||
_ACK_ENOMEM = 66
|
||||
_ACK_ENOTEXIT = 67
|
||||
_ACK_ENOTREADY = 68
|
||||
_ACK_EALREADY = 69
|
||||
_ACK_DISABLE_WORK = 71
|
||||
*/
|
||||
s.QueueSendMHF(&mhfpacket.MsgSysAck{
|
||||
AckHandle: pkt.AckHandle,
|
||||
IsBufferResponse: false,
|
||||
ErrorCode: 0x41,
|
||||
AckData: []byte{0x00, 0x00, 0x00, 0x00},
|
||||
})
|
||||
}
|
||||
|
||||
type Event struct {
|
||||
EventType uint16
|
||||
Unk1 uint16
|
||||
@@ -237,15 +199,11 @@ func handleMsgMhfUseKeepLoginBoost(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(0)
|
||||
switch pkt.BoostWeekUsed {
|
||||
case 1:
|
||||
fallthrough
|
||||
case 3:
|
||||
case 1, 3:
|
||||
expiration = TimeAdjusted().Add(120 * time.Minute)
|
||||
case 4:
|
||||
expiration = TimeAdjusted().Add(180 * time.Minute)
|
||||
case 2:
|
||||
fallthrough
|
||||
case 5:
|
||||
case 2, 5:
|
||||
expiration = TimeAdjusted().Add(240 * time.Minute)
|
||||
}
|
||||
bf.WriteUint32(uint32(expiration.Unix()))
|
||||
|
||||
@@ -92,8 +92,8 @@ func handleMsgMhfEnumerateRanking(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
|
||||
var timestamps []uint32
|
||||
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.TournamentEvent >= 0 {
|
||||
if s.server.erupeConfig.DevModeOptions.TournamentEvent == 0 {
|
||||
if s.server.erupeConfig.DebugOptions.TournamentOverride >= 0 {
|
||||
if s.server.erupeConfig.DebugOptions.TournamentOverride == 0 {
|
||||
bf.WriteBytes(make([]byte, 16))
|
||||
bf.WriteUint32(uint32(TimeAdjusted().Unix()))
|
||||
bf.WriteUint8(0)
|
||||
@@ -103,7 +103,7 @@ func handleMsgMhfEnumerateRanking(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
return
|
||||
}
|
||||
timestamps = generateTournamentTimestamps(uint32(s.server.erupeConfig.DevModeOptions.TournamentEvent), true)
|
||||
timestamps = generateTournamentTimestamps(uint32(s.server.erupeConfig.DebugOptions.TournamentOverride), true)
|
||||
} else {
|
||||
timestamps = generateTournamentTimestamps(start, false)
|
||||
}
|
||||
@@ -124,33 +124,33 @@ func handleMsgMhfEnumerateRanking(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
bf.WriteUint32(uint32(TimeAdjusted().Unix()))
|
||||
bf.WriteUint8(1) // TODO: Make this dynamic depending on timestamp
|
||||
ps.Uint8(bf, "", true)
|
||||
ps.Uint8(bf, "第150回公式狩猟大会", true)
|
||||
|
||||
// Temp direct port
|
||||
tournamentEvents := []TournamentEvent{
|
||||
{2644, 16, 0, 62151, ""},
|
||||
{2645, 16, 1, 62151, ""},
|
||||
{2646, 16, 2, 62151, ""},
|
||||
{2647, 16, 3, 62151, ""},
|
||||
{2648, 16, 4, 62151, ""},
|
||||
{2649, 16, 5, 62151, ""},
|
||||
{2650, 16, 6, 62151, ""},
|
||||
{2651, 16, 7, 62151, ""},
|
||||
{2652, 16, 8, 62151, ""},
|
||||
{2653, 16, 9, 62151, ""},
|
||||
{2654, 16, 10, 62151, ""},
|
||||
{2655, 16, 11, 62151, ""},
|
||||
{2656, 16, 12, 62151, ""},
|
||||
{2657, 16, 13, 62151, ""},
|
||||
{2658, 17, -1, 62150, ""},
|
||||
{2659, 6, 234, 0, ""},
|
||||
{2660, 6, 237, 0, ""},
|
||||
{2661, 6, 239, 0, ""},
|
||||
{2644, 16, 0, 60691, "爆霧竜討伐!"},
|
||||
{2645, 16, 1, 60691, "爆霧竜討伐!"},
|
||||
{2646, 16, 2, 60691, "爆霧竜討伐!"},
|
||||
{2647, 16, 3, 60691, "爆霧竜討伐!"},
|
||||
{2648, 16, 4, 60691, "爆霧竜討伐!"},
|
||||
{2649, 16, 5, 60691, "爆霧竜討伐!"},
|
||||
{2650, 16, 6, 60691, "爆霧竜討伐!"},
|
||||
{2651, 16, 7, 60691, "爆霧竜討伐!"},
|
||||
{2652, 16, 8, 60691, "爆霧竜討伐!"},
|
||||
{2653, 16, 9, 60691, "爆霧竜討伐!"},
|
||||
{2654, 16, 10, 60691, "爆霧竜討伐!"},
|
||||
{2655, 16, 11, 60691, "爆霧竜討伐!"},
|
||||
{2656, 16, 12, 60691, "爆霧竜討伐!"},
|
||||
{2657, 16, 13, 60691, "爆霧竜討伐!"},
|
||||
{2658, 17, -1, 60690, "みんなで爆霧竜討伐!"},
|
||||
{2659, 6, 234, 0, "キレアジ"},
|
||||
{2660, 6, 237, 0, "ハリマグロ"},
|
||||
{2661, 6, 239, 0, "カクサンデメキン"},
|
||||
}
|
||||
tournamentCups := []TournamentCup{
|
||||
{569, 6, 6, 0, "", ""},
|
||||
{570, 17, 7, 0, "", ""},
|
||||
{571, 16, 7, 0, "", ""},
|
||||
{569, 6, 6, 0, "個人 巨大魚杯", "~C05【競技内容】\n~C00クエストで釣った魚のサイズを競う\n~C04【対象魚】\n~C00キレアジ、\nハリマグロ、カクサンデメキン\n~C07【入賞賞品】\n~C00魚杯のしるし、タルネコ生産券、\nグーク生産券、グーク足生産券、\nグーク解放券(1〜3位)\n/猟団ポイント(1〜100位)\n/匠チケット+ハーフチケット白\n(1〜500位)\n~C03【開催期間】\n~C002019年11月22日 14:00から\n2019年11月25日 14:00まで"},
|
||||
{570, 17, 7, 0, "猟団 G級韋駄天杯", "~C05【競技内容】\n~C00≪みんなで爆霧竜討伐!≫を\n同じ猟団に所属する4人までの\n猟団員でいかに早くクリアするか\nを競う\n\n~C07【入賞賞品】\n~C00第147回狩人祭の魂(1〜200位)\n\n~C03【開催期間】\n~C002019年11月22日 14:00から\n2019年11月25日 14:00まで\n\n"},
|
||||
{571, 16, 7, 0, "個人 G級韋駄天杯", "~C05【競技内容】\n~C00≪爆霧竜討伐!≫を\nいかに早くクリアするかを競う\n\n~C07【入賞賞品】\n~C00王者のメダル(1位)\n/公式のしるし、タルネコ生産券、\nグーク生産券、グーク足生産券、\nグーク解放券(1〜3位)\n/猟団ポイント(1〜100位)\n/匠チケット+ハーフチケット白\n(1〜500位)\n~C03【開催期間】\n~C002019年11月22日 14:00から\n2019年11月25日 14:00まで"},
|
||||
}
|
||||
|
||||
bf.WriteUint16(uint16(len(tournamentEvents)))
|
||||
@@ -199,8 +199,9 @@ func handleMsgMhfEnumerateOrder(s *Session, p mhfpacket.MHFPacket) {
|
||||
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_submissions")
|
||||
s.server.db.Exec("DELETE FROM festa_prizes_accepted")
|
||||
s.server.db.Exec("UPDATE guild_characters SET souls=0")
|
||||
s.server.db.Exec("UPDATE guild_characters SET trial_vote=NULL")
|
||||
}
|
||||
|
||||
func generateFestaTimestamps(s *Session, start uint32, debug bool) []uint32 {
|
||||
@@ -245,13 +246,13 @@ func generateFestaTimestamps(s *Session, start uint32, debug bool) []uint32 {
|
||||
}
|
||||
|
||||
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
|
||||
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 FestivalColor `db:"monopoly"`
|
||||
Unk uint16
|
||||
}
|
||||
|
||||
@@ -277,12 +278,12 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
|
||||
var timestamps []uint32
|
||||
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.FestaEvent >= 0 {
|
||||
if s.server.erupeConfig.DevModeOptions.FestaEvent == 0 {
|
||||
if s.server.erupeConfig.DebugOptions.FestaOverride >= 0 {
|
||||
if s.server.erupeConfig.DebugOptions.FestaOverride == 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
timestamps = generateFestaTimestamps(s, uint32(s.server.erupeConfig.DevModeOptions.FestaEvent), true)
|
||||
timestamps = generateFestaTimestamps(s, uint32(s.server.erupeConfig.DebugOptions.FestaOverride), true)
|
||||
} else {
|
||||
timestamps = generateFestaTimestamps(s, start, false)
|
||||
}
|
||||
@@ -293,8 +294,8 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
|
||||
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)
|
||||
s.server.db.QueryRow(`SELECT COALESCE(SUM(fs.souls), 0) AS souls FROM festa_registrations fr LEFT JOIN festa_submissions fs ON fr.guild_id = fs.guild_id AND fr.team = 'blue'`).Scan(&blueSouls)
|
||||
s.server.db.QueryRow(`SELECT COALESCE(SUM(fs.souls), 0) AS souls FROM festa_registrations fr LEFT JOIN festa_submissions fs ON fr.guild_id = fs.guild_id AND fr.team = 'red'`).Scan(&redSouls)
|
||||
|
||||
bf.WriteUint32(id)
|
||||
for _, timestamp := range timestamps {
|
||||
@@ -309,7 +310,19 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
var trials []FestaTrial
|
||||
var trial FestaTrial
|
||||
rows, _ = s.server.db.Queryx("SELECT * FROM festa_trials")
|
||||
rows, _ = s.server.db.Queryx(`SELECT ft.*,
|
||||
COALESCE(CASE
|
||||
WHEN COUNT(gc.id) FILTER (WHERE fr.team = 'blue' AND gc.trial_vote = ft.id) >
|
||||
COUNT(gc.id) FILTER (WHERE fr.team = 'red' AND gc.trial_vote = ft.id)
|
||||
THEN CAST('blue' AS public.festival_color)
|
||||
WHEN COUNT(gc.id) FILTER (WHERE fr.team = 'red' AND gc.trial_vote = ft.id) >
|
||||
COUNT(gc.id) FILTER (WHERE fr.team = 'blue' AND gc.trial_vote = ft.id)
|
||||
THEN CAST('red' AS public.festival_color)
|
||||
END, CAST('none' AS public.festival_color)) AS monopoly
|
||||
FROM public.festa_trials ft
|
||||
LEFT JOIN public.guild_characters gc ON ft.id = gc.trial_vote
|
||||
LEFT JOIN public.festa_registrations fr ON gc.guild_id = fr.guild_id
|
||||
GROUP BY ft.id`)
|
||||
for rows.Next() {
|
||||
err := rows.StructScan(&trial)
|
||||
if err != nil {
|
||||
@@ -325,12 +338,14 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteUint16(trial.TimesReq)
|
||||
bf.WriteUint16(trial.Locale)
|
||||
bf.WriteUint16(trial.Reward)
|
||||
trial.Monopoly = 0xFFFF // NYI
|
||||
bf.WriteUint16(trial.Monopoly)
|
||||
bf.WriteUint16(trial.Unk)
|
||||
bf.WriteInt16(FestivalColorCodes[trial.Monopoly])
|
||||
if _config.ErupeConfig.RealClientMode >= _config.F4 { // Not in S6.0
|
||||
bf.WriteUint16(trial.Unk)
|
||||
}
|
||||
}
|
||||
|
||||
// The Winner and Loser Armor IDs are missing
|
||||
// Item 7011 may not exist in older versions, remove to prevent crashes
|
||||
rewards := []FestaReward{
|
||||
{1, 0, 7, 350, 1520, 0, 0, 0},
|
||||
{1, 0, 7, 1000, 7011, 0, 0, 1},
|
||||
@@ -358,6 +373,7 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
{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)
|
||||
@@ -365,11 +381,13 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteUint16(reward.ItemType)
|
||||
bf.WriteUint16(reward.Quantity)
|
||||
bf.WriteUint16(reward.ItemID)
|
||||
bf.WriteUint16(reward.Unk5)
|
||||
bf.WriteUint16(reward.Unk6)
|
||||
bf.WriteUint8(reward.Unk7)
|
||||
// Not confirmed to be G1 but exists in G3
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G1 {
|
||||
bf.WriteUint16(reward.Unk5)
|
||||
bf.WriteUint16(reward.Unk6)
|
||||
bf.WriteUint8(reward.Unk7)
|
||||
}
|
||||
}
|
||||
|
||||
if _config.ErupeConfig.RealClientMode <= _config.G61 {
|
||||
if s.server.erupeConfig.GameplayOptions.MaximumFP > 0xFFFF {
|
||||
s.server.erupeConfig.GameplayOptions.MaximumFP = 0xFFFF
|
||||
@@ -380,37 +398,62 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
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
|
||||
var temp uint32
|
||||
bf.WriteUint16(4)
|
||||
for i := uint16(0); i < 4; i++ {
|
||||
var guildID uint32
|
||||
var guildName string
|
||||
var guildTeam = FestivalColorNone
|
||||
s.server.db.QueryRow(`
|
||||
SELECT fs.guild_id, g.name, fr.team, SUM(fs.souls) as _
|
||||
FROM festa_submissions fs
|
||||
LEFT JOIN festa_registrations fr ON fs.guild_id = fr.guild_id
|
||||
LEFT JOIN guilds g ON fs.guild_id = g.id
|
||||
WHERE fs.trial_type = $1
|
||||
GROUP BY fs.guild_id, g.name, fr.team
|
||||
ORDER BY _ DESC LIMIT 1
|
||||
`, i+1).Scan(&guildID, &guildName, &guildTeam, &temp)
|
||||
bf.WriteUint32(guildID)
|
||||
bf.WriteUint16(i + 1)
|
||||
bf.WriteInt16(FestivalColorCodes[guildTeam])
|
||||
ps.Uint8(bf, guildName, true)
|
||||
}
|
||||
bf.WriteUint16(7)
|
||||
for i := uint16(0); i < 7; i++ {
|
||||
var guildID uint32
|
||||
var guildName string
|
||||
var guildTeam = FestivalColorNone
|
||||
offset := 86400 * uint32(i)
|
||||
s.server.db.QueryRow(`
|
||||
SELECT fs.guild_id, g.name, fr.team, SUM(fs.souls) as _
|
||||
FROM festa_submissions fs
|
||||
LEFT JOIN festa_registrations fr ON fs.guild_id = fr.guild_id
|
||||
LEFT JOIN guilds g ON fs.guild_id = g.id
|
||||
WHERE EXTRACT(EPOCH FROM fs.timestamp)::int > $1 AND EXTRACT(EPOCH FROM fs.timestamp)::int < $2
|
||||
GROUP BY fs.guild_id, g.name, fr.team
|
||||
ORDER BY _ DESC LIMIT 1
|
||||
`, timestamps[1]+offset, timestamps[1]+offset+86400).Scan(&guildID, &guildName, &guildTeam, &temp)
|
||||
bf.WriteUint32(guildID)
|
||||
bf.WriteUint16(i + 1)
|
||||
bf.WriteInt16(FestivalColorCodes[guildTeam])
|
||||
ps.Uint8(bf, guildName, true)
|
||||
}
|
||||
|
||||
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
|
||||
bf.WriteUint32(0) // Clan goal
|
||||
// Final bonus rates
|
||||
bf.WriteUint32(5000) // 5000+ souls
|
||||
bf.WriteUint32(2000) // 2000-4999 souls
|
||||
bf.WriteUint32(1000) // 1000-1999 souls
|
||||
bf.WriteUint32(100) // 100-999 souls
|
||||
bf.WriteUint16(300) // 300% bonus
|
||||
bf.WriteUint16(200) // 200% bonus
|
||||
bf.WriteUint16(150) // 150% bonus
|
||||
bf.WriteUint16(100) // Normal rate
|
||||
bf.WriteUint16(50) // 50% penalty
|
||||
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G52 {
|
||||
ps.Uint16(bf, "", false)
|
||||
}
|
||||
|
||||
// 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())
|
||||
}
|
||||
|
||||
@@ -427,7 +470,7 @@ func handleMsgMhfStateFestaU(s *Session, p mhfpacket.MHFPacket) {
|
||||
return
|
||||
}
|
||||
var souls, exists uint32
|
||||
s.server.db.QueryRow("SELECT souls FROM guild_characters WHERE character_id=$1", s.charID).Scan(&souls)
|
||||
s.server.db.QueryRow(`SELECT COALESCE((SELECT SUM(souls) FROM festa_submissions WHERE character_id=$1), 0)`, 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)
|
||||
@@ -438,7 +481,6 @@ func handleMsgMhfStateFestaU(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteBool(false)
|
||||
bf.WriteBool(true)
|
||||
}
|
||||
bf.WriteUint16(0) // Unk
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
@@ -453,18 +495,18 @@ func handleMsgMhfStateFestaG(s *Session, p mhfpacket.MHFPacket) {
|
||||
resp := byteframe.NewByteFrame()
|
||||
if err != nil || guild == nil || applicant {
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(0xFFFFFFFF)
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteInt32(0)
|
||||
resp.WriteInt32(-1)
|
||||
resp.WriteInt32(0)
|
||||
resp.WriteInt32(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
|
||||
resp.WriteInt32(1) // unk
|
||||
resp.WriteInt32(1) // unk, rank?
|
||||
resp.WriteInt32(1) // unk
|
||||
resp.WriteInt32(1) // unk
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
@@ -480,21 +522,33 @@ func handleMsgMhfEnumerateFestaMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
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
|
||||
})
|
||||
var validMembers []*GuildMember
|
||||
for _, member := range members {
|
||||
if member.Souls > 0 {
|
||||
validMembers = append(validMembers, member)
|
||||
}
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(uint16(len(validMembers)))
|
||||
bf.WriteUint16(0) // Unk
|
||||
for _, member := range validMembers {
|
||||
bf.WriteUint32(member.CharID)
|
||||
bf.WriteUint32(member.Souls)
|
||||
if _config.ErupeConfig.RealClientMode <= _config.Z1 {
|
||||
bf.WriteUint16(uint16(member.Souls))
|
||||
bf.WriteUint16(0)
|
||||
} else {
|
||||
bf.WriteUint32(member.Souls)
|
||||
}
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfVoteFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfVoteFesta)
|
||||
s.server.db.Exec(`UPDATE guild_characters SET trial_vote=$1 WHERE character_id=$2`, pkt.TrialID, s.charID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
@@ -519,7 +573,14 @@ func handleMsgMhfEntryFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
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)
|
||||
tx, _ := s.server.db.Begin()
|
||||
for i := range pkt.Souls {
|
||||
if pkt.Souls[i] == 0 {
|
||||
continue
|
||||
}
|
||||
_, _ = tx.Exec(`INSERT INTO festa_submissions VALUES ($1, $2, $3, $4, now())`, s.charID, pkt.GuildID, i, pkt.Souls[i])
|
||||
}
|
||||
_ = tx.Commit()
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
_config "erupe-ce/config"
|
||||
@@ -22,18 +21,18 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type FestivalColour string
|
||||
type FestivalColor string
|
||||
|
||||
const (
|
||||
FestivalColourNone FestivalColour = "none"
|
||||
FestivalColourRed FestivalColour = "red"
|
||||
FestivalColourBlue FestivalColour = "blue"
|
||||
FestivalColorNone FestivalColor = "none"
|
||||
FestivalColorBlue FestivalColor = "blue"
|
||||
FestivalColorRed FestivalColor = "red"
|
||||
)
|
||||
|
||||
var FestivalColourCodes = map[FestivalColour]uint8{
|
||||
FestivalColourBlue: 0x00,
|
||||
FestivalColourRed: 0x01,
|
||||
FestivalColourNone: 0xFF,
|
||||
var FestivalColorCodes = map[FestivalColor]int16{
|
||||
FestivalColorNone: -1,
|
||||
FestivalColorBlue: 0,
|
||||
FestivalColorRed: 1,
|
||||
}
|
||||
|
||||
type GuildApplicationType string
|
||||
@@ -44,27 +43,27 @@ const (
|
||||
)
|
||||
|
||||
type Guild struct {
|
||||
ID uint32 `db:"id"`
|
||||
Name string `db:"name"`
|
||||
MainMotto uint8 `db:"main_motto"`
|
||||
SubMotto uint8 `db:"sub_motto"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
MemberCount uint16 `db:"member_count"`
|
||||
RankRP uint32 `db:"rank_rp"`
|
||||
EventRP uint32 `db:"event_rp"`
|
||||
Comment string `db:"comment"`
|
||||
PugiName1 string `db:"pugi_name_1"`
|
||||
PugiName2 string `db:"pugi_name_2"`
|
||||
PugiName3 string `db:"pugi_name_3"`
|
||||
PugiOutfit1 uint8 `db:"pugi_outfit_1"`
|
||||
PugiOutfit2 uint8 `db:"pugi_outfit_2"`
|
||||
PugiOutfit3 uint8 `db:"pugi_outfit_3"`
|
||||
PugiOutfits uint32 `db:"pugi_outfits"`
|
||||
Recruiting bool `db:"recruiting"`
|
||||
FestivalColour FestivalColour `db:"festival_colour"`
|
||||
Souls uint32 `db:"souls"`
|
||||
AllianceID uint32 `db:"alliance_id"`
|
||||
Icon *GuildIcon `db:"icon"`
|
||||
ID uint32 `db:"id"`
|
||||
Name string `db:"name"`
|
||||
MainMotto uint8 `db:"main_motto"`
|
||||
SubMotto uint8 `db:"sub_motto"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
MemberCount uint16 `db:"member_count"`
|
||||
RankRP uint32 `db:"rank_rp"`
|
||||
EventRP uint32 `db:"event_rp"`
|
||||
Comment string `db:"comment"`
|
||||
PugiName1 string `db:"pugi_name_1"`
|
||||
PugiName2 string `db:"pugi_name_2"`
|
||||
PugiName3 string `db:"pugi_name_3"`
|
||||
PugiOutfit1 uint8 `db:"pugi_outfit_1"`
|
||||
PugiOutfit2 uint8 `db:"pugi_outfit_2"`
|
||||
PugiOutfit3 uint8 `db:"pugi_outfit_3"`
|
||||
PugiOutfits uint32 `db:"pugi_outfits"`
|
||||
Recruiting bool `db:"recruiting"`
|
||||
FestivalColor FestivalColor `db:"festival_color"`
|
||||
Souls uint32 `db:"souls"`
|
||||
AllianceID uint32 `db:"alliance_id"`
|
||||
Icon *GuildIcon `db:"icon"`
|
||||
|
||||
GuildLeader
|
||||
}
|
||||
@@ -158,7 +157,7 @@ SELECT
|
||||
sub_motto,
|
||||
created_at,
|
||||
leader_id,
|
||||
lc.name as leader_name,
|
||||
c.name AS leader_name,
|
||||
comment,
|
||||
COALESCE(pugi_name_1, '') AS pugi_name_1,
|
||||
COALESCE(pugi_name_2, '') AS pugi_name_2,
|
||||
@@ -168,8 +167,8 @@ SELECT
|
||||
pugi_outfit_3,
|
||||
pugi_outfits,
|
||||
recruiting,
|
||||
COALESCE((SELECT team FROM festa_registrations fr WHERE fr.guild_id = g.id), 'none') AS festival_colour,
|
||||
(SELECT SUM(souls) FROM guild_characters gc WHERE gc.guild_id = g.id) AS souls,
|
||||
COALESCE((SELECT team FROM festa_registrations fr WHERE fr.guild_id = g.id), 'none') AS festival_color,
|
||||
COALESCE((SELECT SUM(fs.souls) FROM festa_submissions fs WHERE fs.guild_id=g.id), 0) AS souls,
|
||||
COALESCE((
|
||||
SELECT id FROM guild_alliances ga WHERE
|
||||
ga.parent_id = g.id OR
|
||||
@@ -179,8 +178,8 @@ SELECT
|
||||
icon,
|
||||
(SELECT count(1) FROM guild_characters gc WHERE gc.guild_id = g.id) AS member_count
|
||||
FROM guilds g
|
||||
JOIN guild_characters lgc ON lgc.character_id = leader_id
|
||||
JOIN characters lc on leader_id = lc.id
|
||||
JOIN guild_characters gc ON gc.character_id = leader_id
|
||||
JOIN characters c on leader_id = c.id
|
||||
`
|
||||
|
||||
func (guild *Guild) Save(s *Session) error {
|
||||
@@ -968,7 +967,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteUint8(uint8(len(guildLeaderName)))
|
||||
bf.WriteBytes(guildName)
|
||||
bf.WriteBytes(guildComment)
|
||||
bf.WriteUint8(FestivalColourCodes[guild.FestivalColour])
|
||||
bf.WriteInt8(int8(FestivalColorCodes[guild.FestivalColor]))
|
||||
bf.WriteUint32(guild.RankRP)
|
||||
bf.WriteBytes(guildLeaderName)
|
||||
bf.WriteUint32(0) // Unk
|
||||
@@ -990,20 +989,21 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
bf.WriteUint32(guild.PugiOutfits)
|
||||
|
||||
if guild.Rank() >= 3 {
|
||||
bf.WriteUint8(40)
|
||||
} else if guild.Rank() >= 7 {
|
||||
bf.WriteUint8(50)
|
||||
} else if guild.Rank() >= 10 {
|
||||
bf.WriteUint8(60)
|
||||
} else {
|
||||
bf.WriteUint8(30)
|
||||
limit := s.server.erupeConfig.GameplayOptions.ClanMemberLimits[0][1]
|
||||
for _, j := range s.server.erupeConfig.GameplayOptions.ClanMemberLimits {
|
||||
if guild.Rank() >= uint16(j[0]) {
|
||||
limit = j[1]
|
||||
}
|
||||
}
|
||||
if limit > 100 {
|
||||
limit = 100
|
||||
}
|
||||
bf.WriteUint8(limit)
|
||||
|
||||
bf.WriteUint32(55000)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint16(0) // Changing Room RP
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(0) // Ignored
|
||||
|
||||
if guild.AllianceID > 0 {
|
||||
alliance, err := GetAllianceData(s, guild.AllianceID)
|
||||
@@ -1013,7 +1013,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteUint32(alliance.ID)
|
||||
bf.WriteUint32(uint32(alliance.CreatedAt.Unix()))
|
||||
bf.WriteUint16(alliance.TotalMembers)
|
||||
bf.WriteUint8(0)
|
||||
bf.WriteUint8(0) // Ignored
|
||||
bf.WriteUint8(0)
|
||||
ps.Uint16(bf, alliance.Name, true)
|
||||
if alliance.SubGuild1ID > 0 {
|
||||
@@ -1148,7 +1148,6 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
var alliances []*GuildAlliance
|
||||
var rows *sqlx.Rows
|
||||
var err error
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.Data1)
|
||||
|
||||
if pkt.Type <= 8 {
|
||||
var tempGuilds []*Guild
|
||||
@@ -1165,20 +1164,20 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
switch pkt.Type {
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_GUILD_NAME:
|
||||
for _, guild := range tempGuilds {
|
||||
if strings.Contains(guild.Name, pkt.Data2) {
|
||||
if strings.Contains(guild.Name, stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes())) {
|
||||
guilds = append(guilds, guild)
|
||||
}
|
||||
}
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_NAME:
|
||||
for _, guild := range tempGuilds {
|
||||
if strings.Contains(guild.LeaderName, pkt.Data2) {
|
||||
if strings.Contains(guild.LeaderName, stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes())) {
|
||||
guilds = append(guilds, guild)
|
||||
}
|
||||
}
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_ID:
|
||||
ID := bf.ReadUint32()
|
||||
CID := pkt.Data1.ReadUint32()
|
||||
for _, guild := range tempGuilds {
|
||||
if guild.LeaderCharID == ID {
|
||||
if guild.LeaderCharID == CID {
|
||||
guilds = append(guilds, guild)
|
||||
}
|
||||
}
|
||||
@@ -1216,15 +1215,15 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
guilds = tempGuilds
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_MOTTO:
|
||||
mainMotto := uint8(bf.ReadUint16())
|
||||
subMotto := uint8(bf.ReadUint16())
|
||||
mainMotto := uint8(pkt.Data1.ReadUint16())
|
||||
subMotto := uint8(pkt.Data1.ReadUint16())
|
||||
for _, guild := range tempGuilds {
|
||||
if guild.MainMotto == mainMotto && guild.SubMotto == subMotto {
|
||||
guilds = append(guilds, guild)
|
||||
}
|
||||
}
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_RECRUITING:
|
||||
recruitingMotto := uint8(bf.ReadUint16())
|
||||
recruitingMotto := uint8(pkt.Data1.ReadUint16())
|
||||
for _, guild := range tempGuilds {
|
||||
if guild.MainMotto == recruitingMotto {
|
||||
guilds = append(guilds, guild)
|
||||
@@ -1245,20 +1244,20 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
switch pkt.Type {
|
||||
case mhfpacket.ENUMERATE_ALLIANCE_TYPE_ALLIANCE_NAME:
|
||||
for _, alliance := range tempAlliances {
|
||||
if strings.Contains(alliance.Name, pkt.Data2) {
|
||||
if strings.Contains(alliance.Name, stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes())) {
|
||||
alliances = append(alliances, alliance)
|
||||
}
|
||||
}
|
||||
case mhfpacket.ENUMERATE_ALLIANCE_TYPE_LEADER_NAME:
|
||||
for _, alliance := range tempAlliances {
|
||||
if strings.Contains(alliance.ParentGuild.LeaderName, pkt.Data2) {
|
||||
if strings.Contains(alliance.ParentGuild.LeaderName, stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes())) {
|
||||
alliances = append(alliances, alliance)
|
||||
}
|
||||
}
|
||||
case mhfpacket.ENUMERATE_ALLIANCE_TYPE_LEADER_ID:
|
||||
ID := bf.ReadUint32()
|
||||
CID := pkt.Data1.ReadUint32()
|
||||
for _, alliance := range tempAlliances {
|
||||
if alliance.ParentGuild.LeaderCharID == ID {
|
||||
if alliance.ParentGuild.LeaderCharID == CID {
|
||||
alliances = append(alliances, alliance)
|
||||
}
|
||||
}
|
||||
@@ -1292,7 +1291,7 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
return
|
||||
}
|
||||
|
||||
bf = byteframe.NewByteFrame()
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
if pkt.Type > 8 {
|
||||
hasNextPage := false
|
||||
@@ -1428,7 +1427,7 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
bf.WriteUint16(guild.MemberCount)
|
||||
bf.WriteUint16(uint16(len(guildMembers)))
|
||||
|
||||
sort.Slice(guildMembers[:], func(i, j int) bool {
|
||||
return guildMembers[i].OrderIndex < guildMembers[j].OrderIndex
|
||||
@@ -1437,7 +1436,7 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
for _, member := range guildMembers {
|
||||
bf.WriteUint32(member.CharID)
|
||||
bf.WriteUint16(member.HRP)
|
||||
if s.server.erupeConfig.RealClientMode > _config.G7 {
|
||||
if s.server.erupeConfig.RealClientMode >= _config.G10 {
|
||||
bf.WriteUint16(member.GR)
|
||||
}
|
||||
if s.server.erupeConfig.RealClientMode < _config.ZZ {
|
||||
@@ -1461,7 +1460,7 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
|
||||
if guild.AllianceID > 0 {
|
||||
bf.WriteUint16(alliance.TotalMembers - guild.MemberCount)
|
||||
bf.WriteUint16(alliance.TotalMembers - uint16(len(guildMembers)))
|
||||
if guild.ID != alliance.ParentGuildID {
|
||||
mems, err := GetGuildMembers(s, alliance.ParentGuildID, false)
|
||||
if err != nil {
|
||||
@@ -1559,7 +1558,7 @@ func handleMsgMhfEnumerateGuildItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateGuildItem)
|
||||
var boxContents []byte
|
||||
bf := byteframe.NewByteFrame()
|
||||
err := s.server.db.QueryRow("SELECT item_box FROM guilds WHERE id = $1", int(pkt.GuildId)).Scan(&boxContents)
|
||||
err := s.server.db.QueryRow("SELECT item_box FROM guilds WHERE id = $1", pkt.GuildID).Scan(&boxContents)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get guild item box contents from db", zap.Error(err))
|
||||
bf.WriteBytes(make([]byte, 4))
|
||||
@@ -1593,7 +1592,7 @@ func handleMsgMhfUpdateGuildItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
// Get item cache from DB
|
||||
var boxContents []byte
|
||||
var oldItems []Item
|
||||
err := s.server.db.QueryRow("SELECT item_box FROM guilds WHERE id = $1", int(pkt.GuildId)).Scan(&boxContents)
|
||||
err := s.server.db.QueryRow("SELECT item_box FROM guilds WHERE id = $1", pkt.GuildID).Scan(&boxContents)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get guild item box contents from db", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
@@ -1610,16 +1609,16 @@ func handleMsgMhfUpdateGuildItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
// Update item stacks
|
||||
newItems := make([]Item, len(oldItems))
|
||||
copy(newItems, oldItems)
|
||||
for i := 0; i < int(pkt.Amount); i++ {
|
||||
for i := 0; i < len(pkt.Items); i++ {
|
||||
for j := 0; j <= len(oldItems); j++ {
|
||||
if j == len(oldItems) {
|
||||
var newItem Item
|
||||
newItem.ItemId = pkt.Items[i].ItemId
|
||||
newItem.ItemId = pkt.Items[i].ItemID
|
||||
newItem.Amount = pkt.Items[i].Amount
|
||||
newItems = append(newItems, newItem)
|
||||
break
|
||||
}
|
||||
if pkt.Items[i].ItemId == oldItems[j].ItemId {
|
||||
if pkt.Items[i].ItemID == oldItems[j].ItemId {
|
||||
newItems[j].Amount = pkt.Items[i].Amount
|
||||
break
|
||||
}
|
||||
@@ -1643,7 +1642,7 @@ func handleMsgMhfUpdateGuildItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
|
||||
// Upload new item cache
|
||||
_, err = s.server.db.Exec("UPDATE guilds SET item_box = $1 WHERE id = $2", bf.Data(), int(pkt.GuildId))
|
||||
_, err = s.server.db.Exec("UPDATE guilds SET item_box = $1 WHERE id = $2", bf.Data(), pkt.GuildID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to update guild item box contents in db", zap.Error(err))
|
||||
}
|
||||
@@ -1678,7 +1677,7 @@ func handleMsgMhfUpdateGuildIcon(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
icon := &GuildIcon{}
|
||||
|
||||
icon.Parts = make([]GuildIconPart, pkt.PartCount)
|
||||
icon.Parts = make([]GuildIconPart, len(pkt.IconParts))
|
||||
|
||||
for i, p := range pkt.IconParts {
|
||||
icon.Parts[i] = GuildIconPart{
|
||||
@@ -1723,16 +1722,51 @@ func handleMsgMhfReadGuildcard(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
type GuildMission struct {
|
||||
ID uint32
|
||||
Unk uint32
|
||||
Type uint16
|
||||
Goal uint16
|
||||
Quantity uint16
|
||||
SkipTickets uint16
|
||||
GR bool
|
||||
RewardType uint16
|
||||
RewardLevel uint16
|
||||
}
|
||||
|
||||
func handleMsgMhfGetGuildMissionList(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGuildMissionList)
|
||||
|
||||
decoded, err := hex.DecodeString("000694610000023E000112990023000100000200015DDD232100069462000002F30000005F000C000200000300025DDD232100069463000002EA0000005F0006000100000100015DDD23210006946400000245000000530010000200000400025DDD232100069465000002B60001129B0019000100000200015DDD232100069466000003DC0000001B0010000100000600015DDD232100069467000002DA000112A00019000100000400015DDD232100069468000002A800010DEF0032000200000200025DDD2321000694690000045500000022003C000200000600025DDD23210006946A00000080000122D90046000200000300025DDD23210006946B000001960000003B000A000100000100015DDD23210006946C0000049200000046005A000300000600035DDD23210006946D000000A4000000260018000200000600025DDD23210006946E0000017A00010DE40096000300000100035DDD23210006946F000001BE0000005E0014000200000400025DDD2355000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
bf := byteframe.NewByteFrame()
|
||||
missions := []GuildMission{
|
||||
{431201, 574, 1, 4761, 35, 1, false, 2, 1},
|
||||
{431202, 755, 0, 95, 12, 2, false, 3, 2},
|
||||
{431203, 746, 0, 95, 6, 1, false, 1, 1},
|
||||
{431204, 581, 0, 83, 16, 2, false, 4, 2},
|
||||
{431205, 694, 1, 4763, 25, 1, false, 2, 1},
|
||||
{431206, 988, 0, 27, 16, 1, false, 6, 1},
|
||||
{431207, 730, 1, 4768, 25, 1, false, 4, 1},
|
||||
{431208, 680, 1, 3567, 50, 2, false, 2, 2},
|
||||
{431209, 1109, 0, 34, 60, 2, false, 6, 2},
|
||||
{431210, 128, 1, 8921, 70, 2, false, 3, 2},
|
||||
{431211, 406, 0, 59, 10, 1, false, 1, 1},
|
||||
{431212, 1170, 0, 70, 90, 3, false, 6, 3},
|
||||
{431213, 164, 0, 38, 24, 2, false, 6, 2},
|
||||
{431214, 378, 1, 3556, 150, 3, false, 1, 3},
|
||||
{431215, 446, 0, 94, 20, 2, false, 4, 2},
|
||||
}
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, decoded)
|
||||
for _, mission := range missions {
|
||||
bf.WriteUint32(mission.ID)
|
||||
bf.WriteUint32(mission.Unk)
|
||||
bf.WriteUint16(mission.Type)
|
||||
bf.WriteUint16(mission.Goal)
|
||||
bf.WriteUint16(mission.Quantity)
|
||||
bf.WriteUint16(mission.SkipTickets)
|
||||
bf.WriteBool(mission.GR)
|
||||
bf.WriteUint16(mission.RewardType)
|
||||
bf.WriteUint16(mission.RewardLevel)
|
||||
bf.WriteUint32(uint32(TimeAdjusted().Unix()))
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfGetGuildMissionRecord(s *Session, p mhfpacket.MHFPacket) {
|
||||
@@ -1798,18 +1832,18 @@ func handleMsgMhfLoadGuildCooking(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfRegistGuildCooking(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfRegistGuildCooking)
|
||||
guild, _ := GetGuildInfoByCharacterId(s, s.charID)
|
||||
currentTime := TimeAdjusted().Add(time.Duration(s.server.erupeConfig.GameplayOptions.GuildMealDuration-60) * time.Minute)
|
||||
startTime := TimeAdjusted().Add(time.Duration(s.server.erupeConfig.GameplayOptions.ClanMealDuration-3600) * time.Second)
|
||||
if pkt.OverwriteID != 0 {
|
||||
s.server.db.Exec("UPDATE guild_meals SET meal_id = $1, level = $2, created_at = $3 WHERE id = $4", pkt.MealID, pkt.Success, currentTime, pkt.OverwriteID)
|
||||
s.server.db.Exec("UPDATE guild_meals SET meal_id = $1, level = $2, created_at = $3 WHERE id = $4", pkt.MealID, pkt.Success, startTime, pkt.OverwriteID)
|
||||
} else {
|
||||
s.server.db.QueryRow("INSERT INTO guild_meals (guild_id, meal_id, level, created_at) VALUES ($1, $2, $3, $4) RETURNING id", guild.ID, pkt.MealID, pkt.Success, currentTime).Scan(&pkt.OverwriteID)
|
||||
s.server.db.QueryRow("INSERT INTO guild_meals (guild_id, meal_id, level, created_at) VALUES ($1, $2, $3, $4) RETURNING id", guild.ID, pkt.MealID, pkt.Success, startTime).Scan(&pkt.OverwriteID)
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(1)
|
||||
bf.WriteUint32(pkt.OverwriteID)
|
||||
bf.WriteUint32(uint32(pkt.MealID))
|
||||
bf.WriteUint32(uint32(pkt.Success))
|
||||
bf.WriteUint32(uint32(currentTime.Unix()))
|
||||
bf.WriteUint32(uint32(startTime.Unix()))
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
@@ -1817,14 +1851,14 @@ func handleMsgMhfGetGuildWeeklyBonusMaster(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGuildWeeklyBonusMaster)
|
||||
|
||||
// Values taken from brand new guild capture
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0x28))
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 40))
|
||||
}
|
||||
func handleMsgMhfGetGuildWeeklyBonusActiveCount(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGuildWeeklyBonusActiveCount)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(0x3C) // Active count
|
||||
bf.WriteUint8(0x3C) // Current active count
|
||||
bf.WriteUint8(0x00) // New active count
|
||||
bf.WriteUint8(60) // Active count
|
||||
bf.WriteUint8(60) // Current active count
|
||||
bf.WriteUint8(0) // New active count
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
@@ -1832,18 +1866,54 @@ func handleMsgMhfGuildHuntdata(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGuildHuntdata)
|
||||
bf := byteframe.NewByteFrame()
|
||||
switch pkt.Operation {
|
||||
case 0: // Unk
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{})
|
||||
case 1: // Get Huntdata
|
||||
case 0: // Acquire
|
||||
s.server.db.Exec(`UPDATE guild_characters SET box_claimed=$1 WHERE character_id=$2`, TimeAdjusted(), s.charID)
|
||||
case 1: // Enumerate
|
||||
bf.WriteUint8(0) // Entries
|
||||
/* Entry format
|
||||
uint32 UnkID
|
||||
uint32 MonID
|
||||
*/
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
case 2: // Unk, controls glow
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00})
|
||||
rows, err := s.server.db.Query(`SELECT kl.id, kl.monster FROM kill_logs kl
|
||||
INNER JOIN guild_characters gc ON kl.character_id = gc.character_id
|
||||
WHERE gc.guild_id=$1
|
||||
AND kl.timestamp >= (SELECT box_claimed FROM guild_characters WHERE character_id=$2)
|
||||
`, pkt.GuildID, s.charID)
|
||||
if err == nil {
|
||||
var count uint8
|
||||
var huntID, monID uint32
|
||||
for rows.Next() {
|
||||
err = rows.Scan(&huntID, &monID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
count++
|
||||
if count > 255 {
|
||||
count = 255
|
||||
rows.Close()
|
||||
break
|
||||
}
|
||||
bf.WriteUint32(huntID)
|
||||
bf.WriteUint32(monID)
|
||||
}
|
||||
bf.Seek(0, 0)
|
||||
bf.WriteUint8(count)
|
||||
}
|
||||
case 2: // Check
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
if err == nil {
|
||||
var count uint8
|
||||
err = s.server.db.QueryRow(`SELECT COUNT(*) FROM kill_logs kl
|
||||
INNER JOIN guild_characters gc ON kl.character_id = gc.character_id
|
||||
WHERE gc.guild_id=$1
|
||||
AND kl.timestamp >= (SELECT box_claimed FROM guild_characters WHERE character_id=$2)
|
||||
`, guild.ID, s.charID).Scan(&count)
|
||||
if err == nil && count > 0 {
|
||||
bf.WriteBool(true)
|
||||
} else {
|
||||
bf.WriteBool(false)
|
||||
}
|
||||
} else {
|
||||
bf.WriteBool(false)
|
||||
}
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
type MessageBoardPost struct {
|
||||
|
||||
@@ -162,8 +162,7 @@ func handleMsgMhfOperateJoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
case mhfpacket.OPERATE_JOINT_KICK:
|
||||
if alliance.ParentGuild.LeaderCharID == s.charID {
|
||||
_ = pkt.UnkData.ReadUint8()
|
||||
kickedGuildID := pkt.UnkData.ReadUint32()
|
||||
kickedGuildID := pkt.Data1.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 {
|
||||
|
||||
@@ -61,41 +61,35 @@ func (gm *GuildMember) Save(s *Session) error {
|
||||
}
|
||||
|
||||
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
|
||||
SELECT * FROM (
|
||||
SELECT
|
||||
g.id AS guild_id,
|
||||
joined_at,
|
||||
COALESCE((SELECT SUM(souls) FROM festa_submissions fs WHERE fs.character_id=c.id), 0) AS souls,
|
||||
COALESCE(rp_today, 0) AS rp_today,
|
||||
COALESCE(rp_yesterday, 0) AS rp_yesterday,
|
||||
c.name,
|
||||
c.id AS character_id,
|
||||
COALESCE(order_index, 0) AS order_index,
|
||||
c.last_login,
|
||||
COALESCE(recruiter, false) AS recruiter,
|
||||
COALESCE(avoid_leadership, false) AS avoid_leadership,
|
||||
c.hrp,
|
||||
c.gr,
|
||||
c.weapon_id,
|
||||
c.weapon_type,
|
||||
EXISTS(SELECT 1 FROM guild_applications ga WHERE ga.character_id=c.id AND application_type='applied') AS is_applicant,
|
||||
CASE WHEN g.leader_id = c.id THEN true ELSE false END AS is_leader
|
||||
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
|
||||
LEFT JOIN characters c ON c.id = gc.character_id
|
||||
LEFT JOIN guilds g ON g.id = gc.guild_id
|
||||
) AS subquery
|
||||
`
|
||||
|
||||
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
|
||||
WHERE guild_id = $1 AND is_applicant = $2
|
||||
`, guildMembersSelectSQL), guildID, applicants)
|
||||
|
||||
if err != nil {
|
||||
@@ -121,7 +115,7 @@ func GetGuildMembers(s *Session, guildID uint32, applicants bool) ([]*GuildMembe
|
||||
}
|
||||
|
||||
func GetCharacterGuildData(s *Session, charID uint32) (*GuildMember, error) {
|
||||
rows, err := s.server.db.Queryx(fmt.Sprintf("%s WHERE character.character_id=$1", guildMembersSelectSQL), charID)
|
||||
rows, err := s.server.db.Queryx(fmt.Sprintf("%s WHERE character_id=$1", guildMembersSelectSQL), charID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(fmt.Sprintf("failed to retrieve membership data for character '%d'", charID))
|
||||
|
||||
@@ -60,9 +60,9 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||
mail := &Mail{
|
||||
SenderID: s.charID,
|
||||
RecipientID: pkt.CharID,
|
||||
Subject: s.server.dict["guildInviteName"],
|
||||
Subject: s.server.i18n.guild.invite.title,
|
||||
Body: fmt.Sprintf(
|
||||
s.server.dict["guildInvite"],
|
||||
s.server.i18n.guild.invite.body,
|
||||
guildInfo.Name,
|
||||
),
|
||||
IsGuildInvite: true,
|
||||
@@ -146,30 +146,30 @@ func handleMsgMhfAnswerGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||
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),
|
||||
Subject: s.server.i18n.guild.invite.success.title,
|
||||
Body: fmt.Sprintf(s.server.i18n.guild.invite.success.body, 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),
|
||||
Subject: s.server.i18n.guild.invite.accepted.title,
|
||||
Body: fmt.Sprintf(s.server.i18n.guild.invite.accepted.body, 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),
|
||||
Subject: s.server.i18n.guild.invite.rejected.title,
|
||||
Body: fmt.Sprintf(s.server.i18n.guild.invite.rejected.body, 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),
|
||||
Subject: s.server.i18n.guild.invite.declined.title,
|
||||
Body: fmt.Sprintf(s.server.i18n.guild.invite.declined.body, guild.Name),
|
||||
IsSystemMessage: true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,72 +4,79 @@ import (
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/stringsupport"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TreasureHunt struct {
|
||||
HuntID uint32 `db:"id"`
|
||||
HostID uint32 `db:"host_id"`
|
||||
Destination uint32 `db:"destination"`
|
||||
Level uint32 `db:"level"`
|
||||
Return uint32 `db:"return"`
|
||||
Acquired bool `db:"acquired"`
|
||||
Claimed bool `db:"claimed"`
|
||||
Hunters string `db:"hunters"`
|
||||
Treasure string `db:"treasure"`
|
||||
HuntData []byte `db:"hunt_data"`
|
||||
HuntID uint32 `db:"id"`
|
||||
HostID uint32 `db:"host_id"`
|
||||
Destination uint32 `db:"destination"`
|
||||
Level uint32 `db:"level"`
|
||||
Start time.Time `db:"start"`
|
||||
Acquired bool `db:"acquired"`
|
||||
Collected bool `db:"collected"`
|
||||
HuntData []byte `db:"hunt_data"`
|
||||
Hunters uint32 `db:"hunters"`
|
||||
Claimed bool `db:"claimed"`
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateGuildTresure(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateGuildTresure)
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
if err != nil || guild == nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
var hunts []TreasureHunt
|
||||
var hunt TreasureHunt
|
||||
|
||||
switch pkt.MaxHunts {
|
||||
case 1:
|
||||
err = s.server.db.QueryRowx(`SELECT id, host_id, destination, level, start, hunt_data FROM guild_hunts WHERE host_id=$1 AND acquired=FALSE`, s.charID).StructScan(&hunt)
|
||||
if err == nil {
|
||||
hunts = append(hunts, hunt)
|
||||
}
|
||||
case 30:
|
||||
rows, err := s.server.db.Queryx(`SELECT gh.id, gh.host_id, gh.destination, gh.level, gh.start, gh.collected, gh.hunt_data,
|
||||
(SELECT COUNT(*) FROM guild_characters gc WHERE gc.treasure_hunt = gh.id AND gc.character_id <> $1) AS hunters,
|
||||
CASE
|
||||
WHEN ghc.character_id IS NOT NULL THEN true
|
||||
ELSE false
|
||||
END AS claimed
|
||||
FROM guild_hunts gh
|
||||
LEFT JOIN guild_hunts_claimed ghc ON gh.id = ghc.hunt_id AND ghc.character_id = $1
|
||||
WHERE gh.guild_id=$2 AND gh.level=2 AND gh.acquired=TRUE
|
||||
`, s.charID, guild.ID)
|
||||
if err != nil {
|
||||
rows.Close()
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
} else {
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&hunt)
|
||||
if err == nil && hunt.Start.Add(time.Second*time.Duration(s.server.erupeConfig.GameplayOptions.TreasureHuntExpiry)).After(TimeAdjusted()) {
|
||||
hunts = append(hunts, hunt)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(hunts) > 30 {
|
||||
hunts = hunts[:30]
|
||||
}
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
hunts := 0
|
||||
rows, _ := s.server.db.Queryx("SELECT id, host_id, destination, level, return, acquired, claimed, hunters, treasure, hunt_data FROM guild_hunts WHERE guild_id=$1 AND $2 < return+604800", guild.ID, TimeAdjusted().Unix())
|
||||
for rows.Next() {
|
||||
hunt := &TreasureHunt{}
|
||||
err = rows.StructScan(&hunt)
|
||||
// Remove self from other hunter count
|
||||
hunt.Hunters = stringsupport.CSVRemove(hunt.Hunters, int(s.charID))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if pkt.MaxHunts == 1 {
|
||||
if hunt.HostID != s.charID || hunt.Acquired {
|
||||
continue
|
||||
}
|
||||
hunts++
|
||||
bf.WriteUint32(hunt.HuntID)
|
||||
bf.WriteUint32(hunt.Destination)
|
||||
bf.WriteUint32(hunt.Level)
|
||||
bf.WriteUint32(uint32(stringsupport.CSVLength(hunt.Hunters)))
|
||||
bf.WriteUint32(hunt.Return)
|
||||
bf.WriteBool(false)
|
||||
bf.WriteBool(false)
|
||||
bf.WriteBytes(hunt.HuntData)
|
||||
break
|
||||
} else if pkt.MaxHunts == 30 && hunt.Acquired && hunt.Level == 2 {
|
||||
if hunts == 30 {
|
||||
break
|
||||
}
|
||||
hunts++
|
||||
bf.WriteUint32(hunt.HuntID)
|
||||
bf.WriteUint32(hunt.Destination)
|
||||
bf.WriteUint32(hunt.Level)
|
||||
bf.WriteUint32(uint32(stringsupport.CSVLength(hunt.Hunters)))
|
||||
bf.WriteUint32(hunt.Return)
|
||||
bf.WriteBool(hunt.Claimed)
|
||||
bf.WriteBool(stringsupport.CSVContains(hunt.Treasure, int(s.charID)))
|
||||
bf.WriteBytes(hunt.HuntData)
|
||||
}
|
||||
bf.WriteUint16(uint16(len(hunts)))
|
||||
bf.WriteUint16(uint16(len(hunts)))
|
||||
for _, h := range hunts {
|
||||
bf.WriteUint32(h.HuntID)
|
||||
bf.WriteUint32(h.Destination)
|
||||
bf.WriteUint32(h.Level)
|
||||
bf.WriteUint32(h.Hunters)
|
||||
bf.WriteUint32(uint32(h.Start.Unix()))
|
||||
bf.WriteBool(h.Collected)
|
||||
bf.WriteBool(h.Claimed)
|
||||
bf.WriteBytes(h.HuntData)
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint16(uint16(hunts))
|
||||
resp.WriteUint16(uint16(hunts))
|
||||
resp.WriteBytes(bf.Data())
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfRegistGuildTresure(s *Session, p mhfpacket.MHFPacket) {
|
||||
@@ -77,8 +84,9 @@ func handleMsgMhfRegistGuildTresure(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.Data)
|
||||
huntData := byteframe.NewByteFrame()
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
if err != nil || guild == nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
guildCats := getGuildAirouList(s)
|
||||
destination := bf.ReadUint32()
|
||||
@@ -92,87 +100,55 @@ func handleMsgMhfRegistGuildTresure(s *Session, p mhfpacket.MHFPacket) {
|
||||
if catID > 0 {
|
||||
catsUsed = stringsupport.CSVAdd(catsUsed, int(catID))
|
||||
for _, cat := range guildCats {
|
||||
if cat.CatID == catID {
|
||||
huntData.WriteBytes(cat.CatName)
|
||||
if cat.ID == catID {
|
||||
huntData.WriteBytes(cat.Name)
|
||||
break
|
||||
}
|
||||
}
|
||||
huntData.WriteBytes(bf.ReadBytes(9))
|
||||
}
|
||||
}
|
||||
_, err = s.server.db.Exec("INSERT INTO guild_hunts (guild_id, host_id, destination, level, return, hunt_data, cats_used) VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
||||
guild.ID, s.charID, destination, level, TimeAdjusted().Unix(), huntData.Data(), catsUsed)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s.server.db.Exec(`INSERT INTO guild_hunts (guild_id, host_id, destination, level, hunt_data, cats_used) VALUES ($1, $2, $3, $4, $5, $6)
|
||||
`, guild.ID, s.charID, destination, level, huntData.Data(), catsUsed)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireGuildTresure(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAcquireGuildTresure)
|
||||
_, err := s.server.db.Exec("UPDATE guild_hunts SET acquired=true WHERE id=$1", pkt.HuntID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s.server.db.Exec(`UPDATE guild_hunts SET acquired=true WHERE id=$1`, pkt.HuntID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func treasureHuntUnregister(s *Session) {
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
if err != nil || guild == nil {
|
||||
return
|
||||
}
|
||||
var huntID int
|
||||
var hunters string
|
||||
rows, err := s.server.db.Queryx("SELECT id, hunters FROM guild_hunts WHERE guild_id=$1", guild.ID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for rows.Next() {
|
||||
rows.Scan(&huntID, &hunters)
|
||||
hunters = stringsupport.CSVRemove(hunters, int(s.charID))
|
||||
s.server.db.Exec("UPDATE guild_hunts SET hunters=$1 WHERE id=$2", hunters, huntID)
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfOperateGuildTresureReport(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfOperateGuildTresureReport)
|
||||
var csv string
|
||||
if pkt.State == 0 { // Report registration
|
||||
// Unregister from all other hunts
|
||||
treasureHuntUnregister(s)
|
||||
if pkt.HuntID != 0 {
|
||||
// Register to selected hunt
|
||||
err := s.server.db.QueryRow("SELECT hunters FROM guild_hunts WHERE id=$1", pkt.HuntID).Scan(&csv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
csv = stringsupport.CSVAdd(csv, int(s.charID))
|
||||
_, err = s.server.db.Exec("UPDATE guild_hunts SET hunters=$1 WHERE id=$2", csv, pkt.HuntID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
} else if pkt.State == 1 { // Collected by hunter
|
||||
s.server.db.Exec("UPDATE guild_hunts SET hunters='', claimed=true WHERE id=$1", pkt.HuntID)
|
||||
} else if pkt.State == 2 { // Claim treasure
|
||||
err := s.server.db.QueryRow("SELECT treasure FROM guild_hunts WHERE id=$1", pkt.HuntID).Scan(&csv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
csv = stringsupport.CSVAdd(csv, int(s.charID))
|
||||
_, err = s.server.db.Exec("UPDATE guild_hunts SET treasure=$1 WHERE id=$2", csv, pkt.HuntID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
switch pkt.State {
|
||||
case 0: // Report registration
|
||||
s.server.db.Exec(`UPDATE guild_characters SET treasure_hunt=$1 WHERE character_id=$2`, pkt.HuntID, s.charID)
|
||||
case 1: // Collected by hunter
|
||||
s.server.db.Exec(`UPDATE guild_hunts SET collected=true WHERE id=$1`, pkt.HuntID)
|
||||
s.server.db.Exec(`UPDATE guild_characters SET treasure_hunt=NULL WHERE treasure_hunt=$1`, pkt.HuntID)
|
||||
case 2: // Claim treasure
|
||||
s.server.db.Exec(`INSERT INTO guild_hunts_claimed VALUES ($1, $2)`, pkt.HuntID, s.charID)
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
type TreasureSouvenir struct {
|
||||
Destination uint32
|
||||
Quantity uint32
|
||||
}
|
||||
|
||||
func handleMsgMhfGetGuildTresureSouvenir(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGuildTresureSouvenir)
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 6))
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(0)
|
||||
souvenirs := []TreasureSouvenir{}
|
||||
bf.WriteUint16(uint16(len(souvenirs)))
|
||||
for _, souvenir := range souvenirs {
|
||||
bf.WriteUint32(souvenir.Destination)
|
||||
bf.WriteUint32(souvenir.Quantity)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireGuildTresureSouvenir(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
@@ -119,7 +119,9 @@ func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteUint8(0)
|
||||
}
|
||||
bf.WriteUint16(house.HRP)
|
||||
bf.WriteUint16(house.GR)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G10 {
|
||||
bf.WriteUint16(house.GR)
|
||||
}
|
||||
ps.Uint8(bf, house.Name, true)
|
||||
}
|
||||
bf.Seek(0, 0)
|
||||
@@ -213,10 +215,12 @@ func handleMsgMhfLoadHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteBytes(houseFurniture)
|
||||
case 10: // Garden
|
||||
bf.WriteBytes(garden)
|
||||
c, d := getGookData(s, pkt.CharID)
|
||||
bf.WriteUint16(c)
|
||||
goocoos := getGoocooData(s, pkt.CharID)
|
||||
bf.WriteUint16(uint16(len(goocoos)))
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteBytes(d)
|
||||
for _, goocoo := range goocoos {
|
||||
bf.WriteBytes(goocoo)
|
||||
}
|
||||
}
|
||||
if len(bf.Data()) == 0 {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
@@ -238,8 +242,8 @@ func handleMsgMhfGetMyhouseInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgMhfUpdateMyhouseInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfUpdateMyhouseInfo)
|
||||
s.server.db.Exec("UPDATE user_binary SET mission=$1 WHERE id=$2", pkt.Unk0, s.charID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
s.server.db.Exec("UPDATE user_binary SET mission=$1 WHERE id=$2", pkt.Data, s.charID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfLoadDecoMyset(s *Session, p mhfpacket.MHFPacket) {
|
||||
@@ -250,71 +254,65 @@ func handleMsgMhfLoadDecoMyset(s *Session, p mhfpacket.MHFPacket) {
|
||||
s.logger.Error("Failed to load decomyset", zap.Error(err))
|
||||
}
|
||||
if len(data) == 0 {
|
||||
data = []byte{0x01, 0x00}
|
||||
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)
|
||||
var temp []byte
|
||||
err := s.server.db.QueryRow("SELECT decomyset FROM characters WHERE id = $1", s.charID).Scan(&temp)
|
||||
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, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
|
||||
// Version handling
|
||||
bf := byteframe.NewByteFrame()
|
||||
var size uint
|
||||
if s.server.erupeConfig.RealClientMode >= _config.G10 {
|
||||
size = 76
|
||||
bf.WriteUint8(1)
|
||||
} else {
|
||||
size = 68
|
||||
bf.WriteUint8(0)
|
||||
}
|
||||
|
||||
// Handle nil data
|
||||
if len(temp) == 0 {
|
||||
temp = append(bf.Data(), uint8(0))
|
||||
}
|
||||
|
||||
// Build a map of set data
|
||||
sets := make(map[uint16][]byte)
|
||||
oldSets := byteframe.NewByteFrameFromBytes(temp[2:])
|
||||
for i := uint8(0); i < temp[1]; i++ {
|
||||
index := oldSets.ReadUint16()
|
||||
sets[index] = oldSets.ReadBytes(size)
|
||||
}
|
||||
|
||||
// Overwrite existing sets
|
||||
newSets := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload[2:])
|
||||
for i := uint8(0); i < pkt.RawDataPayload[1]; i++ {
|
||||
index := newSets.ReadUint16()
|
||||
sets[index] = newSets.ReadBytes(size)
|
||||
}
|
||||
|
||||
// Serialise the set data
|
||||
bf.WriteUint8(uint8(len(sets)))
|
||||
for u, b := range sets {
|
||||
bf.WriteUint16(u)
|
||||
bf.WriteBytes(b)
|
||||
}
|
||||
|
||||
dumpSaveData(s, bf.Data(), "decomyset")
|
||||
s.server.db.Exec("UPDATE characters SET decomyset=$1 WHERE id=$2", bf.Data(), s.charID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
type Title struct {
|
||||
@@ -353,12 +351,14 @@ func handleMsgMhfEnumerateTitle(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
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()")
|
||||
for _, title := range pkt.TitleIDs {
|
||||
var exists int
|
||||
err := s.server.db.QueryRow(`SELECT count(*) FROM titles WHERE id=$1 AND char_id=$2`, title, s.charID).Scan(&exists)
|
||||
if err != nil || exists == 0 {
|
||||
s.server.db.Exec(`INSERT INTO titles VALUES ($1, $2, now(), now())`, title, s.charID)
|
||||
} else {
|
||||
s.server.db.Exec(`UPDATE titles SET updated_at=now() WHERE id=$1 AND char_id=$2`, title, s.charID)
|
||||
}
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
@@ -79,57 +79,6 @@ func (m *Mail) MarkRead(s *Session) error {
|
||||
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
|
||||
@@ -256,26 +205,21 @@ 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")
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0})
|
||||
return
|
||||
}
|
||||
|
||||
mail, err := GetMailByID(s, mailId)
|
||||
|
||||
if err != nil {
|
||||
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
panic(err)
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0})
|
||||
return
|
||||
}
|
||||
|
||||
_ = mail.MarkRead(s)
|
||||
|
||||
s.server.db.Exec(`UPDATE mail SET read = true WHERE id = $1`, mail.ID)
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
body := stringsupport.UTF8ToSJIS(mail.Body)
|
||||
bf.WriteNullTerminatedBytes(body)
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
@@ -283,10 +227,9 @@ 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)
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0})
|
||||
return
|
||||
}
|
||||
|
||||
if s.mailList == nil {
|
||||
@@ -354,24 +297,20 @@ func handleMsgMhfOprtMail(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
mail, err := GetMailByID(s, s.mailList[pkt.AccIndex])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
case mhfpacket.OperateMailDelete:
|
||||
s.server.db.Exec(`UPDATE mail SET deleted = true WHERE id = $1`, mail.ID)
|
||||
case mhfpacket.OperateMailLock:
|
||||
s.server.db.Exec(`UPDATE mail SET locked = TRUE WHERE id = $1`, mail.ID)
|
||||
case mhfpacket.OperateMailUnlock:
|
||||
s.server.db.Exec(`UPDATE mail SET locked = FALSE WHERE id = $1`, mail.ID)
|
||||
case mhfpacket.OperateMailAcquireItem:
|
||||
s.server.db.Exec(`UPDATE mail SET attached_item_received = TRUE WHERE id = $1`, mail.ID)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@ import (
|
||||
"erupe-ce/server/channelserver/compression/nullcomp"
|
||||
"go.uber.org/zap"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -299,18 +297,12 @@ func handleMsgMhfSaveOtomoAirou(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfEnumerateAiroulist(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateAiroulist)
|
||||
resp := byteframe.NewByteFrame()
|
||||
if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, "airoulist.bin")); err == nil {
|
||||
data, _ := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "airoulist.bin"))
|
||||
resp.WriteBytes(data)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
return
|
||||
}
|
||||
airouList := getGuildAirouList(s)
|
||||
resp.WriteUint16(uint16(len(airouList)))
|
||||
resp.WriteUint16(uint16(len(airouList)))
|
||||
for _, cat := range airouList {
|
||||
resp.WriteUint32(cat.CatID)
|
||||
resp.WriteBytes(cat.CatName)
|
||||
resp.WriteUint32(cat.ID)
|
||||
resp.WriteBytes(cat.Name)
|
||||
resp.WriteUint32(cat.Experience)
|
||||
resp.WriteUint8(cat.Personality)
|
||||
resp.WriteUint8(cat.Class)
|
||||
@@ -321,11 +313,10 @@ func handleMsgMhfEnumerateAiroulist(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
// CatDefinition holds values needed to populate the guild cat list
|
||||
type CatDefinition struct {
|
||||
CatID uint32
|
||||
CatName []byte
|
||||
CurrentTask uint8
|
||||
type Airou struct {
|
||||
ID uint32
|
||||
Name []byte
|
||||
Task uint8
|
||||
Personality uint8
|
||||
Class uint8
|
||||
Experience uint32
|
||||
@@ -333,46 +324,39 @@ type CatDefinition struct {
|
||||
WeaponID uint16
|
||||
}
|
||||
|
||||
func getGuildAirouList(s *Session) []CatDefinition {
|
||||
var guild *Guild
|
||||
var err error
|
||||
var guildCats []CatDefinition
|
||||
|
||||
// returning 0 cats on any guild issues
|
||||
// can probably optimise all of the guild queries pretty heavily
|
||||
guild, err = GetGuildInfoByCharacterId(s, s.charID)
|
||||
func getGuildAirouList(s *Session) []Airou {
|
||||
var guildCats []Airou
|
||||
bannedCats := make(map[uint32]int)
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
if err != nil {
|
||||
return guildCats
|
||||
}
|
||||
|
||||
// Get cats used recently
|
||||
// Retail reset at midday, 12 hours is a midpoint
|
||||
tempBanDuration := 43200 - (1800) // Minus hunt time
|
||||
bannedCats := make(map[uint32]int)
|
||||
var csvTemp string
|
||||
rows, err := s.server.db.Query(`SELECT cats_used
|
||||
FROM guild_hunts gh
|
||||
INNER JOIN characters c
|
||||
ON gh.host_id = c.id
|
||||
WHERE c.id=$1 AND gh.return+$2>$3`, s.charID, tempBanDuration, TimeAdjusted().Unix())
|
||||
rows, err := s.server.db.Query(`SELECT cats_used FROM guild_hunts gh
|
||||
INNER JOIN characters c ON gh.host_id = c.id WHERE c.id=$1
|
||||
`, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Warn("Failed to get recently used airous", zap.Error(err))
|
||||
return guildCats
|
||||
}
|
||||
|
||||
var csvTemp string
|
||||
var startTemp time.Time
|
||||
for rows.Next() {
|
||||
rows.Scan(&csvTemp)
|
||||
for i, j := range stringsupport.CSVElems(csvTemp) {
|
||||
bannedCats[uint32(j)] = i
|
||||
err = rows.Scan(&csvTemp, &startTemp)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if startTemp.Add(time.Second * time.Duration(s.server.erupeConfig.GameplayOptions.TreasureHuntPartnyaCooldown)).Before(TimeAdjusted()) {
|
||||
for i, j := range stringsupport.CSVElems(csvTemp) {
|
||||
bannedCats[uint32(j)] = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ellie's GetGuildMembers didn't seem to pull leader?
|
||||
rows, err = s.server.db.Query(`SELECT c.otomoairou
|
||||
FROM characters c
|
||||
INNER JOIN guild_characters gc
|
||||
ON gc.character_id = c.id
|
||||
rows, err = s.server.db.Query(`SELECT c.otomoairou FROM characters c
|
||||
INNER JOIN guild_characters gc ON gc.character_id = c.id
|
||||
WHERE gc.guild_id = $1 AND c.otomoairou IS NOT NULL
|
||||
ORDER BY c.id ASC
|
||||
LIMIT 60;`, guild.ID)
|
||||
ORDER BY c.id LIMIT 60`, guild.ID)
|
||||
if err != nil {
|
||||
s.logger.Warn("Selecting otomoairou based on guild failed", zap.Error(err))
|
||||
return guildCats
|
||||
@@ -381,11 +365,7 @@ func getGuildAirouList(s *Session) []CatDefinition {
|
||||
for rows.Next() {
|
||||
var data []byte
|
||||
err = rows.Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Warn("select failure", zap.Error(err))
|
||||
continue
|
||||
} else if len(data) == 0 {
|
||||
// non extant cats that aren't null in DB
|
||||
if err != nil || len(data) == 0 {
|
||||
continue
|
||||
}
|
||||
// first byte has cat existence in general, can skip if 0
|
||||
@@ -396,10 +376,10 @@ func getGuildAirouList(s *Session) []CatDefinition {
|
||||
continue
|
||||
}
|
||||
bf := byteframe.NewByteFrameFromBytes(decomp)
|
||||
cats := GetCatDetails(bf)
|
||||
cats := GetAirouDetails(bf)
|
||||
for _, cat := range cats {
|
||||
_, exists := bannedCats[cat.CatID]
|
||||
if cat.CurrentTask == 4 && !exists {
|
||||
_, exists := bannedCats[cat.ID]
|
||||
if cat.Task == 4 && !exists {
|
||||
guildCats = append(guildCats, cat)
|
||||
}
|
||||
}
|
||||
@@ -408,20 +388,20 @@ func getGuildAirouList(s *Session) []CatDefinition {
|
||||
return guildCats
|
||||
}
|
||||
|
||||
func GetCatDetails(bf *byteframe.ByteFrame) []CatDefinition {
|
||||
func GetAirouDetails(bf *byteframe.ByteFrame) []Airou {
|
||||
catCount := bf.ReadUint8()
|
||||
cats := make([]CatDefinition, catCount)
|
||||
cats := make([]Airou, catCount)
|
||||
for x := 0; x < int(catCount); x++ {
|
||||
var catDef CatDefinition
|
||||
var catDef Airou
|
||||
// cat sometimes has additional bytes for whatever reason, gift items? timestamp?
|
||||
// until actual variance is known we can just seek to end based on start
|
||||
catDefLen := bf.ReadUint32()
|
||||
catStart, _ := bf.Seek(0, io.SeekCurrent)
|
||||
|
||||
catDef.CatID = bf.ReadUint32()
|
||||
bf.Seek(1, io.SeekCurrent) // unknown value, probably a bool
|
||||
catDef.CatName = bf.ReadBytes(18) // always 18 len, reads first null terminated string out of section and discards rest
|
||||
catDef.CurrentTask = bf.ReadUint8()
|
||||
catDef.ID = bf.ReadUint32()
|
||||
bf.Seek(1, io.SeekCurrent) // unknown value, probably a bool
|
||||
catDef.Name = bf.ReadBytes(18) // always 18 len, reads first null terminated string out of section and discards rest
|
||||
catDef.Task = bf.ReadUint8()
|
||||
bf.Seek(16, io.SeekCurrent) // appearance data and what is seemingly null bytes
|
||||
catDef.Personality = bf.ReadUint8()
|
||||
catDef.Class = bf.ReadUint8()
|
||||
|
||||
@@ -42,7 +42,7 @@ 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 {
|
||||
if s.server.erupeConfig.DebugOptions.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()
|
||||
|
||||
@@ -42,7 +42,7 @@ func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
} else {
|
||||
// create empty save if absent
|
||||
data = make([]byte, 0x1AF20)
|
||||
data = make([]byte, 140000)
|
||||
}
|
||||
|
||||
// Perform diff and compress it to write back to db
|
||||
@@ -110,7 +110,7 @@ func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
} else {
|
||||
// create empty save if absent
|
||||
data = make([]byte, 0x820)
|
||||
data = make([]byte, 4800)
|
||||
}
|
||||
|
||||
// Perform diff and compress it to write back to db
|
||||
@@ -147,7 +147,7 @@ func handleMsgMhfLoadPlateMyset(s *Session, p mhfpacket.MHFPacket) {
|
||||
err := s.server.db.QueryRow("SELECT platemyset FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if len(data) == 0 {
|
||||
s.logger.Error("Failed to load platemyset", zap.Error(err))
|
||||
data = make([]byte, 0x780)
|
||||
data = make([]byte, 1920)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package channelserver
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/binary"
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/decryption"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
@@ -16,11 +17,82 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type tuneValue struct {
|
||||
ID uint16
|
||||
Value uint16
|
||||
}
|
||||
|
||||
func findSubSliceIndices(data []byte, sub []byte) []int {
|
||||
var indices []int
|
||||
lenSub := len(sub)
|
||||
for i := 0; i < len(data); i++ {
|
||||
if i+lenSub > len(data) {
|
||||
break
|
||||
}
|
||||
if equal(data[i:i+lenSub], sub) {
|
||||
indices = append(indices, i)
|
||||
}
|
||||
}
|
||||
return indices
|
||||
}
|
||||
|
||||
func equal(a, b []byte) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i, v := range a {
|
||||
if v != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func BackportQuest(data []byte) []byte {
|
||||
wp := binary.LittleEndian.Uint32(data[0:4]) + 96
|
||||
rp := wp + 4
|
||||
for i := uint32(0); i < 6; i++ {
|
||||
if i != 0 {
|
||||
wp += 4
|
||||
rp += 8
|
||||
}
|
||||
copy(data[wp:wp+4], data[rp:rp+4])
|
||||
}
|
||||
|
||||
fillLength := uint32(108)
|
||||
if _config.ErupeConfig.RealClientMode <= _config.S6 {
|
||||
fillLength = 44
|
||||
} else if _config.ErupeConfig.RealClientMode <= _config.F5 {
|
||||
fillLength = 52
|
||||
} else if _config.ErupeConfig.RealClientMode <= _config.G101 {
|
||||
fillLength = 76
|
||||
}
|
||||
|
||||
copy(data[wp:wp+fillLength], data[rp:rp+fillLength])
|
||||
if _config.ErupeConfig.RealClientMode <= _config.G91 {
|
||||
patterns := [][]byte{
|
||||
{0x0A, 0x00, 0x01, 0x33, 0xD7, 0x00}, // 10% Armor Sphere -> Stone
|
||||
{0x06, 0x00, 0x02, 0x33, 0xD8, 0x00}, // 6% Armor Sphere+ -> Iron Ore
|
||||
{0x0A, 0x00, 0x03, 0x33, 0xD7, 0x00}, // 10% Adv Armor Sphere -> Stone
|
||||
{0x06, 0x00, 0x04, 0x33, 0xDB, 0x00}, // 6% Hard Armor Sphere -> Dragonite Ore
|
||||
{0x0A, 0x00, 0x05, 0x33, 0xD9, 0x00}, // 10% Heaven Armor Sphere -> Earth Crystal
|
||||
{0x06, 0x00, 0x06, 0x33, 0xDB, 0x00}, // 6% True Armor Sphere -> Dragonite Ore
|
||||
}
|
||||
for i := range patterns {
|
||||
j := findSubSliceIndices(data, patterns[i][0:4])
|
||||
for k := range j {
|
||||
copy(data[j[k]+2:j[k]+4], patterns[i][4:6])
|
||||
}
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysGetFile)
|
||||
|
||||
if pkt.IsScenario {
|
||||
if s.server.erupeConfig.DevModeOptions.QuestDebugTools && s.server.erupeConfig.DevMode {
|
||||
if s.server.erupeConfig.DebugOptions.QuestTools {
|
||||
s.logger.Debug(
|
||||
"Scenario",
|
||||
zap.Uint8("CategoryID", pkt.ScenarioIdentifer.CategoryID),
|
||||
@@ -40,7 +112,7 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
if s.server.erupeConfig.DevModeOptions.QuestDebugTools && s.server.erupeConfig.DevMode {
|
||||
if s.server.erupeConfig.DebugOptions.QuestTools {
|
||||
s.logger.Debug(
|
||||
"Quest",
|
||||
zap.String("Filename", pkt.Filename),
|
||||
@@ -58,29 +130,37 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
return
|
||||
}
|
||||
if _config.ErupeConfig.RealClientMode <= _config.Z1 && s.server.erupeConfig.DebugOptions.AutoQuestBackport {
|
||||
data = BackportQuest(decryption.UnpackSimple(data))
|
||||
}
|
||||
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))
|
||||
filename := fmt.Sprintf("%s%d", questFile[:6], s.server.Season())
|
||||
|
||||
// Return original file if file doesn't exist
|
||||
if _, err := os.Stat(filename); err == nil {
|
||||
// Return the seasonal file
|
||||
if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", filename))); err == nil {
|
||||
return filename
|
||||
} else {
|
||||
return questFile
|
||||
// Attempt to return the requested quest file if the seasonal file doesn't exist
|
||||
if _, err = os.Stat(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", questFile))); err == nil {
|
||||
return questFile
|
||||
}
|
||||
|
||||
// If the code reaches this point, it's most likely a custom quest with no seasonal variations in the files.
|
||||
// Since event quests when seasonal pick day or night and the client requests either one, we need to differentiate between the two to prevent issues.
|
||||
var _time string
|
||||
|
||||
if TimeGameAbsolute() > 2880 {
|
||||
_time = "d"
|
||||
} else {
|
||||
_time = "n"
|
||||
}
|
||||
|
||||
// Request a d0 or n0 file depending on the time of day. The time of day matters and issues will occur if it's different to the one it requests.
|
||||
return fmt.Sprintf("%s%s%d", questFile[:5], _time, 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,31 +183,50 @@ func handleMsgMhfSaveFavoriteQuest(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
|
||||
func loadQuestFile(s *Session, questId int) []byte {
|
||||
data, exists := s.server.questCacheData[questId]
|
||||
if exists && s.server.questCacheTime[questId].Add(time.Duration(s.server.erupeConfig.QuestCacheExpiry)*time.Second).After(time.Now()) {
|
||||
return data
|
||||
}
|
||||
|
||||
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)
|
||||
if _config.ErupeConfig.RealClientMode <= _config.Z1 && s.server.erupeConfig.DebugOptions.AutoQuestBackport {
|
||||
decrypted = BackportQuest(decrypted)
|
||||
}
|
||||
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))
|
||||
bodyLength := 320
|
||||
if _config.ErupeConfig.RealClientMode <= _config.S6 {
|
||||
bodyLength = 160
|
||||
} else if _config.ErupeConfig.RealClientMode <= _config.F5 {
|
||||
bodyLength = 168
|
||||
} else if _config.ErupeConfig.RealClientMode <= _config.G101 {
|
||||
bodyLength = 192
|
||||
} else if _config.ErupeConfig.RealClientMode <= _config.Z1 {
|
||||
bodyLength = 224
|
||||
}
|
||||
|
||||
// The n 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(uint(bodyLength)))
|
||||
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.WriteUint32(uint32(bodyLength))
|
||||
questBody.Seek(0, 2)
|
||||
|
||||
// Rewrite the quest strings and their pointers
|
||||
var tempString []byte
|
||||
newStrings := byteframe.NewByteFrame()
|
||||
tempPointer := 352
|
||||
tempPointer := bodyLength + 32
|
||||
for i := 0; i < 8; i++ {
|
||||
questBody.WriteUint32(uint32(tempPointer))
|
||||
temp := int64(fileBytes.Index())
|
||||
@@ -139,24 +238,27 @@ func loadQuestFile(s *Session, questId int) []byte {
|
||||
}
|
||||
questBody.WriteBytes(newStrings.Data())
|
||||
|
||||
s.server.questCacheData[questId] = questBody.Data()
|
||||
s.server.questCacheTime[questId] = time.Now()
|
||||
return questBody.Data()
|
||||
}
|
||||
|
||||
func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) {
|
||||
var id, mark uint32
|
||||
var questId int
|
||||
var questId, activeDuration, inactiveDuration, flags int
|
||||
var maxPlayers, questType uint8
|
||||
rows.Scan(&id, &maxPlayers, &questType, &questId, &mark)
|
||||
var startTime time.Time
|
||||
rows.Scan(&id, &maxPlayers, &questType, &questId, &mark, &flags, &startTime, &activeDuration, &inactiveDuration)
|
||||
|
||||
data := loadQuestFile(s, questId)
|
||||
if data == nil {
|
||||
return nil, fmt.Errorf("failed to load quest file")
|
||||
return nil, fmt.Errorf(fmt.Sprintf("failed to load quest file (%d)", questId))
|
||||
}
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(id)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint8(0) // Indexer
|
||||
bf.WriteUint32(0) // Unk
|
||||
bf.WriteUint8(0) // Unk
|
||||
switch questType {
|
||||
case 16:
|
||||
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.RegularRavienteMaxPlayers)
|
||||
@@ -177,15 +279,43 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) {
|
||||
} else {
|
||||
bf.WriteBool(true)
|
||||
}
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(0) // Unk
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G1 {
|
||||
bf.WriteUint32(mark)
|
||||
}
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(0) // Unk
|
||||
bf.WriteUint16(uint16(len(data)))
|
||||
bf.WriteBytes(data)
|
||||
ps.Uint8(bf, "", true) // What is this string for?
|
||||
|
||||
// Time Flag Replacement
|
||||
// Bitset Structure: b8 UNK, b7 Required Objective, b6 UNK, b5 Night, b4 Day, b3 Cold, b2 Warm, b1 Spring
|
||||
// if the byte is set to 0 the game choses the quest file corresponding to whatever season the game is on
|
||||
bf.Seek(25, 0)
|
||||
flagByte := bf.ReadUint8()
|
||||
bf.Seek(25, 0)
|
||||
if s.server.erupeConfig.GameplayOptions.SeasonOverride {
|
||||
bf.WriteUint8(flagByte & 0b11100000)
|
||||
} else {
|
||||
// Allow for seasons to be specified in database, otherwise use the one in the file.
|
||||
if flags < 0 {
|
||||
bf.WriteUint8(flagByte)
|
||||
} else {
|
||||
bf.WriteUint8(uint8(flags))
|
||||
}
|
||||
}
|
||||
|
||||
// Bitset Structure Quest Variant 1: b8 UL Fixed, b7 UNK, b6 UNK, b5 UNK, b4 G Rank, b3 HC to UL, b2 Fix HC, b1 Hiden
|
||||
// Bitset Structure Quest Variant 2: b8 Road, b7 High Conquest, b6 Fixed Difficulty, b5 No Active Feature, b4 Timer, b3 No Cuff, b2 No Halk Pots, b1 Low Conquest
|
||||
// Bitset Structure Quest Variant 3: b8 No Sigils, b7 UNK, b6 Interception, b5 Zenith, b4 No GP Skills, b3 No Simple Mode?, b2 GSR to GR, b1 No Reward Skills
|
||||
|
||||
bf.Seek(175, 0)
|
||||
questVariant3 := bf.ReadUint8()
|
||||
questVariant3 &= 0b11011111 // disable Interception flag
|
||||
bf.Seek(175, 0)
|
||||
bf.WriteUint8(questVariant3)
|
||||
|
||||
bf.Seek(0, 2)
|
||||
ps.Uint8(bf, "", true) // Debug/Notes string for quest
|
||||
return bf.Data(), nil
|
||||
}
|
||||
|
||||
@@ -195,23 +325,62 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
|
||||
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 {
|
||||
rows, err := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark, COALESCE(flags, -1), start_time, COALESCE(active_days, 0) AS active_days, COALESCE(inactive_days, 0) AS inactive_days FROM event_quests ORDER BY quest_id")
|
||||
if err == nil {
|
||||
currentTime := time.Now()
|
||||
tx, _ := s.server.db.Begin()
|
||||
|
||||
for rows.Next() {
|
||||
var id, mark uint32
|
||||
var questId, flags, activeDays, inactiveDays int
|
||||
var maxPlayers, questType uint8
|
||||
var startTime time.Time
|
||||
|
||||
err = rows.Scan(&id, &maxPlayers, &questType, &questId, &mark, &flags, &startTime, &activeDays, &inactiveDays)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to scan event quest row", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
// Use the Event Cycling system
|
||||
if activeDays > 0 {
|
||||
cycleLength := (time.Duration(activeDays) + time.Duration(inactiveDays)) * 24 * time.Hour
|
||||
|
||||
// Count the number of full cycles elapsed since the last rotation.
|
||||
extraCycles := int(currentTime.Sub(startTime) / cycleLength)
|
||||
|
||||
if extraCycles > 0 {
|
||||
// Calculate the rotation time based on start time, active duration, and inactive duration.
|
||||
rotationTime := startTime.Add(time.Duration(activeDays+inactiveDays) * 24 * time.Hour * time.Duration(extraCycles))
|
||||
if currentTime.After(rotationTime) {
|
||||
// Normalize rotationTime to 12PM JST to align with the in-game events update notification.
|
||||
newRotationTime := time.Date(rotationTime.Year(), rotationTime.Month(), rotationTime.Day(), 12, 0, 0, 0, TimeAdjusted().Location())
|
||||
|
||||
_, err = tx.Exec("UPDATE event_quests SET start_time = $1 WHERE id = $2", newRotationTime, id)
|
||||
if err != nil {
|
||||
tx.Rollback() // Rollback if an error occurs
|
||||
break
|
||||
}
|
||||
startTime = newRotationTime // Set the new start time so the quest can be used/removed immediately.
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the quest is currently active
|
||||
if currentTime.Before(startTime) || currentTime.After(startTime.Add(time.Duration(activeDays)*24*time.Hour)) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
data, err := makeEventQuest(s, rows)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to make event quest", zap.Error(err))
|
||||
continue
|
||||
} else {
|
||||
totalCount++
|
||||
if _config.ErupeConfig.RealClientMode == _config.F5 {
|
||||
if totalCount > pkt.Offset && len(bf.Data()) < 21550 {
|
||||
returnedCount++
|
||||
bf.WriteBytes(data)
|
||||
continue
|
||||
}
|
||||
if len(data) > 896 || len(data) < 352 {
|
||||
s.logger.Error("Invalid quest data length", zap.Int("len", len(data)))
|
||||
continue
|
||||
} else {
|
||||
totalCount++
|
||||
if totalCount > pkt.Offset && len(bf.Data()) < 60000 {
|
||||
returnedCount++
|
||||
bf.WriteBytes(data)
|
||||
@@ -220,11 +389,9 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type tuneValue struct {
|
||||
ID uint16
|
||||
Value uint16
|
||||
rows.Close()
|
||||
tx.Commit()
|
||||
}
|
||||
|
||||
tuneValues := []tuneValue{
|
||||
@@ -239,43 +406,46 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
|
||||
{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: 1001, Value: 100}, // get_hrp_rate
|
||||
{ID: 1010, Value: 300}, // get_hrp_rate_netcafe
|
||||
{ID: 1011, Value: 300}, // get_zeny_rate_netcafe
|
||||
{ID: 1012, Value: 300}, // get_hrp_rate_ncource
|
||||
{ID: 1013, Value: 300}, // get_zeny_rate_ncource
|
||||
{ID: 1014, Value: 200}, // get_hrp_rate_premium
|
||||
{ID: 1015, Value: 200}, // get_zeny_rate_premium
|
||||
{ID: 1021, Value: 400}, // get_gcp_rate_assist
|
||||
{ID: 1023, Value: 8}, // unused?
|
||||
{ID: 1024, Value: 150}, // get_hrp_rate_ptbonus
|
||||
{ID: 1025, Value: 1}, // isValid_stampcard
|
||||
{ID: 1026, Value: 999}, // get_grank_cap
|
||||
{ID: 1027, Value: 100}, // get_exchange_rate_festa
|
||||
{ID: 1028, Value: 100}, // get_exchange_rate_cafe
|
||||
{ID: 1030, Value: 8}, // get_gquest_cap
|
||||
{ID: 1031, Value: 100}, // get_exchange_rate_guild (GCP)
|
||||
{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}, // get_hunter_life_cap
|
||||
{ID: 1048, Value: 0}, // get_rate_tower_hint_sec
|
||||
{ID: 1049, Value: 10}, // get_rate_tower_gem_max
|
||||
{ID: 1050, Value: 1}, // get_rate_tower_gem_set
|
||||
{ID: 1051, Value: 200}, // get_pallone_score_rate_premium
|
||||
{ID: 1052, Value: 200}, // get_trp_rate_premium
|
||||
{ID: 1063, Value: 50000}, // get_nboost_quest_point_from_hrank
|
||||
{ID: 1064, Value: 50000}, // get_nboost_quest_point_from_srank
|
||||
{ID: 1065, Value: 25000}, // get_nboost_quest_point_from_grank
|
||||
{ID: 1066, Value: 25000}, // get_nboost_quest_point_from_gsrank
|
||||
{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}, // isCapped_tenrou_irai
|
||||
{ID: 1079, Value: 1}, // get_add_tower_level_assist
|
||||
{ID: 1080, Value: 1}, // get_tune_add_tower_level_w_assist_nboost
|
||||
|
||||
// get_tune_secret_book_item
|
||||
{ID: 1081, Value: 1},
|
||||
{ID: 1082, Value: 4},
|
||||
{ID: 1083, Value: 2},
|
||||
@@ -300,14 +470,17 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
|
||||
{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: 1145, Value: 200}, // get_ud_point_rate_premium
|
||||
{ID: 1146, Value: 0}, // isTower_invisible
|
||||
{ID: 1147, Value: 0}, // isVenom_playable
|
||||
{ID: 1149, Value: 20}, // get_ud_break_parts_point
|
||||
{ID: 1152, Value: 1130}, // unused?
|
||||
{ID: 1154, Value: 0}, // isDisabled_object_season
|
||||
{ID: 1158, Value: 1}, // isDelivery_venom_ult_quest
|
||||
{ID: 1160, Value: 300}, // get_rate_premium_ravi_g_enhance_tama
|
||||
|
||||
// unknown
|
||||
{ID: 1162, Value: 1},
|
||||
{ID: 1163, Value: 3},
|
||||
{ID: 1164, Value: 5},
|
||||
@@ -327,245 +500,11 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
|
||||
{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})
|
||||
tuneValues = append(tuneValues, tuneValue{1029, uint16(s.server.erupeConfig.GameplayOptions.GUrgentRate * 100)})
|
||||
|
||||
if s.server.erupeConfig.GameplayOptions.DisableHunterNavi {
|
||||
tuneValues = append(tuneValues, tuneValue{1037, 1})
|
||||
@@ -573,65 +512,70 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
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)})
|
||||
}
|
||||
// get_hrp_rate_from_rank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3000, uint16(s.server.erupeConfig.GameplayOptions.HRPMultiplier*100))...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3338, uint16(s.server.erupeConfig.GameplayOptions.HRPMultiplierNC*100))...)
|
||||
// get_srp_rate_from_rank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3013, uint16(s.server.erupeConfig.GameplayOptions.SRPMultiplier*100))...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3351, uint16(s.server.erupeConfig.GameplayOptions.SRPMultiplierNC*100))...)
|
||||
// get_grp_rate_from_rank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3026, uint16(s.server.erupeConfig.GameplayOptions.GRPMultiplier*100))...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3364, uint16(s.server.erupeConfig.GameplayOptions.GRPMultiplierNC*100))...)
|
||||
// get_gsrp_rate_from_rank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3039, uint16(s.server.erupeConfig.GameplayOptions.GSRPMultiplier*100))...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3377, uint16(s.server.erupeConfig.GameplayOptions.GSRPMultiplierNC*100))...)
|
||||
// get_zeny_rate_from_hrank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3052, uint16(s.server.erupeConfig.GameplayOptions.ZennyMultiplier*100))...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3390, uint16(s.server.erupeConfig.GameplayOptions.ZennyMultiplierNC*100))...)
|
||||
// get_zeny_rate_from_grank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3078, uint16(s.server.erupeConfig.GameplayOptions.GZennyMultiplier*100))...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3416, uint16(s.server.erupeConfig.GameplayOptions.GZennyMultiplierNC*100))...)
|
||||
// get_reward_rate_from_hrank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3104, uint16(s.server.erupeConfig.GameplayOptions.MaterialMultiplier*100))...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3442, uint16(s.server.erupeConfig.GameplayOptions.MaterialMultiplierNC*100))...)
|
||||
// get_reward_rate_from_grank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3130, uint16(s.server.erupeConfig.GameplayOptions.GMaterialMultiplier*100))...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3468, uint16(s.server.erupeConfig.GameplayOptions.GMaterialMultiplierNC*100))...)
|
||||
// get_lottery_rate_from_hrank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3156, 0)...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3494, 0)...)
|
||||
// get_lottery_rate_from_grank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3182, 0)...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3520, 0)...)
|
||||
// get_hagi_rate_from_hrank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3208, s.server.erupeConfig.GameplayOptions.ExtraCarves)...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3546, s.server.erupeConfig.GameplayOptions.ExtraCarvesNC)...)
|
||||
// get_hagi_rate_from_grank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3234, s.server.erupeConfig.GameplayOptions.GExtraCarves)...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3572, s.server.erupeConfig.GameplayOptions.GExtraCarvesNC)...)
|
||||
// get_nboost_transcend_rate_from_hrank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3286, 200)...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3312, 300)...)
|
||||
// get_nboost_transcend_rate_from_grank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3299, 200)...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3325, 300)...)
|
||||
|
||||
for i := uint16(0); i < 13; i++ {
|
||||
tuneValues = append(tuneValues, tuneValue{i + 3039, uint16(s.server.erupeConfig.GameplayOptions.GSRPMultiplier * 100)})
|
||||
var temp []tuneValue
|
||||
for i := range tuneValues {
|
||||
if tuneValues[i].Value > 0 {
|
||||
temp = append(temp, tuneValues[i])
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
tuneValues = temp
|
||||
|
||||
tuneLimit := 770
|
||||
if _config.ErupeConfig.RealClientMode <= _config.F5 {
|
||||
@@ -657,6 +601,9 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
|
||||
tuneValues = tuneValues[:tuneLimit]
|
||||
}
|
||||
|
||||
offset := uint16(time.Now().Unix())
|
||||
bf.WriteUint16(offset)
|
||||
|
||||
bf.WriteUint16(uint16(len(tuneValues)))
|
||||
for i := range tuneValues {
|
||||
bf.WriteUint16(tuneValues[i].ID ^ offset)
|
||||
@@ -698,6 +645,14 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func getTuneValueRange(start uint16, value uint16) []tuneValue {
|
||||
var tv []tuneValue
|
||||
for i := uint16(0); i < 13; i++ {
|
||||
tv = append(tv, tuneValue{start + i, value})
|
||||
}
|
||||
return tv
|
||||
}
|
||||
|
||||
func handleMsgMhfEnterTournamentQuest(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfGetUdBonusQuestInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
@@ -6,256 +6,124 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func handleMsgMhfRegisterEvent(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfRegisterEvent)
|
||||
bf := byteframe.NewByteFrame()
|
||||
// Some kind of check if there's already a session
|
||||
if pkt.Unk1 && s.server.getRaviSemaphore() == nil {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
bf.WriteUint8(uint8(pkt.WorldID))
|
||||
bf.WriteUint8(uint8(pkt.LandID))
|
||||
bf.WriteUint16(s.server.raviente.id)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfReleaseEvent(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReleaseEvent)
|
||||
|
||||
// Do this ack manually because it uses a non-(0|1) error code
|
||||
/*
|
||||
_ACK_SUCCESS = 0
|
||||
_ACK_ERROR = 1
|
||||
|
||||
_ACK_EINPROGRESS = 16
|
||||
_ACK_ENOENT = 17
|
||||
_ACK_ENOSPC = 18
|
||||
_ACK_ETIMEOUT = 19
|
||||
|
||||
_ACK_EINVALID = 64
|
||||
_ACK_EFAILED = 65
|
||||
_ACK_ENOMEM = 66
|
||||
_ACK_ENOTEXIT = 67
|
||||
_ACK_ENOTREADY = 68
|
||||
_ACK_EALREADY = 69
|
||||
_ACK_DISABLE_WORK = 71
|
||||
*/
|
||||
s.QueueSendMHF(&mhfpacket.MsgSysAck{
|
||||
AckHandle: pkt.AckHandle,
|
||||
IsBufferResponse: false,
|
||||
ErrorCode: 0x41,
|
||||
AckData: []byte{0x00, 0x00, 0x00, 0x00},
|
||||
})
|
||||
}
|
||||
|
||||
type RaviUpdate struct {
|
||||
Op uint8
|
||||
Dest uint8
|
||||
Data uint32
|
||||
}
|
||||
|
||||
func handleMsgSysOperateRegister(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysOperateRegister)
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
|
||||
s.server.raviente.Lock()
|
||||
switch pkt.SemaphoreID {
|
||||
case 4:
|
||||
resp := byteframe.NewByteFrame()
|
||||
size := 6
|
||||
for i := 0; i < len(bf.Data())-1; i += size {
|
||||
op := bf.ReadUint8()
|
||||
dest := bf.ReadUint8()
|
||||
data := bf.ReadUint32()
|
||||
resp.WriteUint8(1)
|
||||
resp.WriteUint8(dest)
|
||||
ref := &s.server.raviente.state.stateData[dest]
|
||||
damageMultiplier := s.server.raviente.GetRaviMultiplier(s.server)
|
||||
switch op {
|
||||
case 2:
|
||||
resp.WriteUint32(*ref)
|
||||
if dest == 28 { // Berserk resurrection tracker
|
||||
resp.WriteUint32(*ref + data)
|
||||
*ref += data
|
||||
} else if dest == 17 { // Berserk poison tracker
|
||||
if damageMultiplier == 1 {
|
||||
resp.WriteUint32(*ref + data)
|
||||
*ref += data
|
||||
} else {
|
||||
resp.WriteUint32(*ref)
|
||||
}
|
||||
} else {
|
||||
data = uint32(float64(data) * damageMultiplier)
|
||||
resp.WriteUint32(*ref + data)
|
||||
*ref += data
|
||||
}
|
||||
case 13:
|
||||
fallthrough
|
||||
case 14:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
*ref = data
|
||||
}
|
||||
}
|
||||
resp.WriteUint8(0)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
case 5:
|
||||
resp := byteframe.NewByteFrame()
|
||||
size := 6
|
||||
for i := 0; i < len(bf.Data())-1; i += size {
|
||||
op := bf.ReadUint8()
|
||||
dest := bf.ReadUint8()
|
||||
data := bf.ReadUint32()
|
||||
resp.WriteUint8(1)
|
||||
resp.WriteUint8(dest)
|
||||
ref := &s.server.raviente.support.supportData[dest]
|
||||
switch op {
|
||||
case 2:
|
||||
resp.WriteUint32(*ref)
|
||||
resp.WriteUint32(*ref + data)
|
||||
*ref += data
|
||||
case 13:
|
||||
fallthrough
|
||||
case 14:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
*ref = data
|
||||
}
|
||||
}
|
||||
resp.WriteUint8(0)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
case 6:
|
||||
resp := byteframe.NewByteFrame()
|
||||
size := 6
|
||||
for i := 0; i < len(bf.Data())-1; i += size {
|
||||
op := bf.ReadUint8()
|
||||
dest := bf.ReadUint8()
|
||||
data := bf.ReadUint32()
|
||||
resp.WriteUint8(1)
|
||||
resp.WriteUint8(dest)
|
||||
switch dest {
|
||||
case 0:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
s.server.raviente.register.nextTime = data
|
||||
case 1:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
s.server.raviente.register.startTime = data
|
||||
case 2:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
s.server.raviente.register.killedTime = data
|
||||
case 3:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
s.server.raviente.register.postTime = data
|
||||
case 4:
|
||||
ref := &s.server.raviente.register.register[0]
|
||||
switch op {
|
||||
case 2:
|
||||
resp.WriteUint32(*ref)
|
||||
resp.WriteUint32(*ref + data)
|
||||
*ref += data
|
||||
case 13:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
*ref = data
|
||||
case 14:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
}
|
||||
case 5:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
s.server.raviente.register.carveQuest = data
|
||||
case 6:
|
||||
ref := &s.server.raviente.register.register[1]
|
||||
switch op {
|
||||
case 2:
|
||||
resp.WriteUint32(*ref)
|
||||
resp.WriteUint32(*ref + data)
|
||||
*ref += data
|
||||
case 13:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
*ref = data
|
||||
case 14:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
}
|
||||
case 7:
|
||||
ref := &s.server.raviente.register.register[2]
|
||||
switch op {
|
||||
case 2:
|
||||
resp.WriteUint32(*ref)
|
||||
resp.WriteUint32(*ref + data)
|
||||
*ref += data
|
||||
case 13:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
*ref = data
|
||||
case 14:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
}
|
||||
case 8:
|
||||
ref := &s.server.raviente.register.register[3]
|
||||
switch op {
|
||||
case 2:
|
||||
resp.WriteUint32(*ref)
|
||||
resp.WriteUint32(*ref + data)
|
||||
*ref += data
|
||||
case 13:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
*ref = data
|
||||
case 14:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
}
|
||||
case 9:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
s.server.raviente.register.maxPlayers = data
|
||||
case 10:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
s.server.raviente.register.ravienteType = data
|
||||
case 11:
|
||||
ref := &s.server.raviente.register.register[4]
|
||||
switch op {
|
||||
case 2:
|
||||
resp.WriteUint32(*ref)
|
||||
resp.WriteUint32(*ref + data)
|
||||
*ref += data
|
||||
case 13:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
*ref = data
|
||||
case 14:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
}
|
||||
default:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(0)
|
||||
}
|
||||
}
|
||||
resp.WriteUint8(0)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
|
||||
var raviUpdates []RaviUpdate
|
||||
var raviUpdate RaviUpdate
|
||||
// Strip null terminator
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload[:len(pkt.RawDataPayload)-1])
|
||||
for i := len(pkt.RawDataPayload) / 6; i > 0; i-- {
|
||||
raviUpdate.Op = bf.ReadUint8()
|
||||
raviUpdate.Dest = bf.ReadUint8()
|
||||
raviUpdate.Data = bf.ReadUint32()
|
||||
raviUpdates = append(raviUpdates, raviUpdate)
|
||||
}
|
||||
bf = byteframe.NewByteFrame()
|
||||
|
||||
var _old, _new uint32
|
||||
s.server.raviente.Lock()
|
||||
for _, update := range raviUpdates {
|
||||
switch update.Op {
|
||||
case 2:
|
||||
_old, _new = s.server.UpdateRavi(pkt.SemaphoreID, update.Dest, update.Data, true)
|
||||
case 13, 14:
|
||||
_old, _new = s.server.UpdateRavi(pkt.SemaphoreID, update.Dest, update.Data, false)
|
||||
}
|
||||
bf.WriteUint8(1)
|
||||
bf.WriteUint8(update.Dest)
|
||||
bf.WriteUint32(_old)
|
||||
bf.WriteUint32(_new)
|
||||
}
|
||||
s.server.raviente.Unlock()
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
|
||||
if s.server.erupeConfig.GameplayOptions.LowLatencyRaviente {
|
||||
s.notifyRavi()
|
||||
}
|
||||
s.server.raviente.Unlock()
|
||||
}
|
||||
|
||||
func handleMsgSysLoadRegister(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysLoadRegister)
|
||||
r := pkt.Unk1
|
||||
switch r {
|
||||
case 12:
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(0)
|
||||
resp.WriteUint8(12)
|
||||
resp.WriteUint32(s.server.raviente.register.nextTime)
|
||||
resp.WriteUint32(s.server.raviente.register.startTime)
|
||||
resp.WriteUint32(s.server.raviente.register.killedTime)
|
||||
resp.WriteUint32(s.server.raviente.register.postTime)
|
||||
resp.WriteUint32(s.server.raviente.register.register[0])
|
||||
resp.WriteUint32(s.server.raviente.register.carveQuest)
|
||||
resp.WriteUint32(s.server.raviente.register.register[1])
|
||||
resp.WriteUint32(s.server.raviente.register.register[2])
|
||||
resp.WriteUint32(s.server.raviente.register.register[3])
|
||||
resp.WriteUint32(s.server.raviente.register.maxPlayers)
|
||||
resp.WriteUint32(s.server.raviente.register.ravienteType)
|
||||
resp.WriteUint32(s.server.raviente.register.register[4])
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
case 29:
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(0)
|
||||
resp.WriteUint8(29)
|
||||
for _, v := range s.server.raviente.state.stateData {
|
||||
resp.WriteUint32(v)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(0)
|
||||
bf.WriteUint8(pkt.Values)
|
||||
for i := uint8(0); i < pkt.Values; i++ {
|
||||
switch pkt.RegisterID {
|
||||
case 0x40000:
|
||||
bf.WriteUint32(s.server.raviente.state[i])
|
||||
case 0x50000:
|
||||
bf.WriteUint32(s.server.raviente.support[i])
|
||||
case 0x60000:
|
||||
bf.WriteUint32(s.server.raviente.register[i])
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
case 25:
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(0)
|
||||
resp.WriteUint8(25)
|
||||
for _, v := range s.server.raviente.support.supportData {
|
||||
resp.WriteUint32(v)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func (s *Session) notifyRavi() {
|
||||
sema := getRaviSemaphore(s.server)
|
||||
sema := s.server.getRaviSemaphore()
|
||||
if sema == nil {
|
||||
return
|
||||
}
|
||||
var temp mhfpacket.MHFPacket
|
||||
raviNotif := byteframe.NewByteFrame()
|
||||
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 4}
|
||||
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 0x40000}
|
||||
raviNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(raviNotif, s.clientContext)
|
||||
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 5}
|
||||
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 0x50000}
|
||||
raviNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(raviNotif, s.clientContext)
|
||||
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 6}
|
||||
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 0x60000}
|
||||
raviNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(raviNotif, s.clientContext)
|
||||
raviNotif.WriteUint16(0x0010) // End it.
|
||||
@@ -272,28 +140,13 @@ func (s *Session) notifyRavi() {
|
||||
}
|
||||
}
|
||||
|
||||
func getRaviSemaphore(s *Server) *Semaphore {
|
||||
func (s *Server) getRaviSemaphore() *Semaphore {
|
||||
for _, semaphore := range s.semaphore {
|
||||
if strings.HasPrefix(semaphore.id_semaphore, "hs_l0u3B5") && strings.HasSuffix(semaphore.id_semaphore, "3") {
|
||||
if strings.HasPrefix(semaphore.name, "hs_l0") && strings.HasSuffix(semaphore.name, "3") {
|
||||
return semaphore
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resetRavi(s *Session) {
|
||||
s.server.raviente.Lock()
|
||||
s.server.raviente.register.nextTime = 0
|
||||
s.server.raviente.register.startTime = 0
|
||||
s.server.raviente.register.killedTime = 0
|
||||
s.server.raviente.register.postTime = 0
|
||||
s.server.raviente.register.ravienteType = 0
|
||||
s.server.raviente.register.maxPlayers = 0
|
||||
s.server.raviente.register.carveQuest = 0
|
||||
s.server.raviente.register.register = []uint32{0, 0, 0, 0, 0}
|
||||
s.server.raviente.state.stateData = []uint32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
s.server.raviente.support.supportData = []uint32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
s.server.raviente.Unlock()
|
||||
}
|
||||
|
||||
func handleMsgSysNotifyRegister(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
@@ -2,7 +2,6 @@ package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -13,9 +12,6 @@ import (
|
||||
func removeSessionFromSemaphore(s *Session) {
|
||||
s.server.semaphoreLock.Lock()
|
||||
for _, semaphore := range s.server.semaphore {
|
||||
if _, exists := semaphore.reservedClientSlots[s.charID]; exists {
|
||||
delete(semaphore.reservedClientSlots, s.charID)
|
||||
}
|
||||
if _, exists := semaphore.clients[s]; exists {
|
||||
delete(semaphore.clients, s)
|
||||
}
|
||||
@@ -31,48 +27,38 @@ func handleMsgSysCreateSemaphore(s *Session, p mhfpacket.MHFPacket) {
|
||||
func destructEmptySemaphores(s *Session) {
|
||||
s.server.semaphoreLock.Lock()
|
||||
for id, sema := range s.server.semaphore {
|
||||
if len(sema.reservedClientSlots) == 0 && len(sema.clients) == 0 {
|
||||
s.server.semaphoreLock.Unlock()
|
||||
if len(sema.clients) == 0 {
|
||||
delete(s.server.semaphore, id)
|
||||
s.server.semaphoreLock.Lock()
|
||||
if strings.HasPrefix(id, "hs_l0u3B5") {
|
||||
releaseRaviSemaphore(s, sema)
|
||||
if strings.HasPrefix(id, "hs_l0") {
|
||||
s.server.resetRaviente()
|
||||
}
|
||||
s.logger.Debug("Destructed semaphore", zap.String("sema.id_semaphore", id))
|
||||
s.logger.Debug("Destructed semaphore", zap.String("sema.name", id))
|
||||
}
|
||||
}
|
||||
s.server.semaphoreLock.Unlock()
|
||||
}
|
||||
|
||||
func releaseRaviSemaphore(s *Session, sema *Semaphore) {
|
||||
delete(sema.reservedClientSlots, s.charID)
|
||||
delete(sema.clients, s)
|
||||
if strings.HasSuffix(sema.id_semaphore, "2") && len(sema.clients) == 0 {
|
||||
s.logger.Debug("Main raviente semaphore is empty, resetting")
|
||||
resetRavi(s)
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgSysDeleteSemaphore(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysDeleteSemaphore)
|
||||
if s.server.semaphore != nil {
|
||||
destructEmptySemaphores(s)
|
||||
s.server.semaphoreLock.Lock()
|
||||
for id, sema := range s.server.semaphore {
|
||||
if sema.id == pkt.SemaphoreID {
|
||||
if strings.HasPrefix(id, "hs_l0u3B5") {
|
||||
releaseRaviSemaphore(s, sema)
|
||||
s.server.semaphoreLock.Unlock()
|
||||
return
|
||||
destructEmptySemaphores(s)
|
||||
s.server.semaphoreLock.Lock()
|
||||
for id, sema := range s.server.semaphore {
|
||||
if sema.id == pkt.SemaphoreID {
|
||||
for session := range sema.clients {
|
||||
if s == session {
|
||||
delete(sema.clients, s)
|
||||
}
|
||||
s.server.semaphoreLock.Unlock()
|
||||
}
|
||||
if len(sema.clients) == 0 {
|
||||
delete(s.server.semaphore, id)
|
||||
s.logger.Debug("Destructed semaphore", zap.String("sema.id_semaphore", id))
|
||||
return
|
||||
if strings.HasPrefix(id, "hs_l0") {
|
||||
s.server.resetRaviente()
|
||||
}
|
||||
s.logger.Debug("Destructed semaphore", zap.String("sema.name", id))
|
||||
}
|
||||
}
|
||||
s.server.semaphoreLock.Unlock()
|
||||
}
|
||||
s.server.semaphoreLock.Unlock()
|
||||
}
|
||||
|
||||
func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
|
||||
@@ -80,18 +66,15 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
|
||||
SemaphoreID := pkt.SemaphoreID
|
||||
|
||||
newSemaphore, exists := s.server.semaphore[SemaphoreID]
|
||||
|
||||
fmt.Printf("Got reserve stage req, StageID: %v\n\n", SemaphoreID)
|
||||
if !exists {
|
||||
s.server.semaphoreLock.Lock()
|
||||
if strings.HasPrefix(SemaphoreID, "hs_l0u3B5") {
|
||||
if strings.HasPrefix(SemaphoreID, "hs_l0") {
|
||||
suffix, _ := strconv.Atoi(pkt.SemaphoreID[len(pkt.SemaphoreID)-1:])
|
||||
s.server.semaphore[SemaphoreID] = &Semaphore{
|
||||
id_semaphore: pkt.SemaphoreID,
|
||||
id: uint32(suffix + 1),
|
||||
clients: make(map[*Session]uint32),
|
||||
reservedClientSlots: make(map[uint32]interface{}),
|
||||
maxPlayers: 127,
|
||||
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)
|
||||
@@ -102,22 +85,19 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
newSemaphore.Lock()
|
||||
defer newSemaphore.Unlock()
|
||||
if _, exists := newSemaphore.reservedClientSlots[s.charID]; exists {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf := byteframe.NewByteFrame()
|
||||
if _, exists := newSemaphore.clients[s]; exists {
|
||||
bf.WriteUint32(newSemaphore.id)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
} else if uint16(len(newSemaphore.reservedClientSlots)) < newSemaphore.maxPlayers {
|
||||
newSemaphore.reservedClientSlots[s.charID] = nil
|
||||
} else if uint16(len(newSemaphore.clients)) < newSemaphore.maxPlayers {
|
||||
newSemaphore.clients[s] = s.charID
|
||||
s.Lock()
|
||||
s.semaphore = newSemaphore
|
||||
s.Unlock()
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(newSemaphore.id)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
} else {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
bf.WriteUint32(0)
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgSysAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
|
||||
@@ -130,7 +110,6 @@ func handleMsgSysAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
|
||||
} else {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func handleMsgSysReleaseSemaphore(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
@@ -10,13 +10,13 @@ import (
|
||||
|
||||
type ShopItem struct {
|
||||
ID uint32 `db:"id"`
|
||||
ItemID uint16 `db:"item_id"`
|
||||
ItemID uint32 `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"`
|
||||
StoreLevel uint8 `db:"store_level"`
|
||||
MaxQuantity uint16 `db:"max_quantity"`
|
||||
UsedQuantity uint16 `db:"used_quantity"`
|
||||
RoadFloors uint16 `db:"road_floors"`
|
||||
@@ -61,19 +61,30 @@ 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)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.Z2 {
|
||||
bf.WriteUint32(item.ID)
|
||||
}
|
||||
bf.WriteUint32(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)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.Z2 {
|
||||
bf.WriteUint16(item.MinGR)
|
||||
}
|
||||
bf.WriteUint8(0) // Unk
|
||||
bf.WriteUint8(item.StoreLevel)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.Z2 {
|
||||
bf.WriteUint16(item.MaxQuantity)
|
||||
bf.WriteUint16(item.UsedQuantity)
|
||||
}
|
||||
if _config.ErupeConfig.RealClientMode == _config.Z1 {
|
||||
bf.WriteUint8(uint8(item.RoadFloors))
|
||||
bf.WriteUint8(uint8(item.RoadFatalis))
|
||||
} else if _config.ErupeConfig.RealClientMode >= _config.Z2 {
|
||||
bf.WriteUint16(item.RoadFloors)
|
||||
bf.WriteUint16(item.RoadFatalis)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,103 +127,113 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
|
||||
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")
|
||||
rows, 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)
|
||||
bf := byteframe.NewByteFrame()
|
||||
var gacha Gacha
|
||||
for shopEntries.Next() {
|
||||
err = shopEntries.StructScan(&gacha)
|
||||
if err != nil {
|
||||
continue
|
||||
var gachas []Gacha
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&gacha)
|
||||
if err == nil {
|
||||
gachas = append(gachas, gacha)
|
||||
}
|
||||
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())
|
||||
bf.WriteUint16(uint16(len(gachas)))
|
||||
bf.WriteUint16(uint16(len(gachas)))
|
||||
for _, g := range gachas {
|
||||
bf.WriteUint32(g.ID)
|
||||
bf.WriteUint32(0) // Unknown rank restrictions
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(g.MinGR)
|
||||
bf.WriteUint32(g.MinHR)
|
||||
bf.WriteUint32(0) // only 0 in known packet
|
||||
ps.Uint8(bf, g.Name, true)
|
||||
ps.Uint8(bf, g.URLBanner, false)
|
||||
ps.Uint8(bf, g.URLFeature, false)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G10 {
|
||||
bf.WriteBool(g.Wide)
|
||||
ps.Uint8(bf, g.URLThumbnail, false)
|
||||
}
|
||||
if g.Recommended {
|
||||
bf.WriteUint16(2)
|
||||
} else {
|
||||
bf.WriteUint16(0)
|
||||
}
|
||||
bf.WriteUint8(g.GachaType)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G10 {
|
||||
bf.WriteBool(g.Hidden)
|
||||
}
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.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)
|
||||
rows, err := s.server.db.Queryx(`SELECT entry_type, id, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points, COALESCE(name, '') AS 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)
|
||||
|
||||
var entry GachaEntry
|
||||
var entries []GachaEntry
|
||||
var item GachaItem
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&entry)
|
||||
if err == nil {
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
}
|
||||
bf.WriteUint16(uint16(len(entries)))
|
||||
for _, ge := range entries {
|
||||
var items []GachaItem
|
||||
bf.WriteUint8(ge.EntryType)
|
||||
bf.WriteUint32(ge.ID)
|
||||
bf.WriteUint8(ge.ItemType)
|
||||
bf.WriteUint32(ge.ItemNumber)
|
||||
bf.WriteUint16(ge.ItemQuantity)
|
||||
if gachaType >= 4 { // If box
|
||||
bf.WriteUint16(1)
|
||||
} else {
|
||||
bf.WriteUint16(uint16(gachaEntry.Weight / divisor))
|
||||
bf.WriteUint16(uint16(ge.Weight / divisor))
|
||||
}
|
||||
bf.WriteUint8(gachaEntry.Rarity)
|
||||
bf.WriteUint8(gachaEntry.Rolls)
|
||||
bf.WriteUint8(ge.Rarity)
|
||||
bf.WriteUint8(ge.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)
|
||||
rows, err = s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id=$1`, ge.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)
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&item)
|
||||
if err == nil {
|
||||
items = append(items, item)
|
||||
}
|
||||
}
|
||||
bf.WriteUint8(itemCount)
|
||||
bf.WriteUint8(uint8(len(items)))
|
||||
}
|
||||
|
||||
bf.WriteUint16(gachaEntry.FrontierPoints)
|
||||
bf.WriteUint8(gachaEntry.DailyLimit)
|
||||
if gachaEntry.EntryType < 10 {
|
||||
ps.Uint8(bf, gachaEntry.Name, true)
|
||||
bf.WriteUint16(ge.FrontierPoints)
|
||||
bf.WriteUint8(ge.DailyLimit)
|
||||
if ge.EntryType < 10 {
|
||||
ps.Uint8(bf, ge.Name, true)
|
||||
} else {
|
||||
bf.WriteUint8(0)
|
||||
}
|
||||
bf.WriteBytes(temp.Data())
|
||||
for _, gi := range items {
|
||||
bf.WriteUint16(uint16(gi.ItemType))
|
||||
bf.WriteUint16(gi.ItemID)
|
||||
bf.WriteUint16(gi.Quantity)
|
||||
}
|
||||
}
|
||||
bf.Seek(4, 0)
|
||||
bf.WriteUint16(entryCount)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
case 3: // Hunting Festival Exchange
|
||||
fallthrough
|
||||
@@ -231,6 +252,9 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
|
||||
case 10: // Item shop, 0-8
|
||||
bf := byteframe.NewByteFrame()
|
||||
items := getShopItems(s, pkt.ShopType, pkt.ShopID)
|
||||
if len(items) > int(pkt.Limit) {
|
||||
items = items[:pkt.Limit]
|
||||
}
|
||||
writeShopItems(bf, items)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
@@ -424,7 +448,7 @@ func handleMsgMhfReceiveGachaItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPlayNormalGacha)
|
||||
bf := byteframe.NewByteFrame()
|
||||
var gachaEntries []GachaEntry
|
||||
var entries []GachaEntry
|
||||
var entry GachaEntry
|
||||
var rewards []GachaItem
|
||||
var reward GachaItem
|
||||
@@ -433,31 +457,40 @@ func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
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)
|
||||
|
||||
rows, 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)
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&entry)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for items.Next() {
|
||||
items.StructScan(&reward)
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
|
||||
rewardEntries, err := getRandomEntries(entries, rolls, false)
|
||||
temp := byteframe.NewByteFrame()
|
||||
for i := range rewardEntries {
|
||||
rows, 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 rows.Next() {
|
||||
err = rows.StructScan(&reward)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
rewards = append(rewards, reward)
|
||||
temp.WriteUint8(reward.ItemType)
|
||||
temp.WriteUint16(reward.ItemID)
|
||||
temp.WriteUint16(reward.Quantity)
|
||||
temp.WriteUint8(entry.Rarity)
|
||||
temp.WriteUint8(rewardEntries[i].Rarity)
|
||||
}
|
||||
}
|
||||
|
||||
bf.WriteUint8(uint8(len(rewards)))
|
||||
bf.WriteBytes(temp.Data())
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
@@ -467,7 +500,7 @@ func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfPlayStepupGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPlayStepupGacha)
|
||||
bf := byteframe.NewByteFrame()
|
||||
var gachaEntries []GachaEntry
|
||||
var entries []GachaEntry
|
||||
var entry GachaEntry
|
||||
var rewards []GachaItem
|
||||
var reward GachaItem
|
||||
@@ -479,40 +512,49 @@ func handleMsgMhfPlayStepupGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
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)
|
||||
|
||||
rows, 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)
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&entry)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for items.Next() {
|
||||
items.StructScan(&reward)
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
|
||||
guaranteedItems := getGuaranteedItems(s, pkt.GachaID, pkt.RollType)
|
||||
rewardEntries, err := getRandomEntries(entries, rolls, false)
|
||||
temp := byteframe.NewByteFrame()
|
||||
for i := range rewardEntries {
|
||||
rows, 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 rows.Next() {
|
||||
err = rows.StructScan(&reward)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
rewards = append(rewards, reward)
|
||||
temp.WriteUint8(reward.ItemType)
|
||||
temp.WriteUint16(reward.ItemID)
|
||||
temp.WriteUint16(reward.Quantity)
|
||||
temp.WriteUint8(entry.Rarity)
|
||||
temp.WriteUint8(rewardEntries[i].Rarity)
|
||||
}
|
||||
}
|
||||
|
||||
bf.WriteUint8(uint8(len(rewards) + len(guaranteedItems)))
|
||||
bf.WriteUint8(uint8(len(rewards)))
|
||||
for _, item := range guaranteedItems {
|
||||
bf.WriteUint8(item.ItemType)
|
||||
bf.WriteUint16(item.ItemID)
|
||||
bf.WriteUint16(item.Quantity)
|
||||
bf.WriteUint8(0)
|
||||
}
|
||||
bf.WriteBytes(temp.Data())
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
addGachaItem(s, rewards)
|
||||
@@ -561,7 +603,7 @@ func handleMsgMhfGetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfPlayBoxGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPlayBoxGacha)
|
||||
bf := byteframe.NewByteFrame()
|
||||
var gachaEntries []GachaEntry
|
||||
var entries []GachaEntry
|
||||
var entry GachaEntry
|
||||
var rewards []GachaItem
|
||||
var reward GachaItem
|
||||
@@ -570,17 +612,18 @@ func handleMsgMhfPlayBoxGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
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)
|
||||
rows, 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)
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&entry)
|
||||
if err == nil {
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
}
|
||||
rewardEntries, err := getRandomEntries(gachaEntries, rolls, true)
|
||||
rewardEntries, err := getRandomEntries(entries, 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 {
|
||||
@@ -588,16 +631,19 @@ func handleMsgMhfPlayBoxGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
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)
|
||||
err = items.StructScan(&reward)
|
||||
if err == nil {
|
||||
rewards = append(rewards, reward)
|
||||
}
|
||||
}
|
||||
}
|
||||
bf.WriteUint8(uint8(len(rewards)))
|
||||
bf.WriteBytes(temp.Data())
|
||||
for _, r := range rewards {
|
||||
bf.WriteUint8(r.ItemType)
|
||||
bf.WriteUint16(r.ItemID)
|
||||
bf.WriteUint16(r.Quantity)
|
||||
bf.WriteUint8(0)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
addGachaItem(s, rewards)
|
||||
}
|
||||
@@ -632,59 +678,59 @@ func handleMsgMhfExchangeItem2Fpoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
type FPointExchange struct {
|
||||
ID uint32 `db:"id"`
|
||||
ItemType uint8 `db:"item_type"`
|
||||
ItemID uint16 `db:"item_id"`
|
||||
Quantity uint16 `db:"quantity"`
|
||||
FPoints uint16 `db:"fpoints"`
|
||||
Buyable bool `db:"buyable"`
|
||||
}
|
||||
|
||||
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")
|
||||
bf := byteframe.NewByteFrame()
|
||||
var exchange FPointExchange
|
||||
var exchanges []FPointExchange
|
||||
var buyables uint16
|
||||
rows, err := s.server.db.Queryx(`SELECT id, item_type, item_id, quantity, fpoints, buyable FROM fpoint_items ORDER BY buyable DESC`)
|
||||
if err == nil {
|
||||
for buyRows.Next() {
|
||||
err = buyRows.Scan(&id, &itemType, &itemID, &quantity, &fPoints)
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&exchange)
|
||||
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
|
||||
if exchange.Buyable {
|
||||
buyables++
|
||||
}
|
||||
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++
|
||||
exchanges = append(exchanges, exchange)
|
||||
}
|
||||
}
|
||||
resp.Seek(0, 0)
|
||||
resp.WriteUint16(buyables)
|
||||
resp.WriteUint16(sellables)
|
||||
if _config.ErupeConfig.RealClientMode <= _config.Z2 {
|
||||
bf.WriteUint8(uint8(len(exchanges)))
|
||||
bf.WriteUint8(uint8(buyables))
|
||||
} else {
|
||||
bf.WriteUint16(uint16(len(exchanges)))
|
||||
bf.WriteUint16(buyables)
|
||||
}
|
||||
for _, e := range exchanges {
|
||||
bf.WriteUint32(e.ID)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint8(e.ItemType)
|
||||
bf.WriteUint16(e.ItemID)
|
||||
bf.WriteUint16(e.Quantity)
|
||||
bf.WriteUint16(e.FPoints)
|
||||
}
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.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
|
||||
pkt := p.(*mhfpacket.MsgMhfPlayFreeGacha)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(1)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
@@ -55,7 +55,6 @@ func doStageTransfer(s *Session, ackHandle uint32, stageID string) {
|
||||
|
||||
// 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()
|
||||
|
||||
@@ -153,17 +152,13 @@ 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 {
|
||||
if s.stage != nil {
|
||||
s.stage.Lock()
|
||||
s.stage.reservedClientSlots[s.charID] = false
|
||||
s.stage.Unlock()
|
||||
s.stageMoveStack.Push(s.stageID)
|
||||
s.stageMoveStack.Lock()
|
||||
s.stageMoveStack.Push(s.stage.id)
|
||||
}
|
||||
|
||||
s.QueueSendMHF(&mhfpacket.MsgSysCleanupObject{})
|
||||
if s.reservationStage != nil {
|
||||
s.reservationStage = nil
|
||||
}
|
||||
@@ -175,10 +170,9 @@ 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 backStage == "" || err != nil {
|
||||
backStage = "sl1Ns200p0a0u0"
|
||||
}
|
||||
|
||||
if _, exists := s.stage.reservedClientSlots[s.charID]; exists {
|
||||
@@ -194,12 +188,6 @@ func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -207,9 +195,12 @@ 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})
|
||||
if stage, exists := s.server.stages[pkt.StageID]; exists {
|
||||
stage.Lock()
|
||||
stage.locked = true
|
||||
stage.Unlock()
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgSysUnlockStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
@@ -219,7 +210,9 @@ func handleMsgSysUnlockStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
for charID := range s.reservationStage.reservedClientSlots {
|
||||
session := s.server.FindSessionByCharID(charID)
|
||||
session.QueueSendMHF(&mhfpacket.MsgSysStageDestruct{})
|
||||
if session != nil {
|
||||
session.QueueSendMHF(&mhfpacket.MsgSysStageDestruct{})
|
||||
}
|
||||
}
|
||||
|
||||
delete(s.server.stages, s.reservationStage.id)
|
||||
@@ -242,6 +235,10 @@ func handleMsgSysReserveStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
} else if uint16(len(stage.reservedClientSlots)) < stage.maxPlayers {
|
||||
if stage.locked {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
if len(stage.password) > 0 {
|
||||
if stage.password != s.stagePass {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
@@ -367,39 +364,43 @@ func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
defer s.server.stagesLock.RUnlock()
|
||||
|
||||
// Build the response
|
||||
resp := byteframe.NewByteFrame()
|
||||
bf := byteframe.NewByteFrame()
|
||||
var joinable int
|
||||
var joinable uint16
|
||||
bf.WriteUint16(0)
|
||||
for sid, stage := range s.server.stages {
|
||||
stage.RLock()
|
||||
defer stage.RUnlock()
|
||||
|
||||
if len(stage.reservedClientSlots) == 0 && len(stage.clients) == 0 {
|
||||
stage.RUnlock()
|
||||
continue
|
||||
}
|
||||
|
||||
if !strings.Contains(stage.id, pkt.StagePrefix) {
|
||||
stage.RUnlock()
|
||||
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)
|
||||
bf.WriteUint16(uint16(len(stage.reservedClientSlots)))
|
||||
bf.WriteUint16(uint16(len(stage.clients)))
|
||||
if strings.HasPrefix(stage.id, "sl2Ls") {
|
||||
bf.WriteUint16(uint16(len(stage.clients) + len(stage.reservedClientSlots)))
|
||||
} else {
|
||||
resp.WriteUint8(0)
|
||||
bf.WriteUint16(uint16(len(stage.clients)))
|
||||
}
|
||||
ps.Uint8(resp, sid, false)
|
||||
bf.WriteUint16(stage.maxPlayers)
|
||||
var flags uint8
|
||||
if stage.locked {
|
||||
flags |= 1
|
||||
}
|
||||
if len(stage.password) > 0 {
|
||||
flags |= 2
|
||||
}
|
||||
bf.WriteUint8(flags)
|
||||
ps.Uint8(bf, sid, false)
|
||||
stage.RUnlock()
|
||||
}
|
||||
bf.WriteUint16(uint16(joinable))
|
||||
bf.WriteBytes(resp.Data())
|
||||
bf.Seek(0, 0)
|
||||
bf.WriteUint16(joinable)
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
_config "erupe-ce/config"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
@@ -16,8 +18,8 @@ type TowerInfoTRP struct {
|
||||
}
|
||||
|
||||
type TowerInfoSkill struct {
|
||||
TSP int32
|
||||
Unk1 []int16 // 40
|
||||
TSP int32
|
||||
Skills []int16 // 64
|
||||
}
|
||||
|
||||
type TowerInfoHistory struct {
|
||||
@@ -32,6 +34,14 @@ type TowerInfoLevel struct {
|
||||
Unk3 int32
|
||||
}
|
||||
|
||||
func EmptyTowerCSV(len int) string {
|
||||
temp := make([]string, len)
|
||||
for i := range temp {
|
||||
temp[i] = "0"
|
||||
}
|
||||
return strings.Join(temp, ",")
|
||||
}
|
||||
|
||||
func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetTowerInfo)
|
||||
var data []*byteframe.ByteFrame
|
||||
@@ -44,21 +54,24 @@ func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
towerInfo := TowerInfo{
|
||||
TRP: []TowerInfoTRP{{0, 0}},
|
||||
Skill: []TowerInfoSkill{{0, make([]int16, 40)}},
|
||||
Skill: []TowerInfoSkill{{0, make([]int16, 64)}},
|
||||
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)
|
||||
var tempSkills string
|
||||
err := s.server.db.QueryRow(`SELECT COALESCE(tr, 0), COALESCE(trp, 0), COALESCE(tsp, 0), COALESCE(block1, 0), COALESCE(block2, 0), COALESCE(skills, $1) FROM tower WHERE char_id=$2
|
||||
`, EmptyTowerCSV(64), 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)
|
||||
}
|
||||
|
||||
if _config.ErupeConfig.RealClientMode <= _config.G7 {
|
||||
towerInfo.Level = towerInfo.Level[:1]
|
||||
}
|
||||
|
||||
for i, skill := range stringsupport.CSVElems(tempSkills) {
|
||||
towerInfo.Skill[0].Unk1[i] = int16(skill)
|
||||
towerInfo.Skill[0].Skills[i] = int16(skill)
|
||||
}
|
||||
|
||||
switch pkt.InfoType {
|
||||
@@ -73,8 +86,8 @@ func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
for _, skills := range towerInfo.Skill {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteInt32(skills.TSP)
|
||||
for i := range skills.Unk1 {
|
||||
bf.WriteInt16(skills.Unk1[i])
|
||||
for i := range skills.Skills {
|
||||
bf.WriteInt16(skills.Skills[i])
|
||||
}
|
||||
data = append(data, bf)
|
||||
}
|
||||
@@ -89,7 +102,7 @@ func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
data = append(data, bf)
|
||||
}
|
||||
case 5:
|
||||
case 3, 5:
|
||||
for _, level := range towerInfo.Level {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteInt32(level.Floors)
|
||||
@@ -105,7 +118,7 @@ func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfPostTowerInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostTowerInfo)
|
||||
|
||||
if s.server.erupeConfig.DevModeOptions.QuestDebugTools {
|
||||
if s.server.erupeConfig.DebugOptions.QuestTools {
|
||||
s.logger.Debug(
|
||||
p.Opcode().String(),
|
||||
zap.Uint32("InfoType", pkt.InfoType),
|
||||
@@ -123,11 +136,12 @@ func handleMsgMhfPostTowerInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
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)
|
||||
var skills string
|
||||
s.server.db.QueryRow(`SELECT COALESCE(skills, $1) FROM tower WHERE char_id=$2`, EmptyTowerCSV(64), 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)
|
||||
case 1, 7:
|
||||
// This might give too much TSP? No idea what the rate is supposed to be
|
||||
s.server.db.Exec(`UPDATE tower SET tr=$1, trp=COALESCE(trp, 0)+$2, tsp=COALESCE(tsp, 0)+$3, block1=COALESCE(block1, 0)+$4 WHERE char_id=$5`, pkt.TR, pkt.TRP, pkt.Cost, pkt.Block1, s.charID)
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
@@ -327,7 +341,7 @@ func handleMsgMhfGetTenrouirai(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfPostTenrouirai(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostTenrouirai)
|
||||
|
||||
if s.server.erupeConfig.DevModeOptions.QuestDebugTools {
|
||||
if s.server.erupeConfig.DebugOptions.QuestTools {
|
||||
s.logger.Debug(
|
||||
p.Opcode().String(),
|
||||
zap.Uint8("Unk0", pkt.Unk0),
|
||||
@@ -411,10 +425,10 @@ func handleMsgMhfGetGemInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
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)
|
||||
var tempGems string
|
||||
s.server.db.QueryRow(`SELECT COALESCE(gems, $1) FROM tower WHERE char_id=$2`, EmptyTowerCSV(30), s.charID).Scan(&tempGems)
|
||||
for i, v := range stringsupport.CSVElems(tempGems) {
|
||||
gemInfo = append(gemInfo, GemInfo{uint16(((i / 5) * 256) + ((i % 5) + 1)), uint16(v)})
|
||||
gemInfo = append(gemInfo, GemInfo{uint16((i / 5 << 8) + (i%5 + 1)), uint16(v)})
|
||||
}
|
||||
|
||||
switch pkt.Unk0 {
|
||||
@@ -441,7 +455,7 @@ func handleMsgMhfGetGemInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfPostGemInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostGemInfo)
|
||||
|
||||
if s.server.erupeConfig.DevModeOptions.QuestDebugTools {
|
||||
if s.server.erupeConfig.DebugOptions.QuestTools {
|
||||
s.logger.Debug(
|
||||
p.Opcode().String(),
|
||||
zap.Uint32("Op", pkt.Op),
|
||||
@@ -454,11 +468,11 @@ func handleMsgMhfPostGemInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
var gems string
|
||||
s.server.db.QueryRow(`SELECT COALESCE(gems, $1) FROM tower WHERE char_id=$2`, EmptyTowerCSV(30), 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))
|
||||
i := int((pkt.Gem >> 8 * 5) + (pkt.Gem - pkt.Gem&0xFF00 - 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
|
||||
|
||||
@@ -3,11 +3,13 @@ package channelserver
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
"erupe-ce/config"
|
||||
_config "erupe-ce/config"
|
||||
"erupe-ce/network/binpacket"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"erupe-ce/server/discordbot"
|
||||
@@ -55,7 +57,7 @@ type Server struct {
|
||||
stages map[string]*Stage
|
||||
|
||||
// Used to map different languages
|
||||
dict map[string]string
|
||||
i18n i18n
|
||||
|
||||
// UserBinary
|
||||
userBinaryPartsLock sync.RWMutex
|
||||
@@ -72,65 +74,37 @@ type Server struct {
|
||||
name string
|
||||
|
||||
raviente *Raviente
|
||||
|
||||
questCacheData map[int][]byte
|
||||
questCacheTime map[int]time.Time
|
||||
}
|
||||
|
||||
type Raviente struct {
|
||||
sync.Mutex
|
||||
|
||||
register *RavienteRegister
|
||||
state *RavienteState
|
||||
support *RavienteSupport
|
||||
id uint16
|
||||
register []uint32
|
||||
state []uint32
|
||||
support []uint32
|
||||
}
|
||||
|
||||
type RavienteRegister struct {
|
||||
nextTime uint32
|
||||
startTime uint32
|
||||
postTime uint32
|
||||
killedTime uint32
|
||||
ravienteType uint32
|
||||
maxPlayers uint32
|
||||
carveQuest uint32
|
||||
register []uint32
|
||||
}
|
||||
|
||||
type RavienteState struct {
|
||||
stateData []uint32
|
||||
}
|
||||
|
||||
type RavienteSupport struct {
|
||||
supportData []uint32
|
||||
}
|
||||
|
||||
// Set up the Raviente variables for the server
|
||||
func NewRaviente() *Raviente {
|
||||
ravienteRegister := &RavienteRegister{
|
||||
nextTime: 0,
|
||||
startTime: 0,
|
||||
killedTime: 0,
|
||||
postTime: 0,
|
||||
ravienteType: 0,
|
||||
maxPlayers: 0,
|
||||
carveQuest: 0,
|
||||
func (s *Server) resetRaviente() {
|
||||
for _, semaphore := range s.semaphore {
|
||||
if strings.HasPrefix(semaphore.name, "hs_l0") {
|
||||
return
|
||||
}
|
||||
}
|
||||
ravienteState := &RavienteState{}
|
||||
ravienteSupport := &RavienteSupport{}
|
||||
ravienteRegister.register = []uint32{0, 0, 0, 0, 0}
|
||||
ravienteState.stateData = []uint32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
ravienteSupport.supportData = []uint32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
|
||||
raviente := &Raviente{
|
||||
register: ravienteRegister,
|
||||
state: ravienteState,
|
||||
support: ravienteSupport,
|
||||
}
|
||||
return raviente
|
||||
s.logger.Debug("All Raviente Semaphores empty, resetting")
|
||||
s.raviente.id = s.raviente.id + 1
|
||||
s.raviente.register = make([]uint32, 30)
|
||||
s.raviente.state = make([]uint32, 30)
|
||||
s.raviente.support = make([]uint32, 30)
|
||||
}
|
||||
|
||||
func (r *Raviente) GetRaviMultiplier(s *Server) float64 {
|
||||
raviSema := getRaviSemaphore(s)
|
||||
func (s *Server) GetRaviMultiplier() float64 {
|
||||
raviSema := s.getRaviSemaphore()
|
||||
if raviSema != nil {
|
||||
var minPlayers int
|
||||
if r.register.maxPlayers > 8 {
|
||||
if s.raviente.register[9] > 8 {
|
||||
minPlayers = 24
|
||||
} else {
|
||||
minPlayers = 4
|
||||
@@ -143,6 +117,33 @@ func (r *Raviente) GetRaviMultiplier(s *Server) float64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *Server) UpdateRavi(semaID uint32, index uint8, value uint32, update bool) (uint32, uint32) {
|
||||
var prev uint32
|
||||
var dest *[]uint32
|
||||
switch semaID {
|
||||
case 0x40000:
|
||||
switch index {
|
||||
case 17, 28: // Ignore res and poison
|
||||
break
|
||||
default:
|
||||
value = uint32(float64(value) * s.GetRaviMultiplier())
|
||||
}
|
||||
dest = &s.raviente.state
|
||||
case 0x50000:
|
||||
dest = &s.raviente.support
|
||||
case 0x60000:
|
||||
dest = &s.raviente.register
|
||||
default:
|
||||
return 0, 0
|
||||
}
|
||||
if update {
|
||||
(*dest)[index] += value
|
||||
} else {
|
||||
(*dest)[index] = value
|
||||
}
|
||||
return prev, (*dest)[index]
|
||||
}
|
||||
|
||||
// NewServer creates a new Server type.
|
||||
func NewServer(config *Config) *Server {
|
||||
s := &Server{
|
||||
@@ -160,7 +161,14 @@ func NewServer(config *Config) *Server {
|
||||
semaphoreIndex: 7,
|
||||
discordBot: config.DiscordBot,
|
||||
name: config.Name,
|
||||
raviente: NewRaviente(),
|
||||
raviente: &Raviente{
|
||||
id: 1,
|
||||
register: make([]uint32, 30),
|
||||
state: make([]uint32, 30),
|
||||
support: make([]uint32, 30),
|
||||
},
|
||||
questCacheData: make(map[int][]byte),
|
||||
questCacheTime: make(map[int]time.Time),
|
||||
}
|
||||
|
||||
// Mezeporta
|
||||
@@ -184,7 +192,7 @@ func NewServer(config *Config) *Server {
|
||||
// MezFes
|
||||
s.stages["sl1Ns462p0a0u0"] = NewStage("sl1Ns462p0a0u0")
|
||||
|
||||
s.dict = getLangStrings(s)
|
||||
s.i18n = getLangStrings(s)
|
||||
|
||||
return s
|
||||
}
|
||||
@@ -203,6 +211,7 @@ func (s *Server) Start() error {
|
||||
// Start the discord bot for chat integration.
|
||||
if s.erupeConfig.Discord.Enabled && s.discordBot != nil {
|
||||
s.discordBot.Session.AddHandler(s.onDiscordMessage)
|
||||
s.discordBot.Session.AddHandler(s.onInteraction)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -314,7 +323,6 @@ func (s *Server) BroadcastChatMessage(message string) {
|
||||
msgBinChat.Build(bf)
|
||||
|
||||
s.BroadcastMHF(&mhfpacket.MsgSysCastedBinary{
|
||||
CharID: 0xFFFFFFFF,
|
||||
MessageType: BinaryMessageTypeChat,
|
||||
RawDataPayload: bf.Data(),
|
||||
}, nil)
|
||||
@@ -329,13 +337,13 @@ func (s *Server) BroadcastRaviente(ip uint32, port uint16, stage []byte, _type u
|
||||
var text string
|
||||
switch _type {
|
||||
case 2:
|
||||
text = s.dict["ravienteBerserk"]
|
||||
text = s.i18n.raviente.berserk
|
||||
case 3:
|
||||
text = s.dict["ravienteExtreme"]
|
||||
text = s.i18n.raviente.extreme
|
||||
case 4:
|
||||
text = s.dict["ravienteExtremeLimited"]
|
||||
text = s.i18n.raviente.extremeLimited
|
||||
case 5:
|
||||
text = s.dict["ravienteBerserkSmall"]
|
||||
text = s.i18n.raviente.berserkSmall
|
||||
default:
|
||||
s.logger.Error("Unk raviente type", zap.Uint8("_type", _type))
|
||||
}
|
||||
@@ -346,7 +354,6 @@ func (s *Server) BroadcastRaviente(ip uint32, port uint16, stage []byte, _type u
|
||||
bf.WriteUint16(0) // Unk
|
||||
bf.WriteBytes(stage)
|
||||
s.WorldcastMHF(&mhfpacket.MsgSysCastedBinary{
|
||||
CharID: 0x00000000,
|
||||
BroadcastType: BroadcastTypeServer,
|
||||
MessageType: BinaryMessageTypeChat,
|
||||
RawDataPayload: bf.Data(),
|
||||
@@ -371,6 +378,26 @@ func (s *Server) FindSessionByCharID(charID uint32) *Session {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) DisconnectUser(uid uint32) {
|
||||
var cid uint32
|
||||
var cids []uint32
|
||||
rows, _ := s.db.Query(`SELECT id FROM characters WHERE user_id=$1`, uid)
|
||||
for rows.Next() {
|
||||
rows.Scan(&cid)
|
||||
cids = append(cids, cid)
|
||||
}
|
||||
for _, c := range s.Channels {
|
||||
for _, session := range c.sessions {
|
||||
for _, cid := range cids {
|
||||
if session.charID == cid {
|
||||
session.rawConn.Close()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) FindObjectByChar(charID uint32) *Object {
|
||||
s.stagesLock.RLock()
|
||||
defer s.stagesLock.RUnlock()
|
||||
@@ -393,15 +420,16 @@ func (s *Server) NextSemaphoreID() uint32 {
|
||||
for {
|
||||
exists := false
|
||||
s.semaphoreIndex = s.semaphoreIndex + 1
|
||||
if s.semaphoreIndex == 0 {
|
||||
s.semaphoreIndex = 7 // Skip reserved indexes
|
||||
if s.semaphoreIndex > 0xFFFF {
|
||||
s.semaphoreIndex = 1
|
||||
}
|
||||
for _, semaphore := range s.semaphore {
|
||||
if semaphore.id == s.semaphoreIndex {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if exists == false {
|
||||
if !exists {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,110 +1,238 @@
|
||||
package channelserver
|
||||
|
||||
func getLangStrings(s *Server) map[string]string {
|
||||
strings := make(map[string]string)
|
||||
type i18n struct {
|
||||
language string
|
||||
cafe struct {
|
||||
reset string
|
||||
}
|
||||
timer string
|
||||
commands struct {
|
||||
noOp string
|
||||
disabled string
|
||||
reload string
|
||||
kqf struct {
|
||||
get string
|
||||
set struct {
|
||||
error string
|
||||
success string
|
||||
}
|
||||
version string
|
||||
}
|
||||
rights struct {
|
||||
error string
|
||||
success string
|
||||
}
|
||||
course struct {
|
||||
error string
|
||||
disabled string
|
||||
enabled string
|
||||
locked string
|
||||
}
|
||||
teleport struct {
|
||||
error string
|
||||
success string
|
||||
}
|
||||
psn struct {
|
||||
error string
|
||||
success string
|
||||
exists string
|
||||
}
|
||||
discord struct {
|
||||
success string
|
||||
}
|
||||
ban struct {
|
||||
success string
|
||||
noUser string
|
||||
invalid string
|
||||
error string
|
||||
length string
|
||||
}
|
||||
timer struct {
|
||||
enabled string
|
||||
disabled string
|
||||
}
|
||||
ravi struct {
|
||||
noCommand string
|
||||
start struct {
|
||||
success string
|
||||
error string
|
||||
}
|
||||
multiplier string
|
||||
res struct {
|
||||
success string
|
||||
error string
|
||||
}
|
||||
sed struct {
|
||||
success string
|
||||
}
|
||||
request string
|
||||
error string
|
||||
noPlayers string
|
||||
version string
|
||||
}
|
||||
}
|
||||
raviente struct {
|
||||
berserk string
|
||||
extreme string
|
||||
extremeLimited string
|
||||
berserkSmall string
|
||||
}
|
||||
guild struct {
|
||||
invite struct {
|
||||
title string
|
||||
body string
|
||||
success struct {
|
||||
title string
|
||||
body string
|
||||
}
|
||||
accepted struct {
|
||||
title string
|
||||
body string
|
||||
}
|
||||
rejected struct {
|
||||
title string
|
||||
body string
|
||||
}
|
||||
declined struct {
|
||||
title string
|
||||
body string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getLangStrings(s *Server) i18n {
|
||||
var i i18n
|
||||
switch s.erupeConfig.Language {
|
||||
case "jp":
|
||||
strings["language"] = "日本語"
|
||||
strings["cafeReset"] = "%d/%dにリセット"
|
||||
i.language = "日本語"
|
||||
i.cafe.reset = "%d/%dにリセット"
|
||||
i.timer = "タイマー:%02d'%02d\"%02d.%03d (%df)"
|
||||
|
||||
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は既存のユーザに接続されています"
|
||||
i.commands.noOp = "You don't have permission to use this command"
|
||||
i.commands.disabled = "%sのコマンドは無効です"
|
||||
i.commands.reload = "リロードします"
|
||||
i.commands.kqf.get = "現在のキークエストフラグ:%x"
|
||||
i.commands.kqf.set.error = "キークエコマンドエラー 例:%s set xxxxxxxxxxxxxxxx"
|
||||
i.commands.kqf.set.success = "キークエストのフラグが更新されました。ワールド/ランドを移動してください"
|
||||
i.commands.kqf.version = "This command is disabled prior to MHFG10"
|
||||
i.commands.rights.error = "コース更新コマンドエラー 例:%s x"
|
||||
i.commands.rights.success = "コース情報を更新しました:%d"
|
||||
i.commands.course.error = "コース確認コマンドエラー 例:%s <name>"
|
||||
i.commands.course.disabled = "%sコースは無効です"
|
||||
i.commands.course.enabled = "%sコースは有効です"
|
||||
i.commands.course.locked = "%sコースはロックされています"
|
||||
i.commands.teleport.error = "テレポートコマンドエラー 構文:%s x y"
|
||||
i.commands.teleport.success = "%d %dにテレポート"
|
||||
i.commands.psn.error = "PSN連携コマンドエラー 例:%s <psn id>"
|
||||
i.commands.psn.success = "PSN「%s」が連携されています"
|
||||
i.commands.psn.exists = "PSNは既存のユーザに接続されています"
|
||||
|
||||
strings["commandRaviNoCommand"] = "ラヴィコマンドが指定されていません"
|
||||
strings["commandRaviStartSuccess"] = "大討伐を開始します"
|
||||
strings["commandRaviStartError"] = "大討伐は既に開催されています"
|
||||
strings["commandRaviMultiplier"] = "ラヴィダメージ倍率:x%.2f"
|
||||
strings["commandRaviResSuccess"] = "復活支援を実行します"
|
||||
strings["commandRaviResError"] = "復活支援は実行されませんでした"
|
||||
strings["commandRaviSedSuccess"] = "鎮静支援を実行します"
|
||||
strings["commandRaviRequest"] = "鎮静支援を要請します"
|
||||
strings["commandRaviError"] = "ラヴィコマンドが認識されません"
|
||||
strings["commandRaviNoPlayers"] = "誰も大討伐に参加していません"
|
||||
i.commands.discord.success = "あなたのDiscordトークン:%s"
|
||||
|
||||
strings["ravienteBerserk"] = "<大討伐:猛狂期>が開催されました!"
|
||||
strings["ravienteExtreme"] = "<大討伐:猛狂期【極】>が開催されました!"
|
||||
strings["ravienteExtremeLimited"] = "<大討伐:猛狂期【極】(制限付)>が開催されました!"
|
||||
strings["ravienteBerserkSmall"] = "<大討伐:猛狂期(小数)>が開催されました!"
|
||||
i.commands.ban.noUser = "Could not find user"
|
||||
i.commands.ban.success = "Successfully banned %s"
|
||||
i.commands.ban.invalid = "Invalid Character ID"
|
||||
i.commands.ban.error = "Error in command. Format: %s <id> [length]"
|
||||
i.commands.ban.length = " until %s"
|
||||
|
||||
strings["guildInviteName"] = "猟団勧誘のご案内"
|
||||
strings["guildInvite"] = "猟団「%s」からの勧誘通知です。\n「勧誘に返答」より、返答を行ってください。"
|
||||
i.commands.ravi.noCommand = "ラヴィコマンドが指定されていません"
|
||||
i.commands.ravi.start.success = "大討伐を開始します"
|
||||
i.commands.ravi.start.error = "大討伐は既に開催されています"
|
||||
i.commands.ravi.multiplier = "ラヴィダメージ倍率:x%.2f"
|
||||
i.commands.ravi.res.success = "復活支援を実行します"
|
||||
i.commands.ravi.res.error = "復活支援は実行されませんでした"
|
||||
i.commands.ravi.sed.success = "鎮静支援を実行します"
|
||||
i.commands.ravi.request = "鎮静支援を要請します"
|
||||
i.commands.ravi.error = "ラヴィコマンドが認識されません"
|
||||
i.commands.ravi.noPlayers = "誰も大討伐に参加していません"
|
||||
i.commands.ravi.version = "This command is disabled outside of MHFZZ"
|
||||
|
||||
strings["guildInviteSuccessName"] = "成功"
|
||||
strings["guildInviteSuccess"] = "あなたは「%s」に参加できました。"
|
||||
i.raviente.berserk = "<大討伐:猛狂期>が開催されました!"
|
||||
i.raviente.extreme = "<大討伐:猛狂期【極】>が開催されました!"
|
||||
i.raviente.extremeLimited = "<大討伐:猛狂期【極】(制限付)>が開催されました!"
|
||||
i.raviente.berserkSmall = "<大討伐:猛狂期(小数)>が開催されました!"
|
||||
|
||||
strings["guildInviteAcceptedName"] = "承諾されました"
|
||||
strings["guildInviteAccepted"] = "招待した狩人が「%s」への招待を承諾しました。"
|
||||
i.guild.invite.title = "猟団勧誘のご案内"
|
||||
i.guild.invite.body = "猟団「%s」からの勧誘通知です。\n「勧誘に返答」より、返答を行ってください。"
|
||||
|
||||
strings["guildInviteRejectName"] = "却下しました"
|
||||
strings["guildInviteReject"] = "あなたは「%s」への参加を却下しました。"
|
||||
i.guild.invite.success.title = "成功"
|
||||
i.guild.invite.success.body = "あなたは「%s」に参加できました。"
|
||||
|
||||
strings["guildInviteDeclinedName"] = "辞退しました"
|
||||
strings["guildInviteDeclined"] = "招待した狩人が「%s」への招待を辞退しました。"
|
||||
i.guild.invite.accepted.title = "承諾されました"
|
||||
i.guild.invite.accepted.body = "招待した狩人が「%s」への招待を承諾しました。"
|
||||
|
||||
i.guild.invite.rejected.title = "却下しました"
|
||||
i.guild.invite.rejected.body = "あなたは「%s」への参加を却下しました。"
|
||||
|
||||
i.guild.invite.declined.title = "辞退しました"
|
||||
i.guild.invite.declined.body = "招待した狩人が「%s」への招待を辞退しました。"
|
||||
default:
|
||||
strings["language"] = "English"
|
||||
strings["cafeReset"] = "Resets on %d/%d"
|
||||
i.language = "English"
|
||||
i.cafe.reset = "Resets on %d/%d"
|
||||
i.timer = "Time: %02d:%02d:%02d.%03d (%df)"
|
||||
|
||||
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!"
|
||||
i.commands.noOp = "You don't have permission to use this command"
|
||||
i.commands.disabled = "%s command is disabled"
|
||||
i.commands.reload = "Reloading players..."
|
||||
i.commands.kqf.get = "KQF: %x"
|
||||
i.commands.kqf.set.error = "Error in command. Format: %s set xxxxxxxxxxxxxxxx"
|
||||
i.commands.kqf.set.success = "KQF set, please switch Land/World"
|
||||
i.commands.kqf.version = "This command is disabled prior to MHFG10"
|
||||
i.commands.rights.error = "Error in command. Format: %s x"
|
||||
i.commands.rights.success = "Set rights integer: %d"
|
||||
i.commands.course.error = "Error in command. Format: %s <name>"
|
||||
i.commands.course.disabled = "%s Course disabled"
|
||||
i.commands.course.enabled = "%s Course enabled"
|
||||
i.commands.course.locked = "%s Course is locked"
|
||||
i.commands.teleport.error = "Error in command. Format: %s x y"
|
||||
i.commands.teleport.success = "Teleporting to %d %d"
|
||||
i.commands.psn.error = "Error in command. Format: %s <psn id>"
|
||||
i.commands.psn.success = "Connected PSN ID: %s"
|
||||
i.commands.psn.exists = "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!"
|
||||
i.commands.discord.success = "Your Discord token: %s"
|
||||
|
||||
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!"
|
||||
i.commands.ban.noUser = "Could not find user"
|
||||
i.commands.ban.success = "Successfully banned %s"
|
||||
i.commands.ban.invalid = "Invalid Character ID"
|
||||
i.commands.ban.error = "Error in command. Format: %s <id> [length]"
|
||||
i.commands.ban.length = " until %s"
|
||||
|
||||
strings["guildInviteName"] = "Invitation!"
|
||||
strings["guildInvite"] = "You have been invited to join\n「%s」\nDo you want to accept?"
|
||||
i.commands.timer.enabled = "Quest timer enabled"
|
||||
i.commands.timer.disabled = "Quest timer disabled"
|
||||
|
||||
strings["guildInviteSuccessName"] = "Success!"
|
||||
strings["guildInviteSuccess"] = "You have successfully joined\n「%s」."
|
||||
i.commands.ravi.noCommand = "No Raviente command specified!"
|
||||
i.commands.ravi.start.success = "The Great Slaying will begin in a moment"
|
||||
i.commands.ravi.start.error = "The Great Slaying has already begun!"
|
||||
i.commands.ravi.multiplier = "Raviente multiplier is currently %.2fx"
|
||||
i.commands.ravi.res.success = "Sending resurrection support!"
|
||||
i.commands.ravi.res.error = "Resurrection support has not been requested!"
|
||||
i.commands.ravi.sed.success = "Sending sedation support if requested!"
|
||||
i.commands.ravi.request = "Requesting sedation support!"
|
||||
i.commands.ravi.error = "Raviente command not recognised!"
|
||||
i.commands.ravi.noPlayers = "No one has joined the Great Slaying!"
|
||||
i.commands.ravi.version = "This command is disabled outside of MHFZZ"
|
||||
|
||||
strings["guildInviteAcceptedName"] = "Accepted"
|
||||
strings["guildInviteAccepted"] = "The recipient accepted your invitation to join\n「%s」."
|
||||
i.raviente.berserk = "<Great Slaying: Berserk> is being held!"
|
||||
i.raviente.extreme = "<Great Slaying: Extreme> is being held!"
|
||||
i.raviente.extremeLimited = "<Great Slaying: Extreme (Limited)> is being held!"
|
||||
i.raviente.berserkSmall = "<Great Slaying: Berserk (Small)> is being held!"
|
||||
|
||||
strings["guildInviteRejectName"] = "Rejected"
|
||||
strings["guildInviteReject"] = "You rejected the invitation to join\n「%s」."
|
||||
i.guild.invite.title = "Invitation!"
|
||||
i.guild.invite.body = "You have been invited to join\n「%s」\nDo you want to accept?"
|
||||
|
||||
strings["guildInviteDeclinedName"] = "Declined"
|
||||
strings["guildInviteDeclined"] = "The recipient declined your invitation to join\n「%s」."
|
||||
i.guild.invite.success.title = "Success!"
|
||||
i.guild.invite.success.body = "You have successfully joined\n「%s」."
|
||||
|
||||
i.guild.invite.accepted.title = "Accepted"
|
||||
i.guild.invite.accepted.body = "The recipient accepted your invitation to join\n「%s」."
|
||||
|
||||
i.guild.invite.rejected.title = "Rejected"
|
||||
i.guild.invite.rejected.body = "You rejected the invitation to join\n「%s」."
|
||||
|
||||
i.guild.invite.declined.title = "Declined"
|
||||
i.guild.invite.declined.body = "The recipient declined your invitation to join\n「%s」."
|
||||
}
|
||||
return strings
|
||||
return i
|
||||
}
|
||||
|
||||
@@ -7,55 +7,35 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Stage holds stage-specific information
|
||||
// Semaphore holds Semaphore-specific information
|
||||
type Semaphore struct {
|
||||
sync.RWMutex
|
||||
|
||||
// Stage ID string
|
||||
id_semaphore string
|
||||
// Semaphore ID string
|
||||
name string
|
||||
|
||||
id uint32
|
||||
|
||||
// Map of session -> charID.
|
||||
// These are clients that are CURRENTLY in the stage
|
||||
// These are clients that are registered to the Semaphore
|
||||
clients map[*Session]uint32
|
||||
|
||||
// Map of charID -> interface{}, only the key is used, value is always nil.
|
||||
reservedClientSlots map[uint32]interface{}
|
||||
|
||||
// Max Players for Semaphore
|
||||
maxPlayers uint16
|
||||
}
|
||||
|
||||
// NewStage creates a new stage with intialized values.
|
||||
// NewSemaphore creates a new Semaphore with intialized values
|
||||
func NewSemaphore(s *Server, ID string, MaxPlayers uint16) *Semaphore {
|
||||
sema := &Semaphore{
|
||||
id_semaphore: ID,
|
||||
id: s.NextSemaphoreID(),
|
||||
clients: make(map[*Session]uint32),
|
||||
reservedClientSlots: make(map[uint32]interface{}),
|
||||
maxPlayers: MaxPlayers,
|
||||
name: ID,
|
||||
id: s.NextSemaphoreID(),
|
||||
clients: make(map[*Session]uint32),
|
||||
maxPlayers: MaxPlayers,
|
||||
}
|
||||
return sema
|
||||
}
|
||||
|
||||
func (s *Semaphore) BroadcastRavi(pkt mhfpacket.MHFPacket) {
|
||||
// Broadcast the data.
|
||||
for session := range s.clients {
|
||||
|
||||
// Make the header
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(uint16(pkt.Opcode()))
|
||||
|
||||
// Build the packet onto the byteframe.
|
||||
pkt.Build(bf, session.clientContext)
|
||||
|
||||
// Enqueue in a non-blocking way that drops the packet if the connections send buffer channel is full.
|
||||
session.QueueSendNonBlocking(bf.Data())
|
||||
}
|
||||
}
|
||||
|
||||
// BroadcastMHF queues a MHFPacket to be sent to all sessions in the stage.
|
||||
// BroadcastMHF queues a MHFPacket to be sent to all sessions in the Semaphore
|
||||
func (s *Semaphore) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) {
|
||||
// Broadcast the data.
|
||||
for session := range s.clients {
|
||||
|
||||
@@ -36,7 +36,6 @@ type Session struct {
|
||||
|
||||
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
|
||||
@@ -62,8 +61,9 @@ type Session struct {
|
||||
mailList []int
|
||||
|
||||
// For Debuging
|
||||
Name string
|
||||
closed bool
|
||||
Name string
|
||||
closed bool
|
||||
ackStart map[uint32]time.Time
|
||||
}
|
||||
|
||||
// NewSession creates a new Session type.
|
||||
@@ -78,6 +78,7 @@ func NewSession(server *Server, conn net.Conn) *Session {
|
||||
lastPacket: time.Now(),
|
||||
sessionStart: TimeAdjusted().Unix(),
|
||||
stageMoveStack: stringstack.New(),
|
||||
ackStart: make(map[uint32]time.Time),
|
||||
}
|
||||
s.SetObjectID()
|
||||
return s
|
||||
@@ -85,13 +86,11 @@ func NewSession(server *Server, conn net.Conn) *Session {
|
||||
|
||||
// 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()
|
||||
}()
|
||||
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()
|
||||
go s.recvLoop()
|
||||
}
|
||||
|
||||
// QueueSend queues a packet (raw []byte) to be sent.
|
||||
@@ -149,16 +148,19 @@ func (s *Session) QueueAck(ackHandle uint32, data []byte) {
|
||||
}
|
||||
|
||||
func (s *Session) sendLoop() {
|
||||
var pkt packet
|
||||
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")
|
||||
for len(s.sendPackets) > 0 {
|
||||
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)
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,14 +179,13 @@ func (s *Session) recvLoop() {
|
||||
s.logger.Info(fmt.Sprintf("[%s] Disconnected", s.Name))
|
||||
logoutPlayer(s)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
} else 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)
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,6 +193,10 @@ func (s *Session) handlePacketGroup(pktGroup []byte) {
|
||||
s.lastPacket = time.Now()
|
||||
bf := byteframe.NewByteFrameFromBytes(pktGroup)
|
||||
opcodeUint16 := bf.ReadUint16()
|
||||
if len(bf.Data()) >= 6 {
|
||||
s.ackStart[bf.ReadUint32()] = time.Now()
|
||||
bf.Seek(2, io.SeekStart)
|
||||
}
|
||||
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.
|
||||
@@ -248,13 +253,9 @@ func ignored(opcode network.PacketID) bool {
|
||||
}
|
||||
|
||||
func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipient string) {
|
||||
if !s.server.erupeConfig.DevMode {
|
||||
if sender == "Server" && !s.server.erupeConfig.DebugOptions.LogOutboundMessages {
|
||||
return
|
||||
}
|
||||
|
||||
if sender == "Server" && !s.server.erupeConfig.DevModeOptions.LogOutboundMessages {
|
||||
return
|
||||
} else if !s.server.erupeConfig.DevModeOptions.LogInboundMessages {
|
||||
} else if sender != "Server" && !s.server.erupeConfig.DebugOptions.LogInboundMessages {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -262,12 +263,24 @@ func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipien
|
||||
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))
|
||||
var ackHandle uint32
|
||||
if len(data) >= 6 {
|
||||
ackHandle = binary.BigEndian.Uint32(data[2:6])
|
||||
}
|
||||
if t, ok := s.ackStart[ackHandle]; ok {
|
||||
fmt.Printf("[%s] -> [%s] (%fs)\n", sender, recipient, float64(time.Now().UnixNano()-t.UnixNano())/1000000000)
|
||||
} else {
|
||||
fmt.Printf("Data [%d bytes]:\n(Too long!)\n\n", len(data))
|
||||
fmt.Printf("[%s] -> [%s]\n", sender, recipient)
|
||||
}
|
||||
fmt.Printf("Opcode: %s\n", opcodePID)
|
||||
if s.server.erupeConfig.DebugOptions.LogMessageData {
|
||||
if len(data) <= s.server.erupeConfig.DebugOptions.MaxHexdumpLength {
|
||||
fmt.Printf("Data [%d bytes]:\n%s\n", len(data), hex.Dump(data))
|
||||
} else {
|
||||
fmt.Printf("Data [%d bytes]: (Too long!)\n\n", len(data))
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,3 +309,12 @@ func (s *Session) NextObjectID() uint32 {
|
||||
bf.Seek(0, 0)
|
||||
return bf.ReadUint32()
|
||||
}
|
||||
|
||||
func (s *Session) isOp() bool {
|
||||
var op bool
|
||||
err := s.server.db.QueryRow(`SELECT op FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&op)
|
||||
if err == nil && op {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ type Stage struct {
|
||||
host *Session
|
||||
maxPlayers uint16
|
||||
password string
|
||||
locked bool
|
||||
}
|
||||
|
||||
// NewStage creates a new stage with intialized values.
|
||||
|
||||
@@ -16,8 +16,11 @@ func TimeMidnight() time.Time {
|
||||
|
||||
func TimeWeekStart() time.Time {
|
||||
midnight := TimeMidnight()
|
||||
offset := (int(midnight.Weekday()) - 1) * -24
|
||||
return midnight.Add(time.Hour * time.Duration(offset))
|
||||
offset := int(midnight.Weekday()) - int(time.Monday)
|
||||
if offset < 0 {
|
||||
offset += 7
|
||||
}
|
||||
return midnight.Add(-time.Duration(offset) * 24 * time.Hour)
|
||||
}
|
||||
|
||||
func TimeWeekNext() time.Time {
|
||||
|
||||
@@ -7,12 +7,39 @@ import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var Commands = []*discordgo.ApplicationCommand{
|
||||
{
|
||||
Name: "link",
|
||||
Description: "Link your Erupe account to Discord",
|
||||
Options: []*discordgo.ApplicationCommandOption{
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionString,
|
||||
Name: "token",
|
||||
Description: "The token provided by the Discord command in-game",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "password",
|
||||
Description: "Change your Erupe account password",
|
||||
Options: []*discordgo.ApplicationCommandOption{
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionString,
|
||||
Name: "password",
|
||||
Description: "Your new password",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type DiscordBot struct {
|
||||
Session *discordgo.Session
|
||||
config *_config.Config
|
||||
logger *zap.Logger
|
||||
MainGuild *discordgo.Guild
|
||||
RealtimeChannel *discordgo.Channel
|
||||
Session *discordgo.Session
|
||||
config *_config.Config
|
||||
logger *zap.Logger
|
||||
MainGuild *discordgo.Guild
|
||||
RelayChannel *discordgo.Channel
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
@@ -28,18 +55,22 @@ func NewDiscordBot(options Options) (discordBot *DiscordBot, err error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
realtimeChannel, err := session.Channel(options.Config.Discord.RealtimeChannelID)
|
||||
var relayChannel *discordgo.Channel
|
||||
|
||||
if options.Config.Discord.RelayChannel.Enabled {
|
||||
relayChannel, err = session.Channel(options.Config.Discord.RelayChannel.RelayChannelID)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
options.Logger.Fatal("Discord failed to create realtimeChannel", zap.Error(err))
|
||||
options.Logger.Fatal("Discord failed to create relayChannel", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
discordBot = &DiscordBot{
|
||||
config: options.Config,
|
||||
logger: options.Logger,
|
||||
Session: session,
|
||||
RealtimeChannel: realtimeChannel,
|
||||
config: options.Config,
|
||||
logger: options.Logger,
|
||||
Session: session,
|
||||
RelayChannel: relayChannel,
|
||||
}
|
||||
|
||||
return
|
||||
@@ -51,7 +82,7 @@ func (bot *DiscordBot) Start() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Replace all mentions to real name from the message.
|
||||
// NormalizeDiscordMessage replaces all mentions to real name from the message.
|
||||
func (bot *DiscordBot) NormalizeDiscordMessage(message string) string {
|
||||
userRegex := regexp.MustCompile(`<@!?(\d{17,19})>`)
|
||||
emojiRegex := regexp.MustCompile(`(?:<a?)?:(\w+):(?:\d{18}>)?`)
|
||||
@@ -74,7 +105,11 @@ func (bot *DiscordBot) NormalizeDiscordMessage(message string) string {
|
||||
}
|
||||
|
||||
func (bot *DiscordBot) RealtimeChannelSend(message string) (err error) {
|
||||
_, err = bot.Session.ChannelMessageSend(bot.RealtimeChannel.ID, message)
|
||||
if bot.RelayChannel == nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = bot.Session.ChannelMessageSend(bot.RelayChannel.ID, message)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -12,45 +12,40 @@ var (
|
||||
|
||||
// CalcSum32 calculates the custom MHF "sum32" checksum of the given data.
|
||||
func CalcSum32(data []byte) uint32 {
|
||||
tableIdx0 := int(len(data) & 0xFF)
|
||||
tableIdx1 := int(data[len(data)>>1] & 0xFF)
|
||||
|
||||
tableIdx0 := (len(data) + 1) & 0xFF
|
||||
tableIdx1 := int((data[len(data)>>1] + 1) & 0xFF)
|
||||
out := make([]byte, 4)
|
||||
for i := 0; i < len(data); i++ {
|
||||
tableIdx0++
|
||||
tableIdx1++
|
||||
|
||||
tmp := byte((_sum32Table1[tableIdx1%9] ^ _sum32Table0[tableIdx0%7]) ^ data[i])
|
||||
out[i&3] = (out[i&3] + tmp) & 0xFF
|
||||
key := data[i] ^ _sum32Table0[(tableIdx0+i)%7] ^ _sum32Table1[(tableIdx1+i)%9]
|
||||
out[i&3] = (out[i&3] + key) & 0xFF
|
||||
}
|
||||
|
||||
return binary.BigEndian.Uint32(out)
|
||||
}
|
||||
|
||||
func rotate(k *uint32) {
|
||||
*k = uint32(((54323 * uint(*k)) + 1) & 0xFFFFFFFF)
|
||||
}
|
||||
|
||||
// EncryptBin8 encrypts the given data using MHF's "binary8" encryption.
|
||||
func EncryptBin8(data []byte, key byte) []byte {
|
||||
curKey := uint32(((54323 * uint(key)) + 1) & 0xFFFFFFFF)
|
||||
|
||||
_key := uint32(key)
|
||||
var output []byte
|
||||
for i := 0; i < len(data); i++ {
|
||||
tmp := (_bin8Key[i&7] ^ byte((curKey>>13)&0xFF))
|
||||
rotate(&_key)
|
||||
tmp := _bin8Key[i&7] ^ byte((_key>>13)&0xFF)
|
||||
output = append(output, data[i]^tmp)
|
||||
curKey = uint32(((54323 * uint(curKey)) + 1) & 0xFFFFFFFF)
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
// DecryptBin8 decrypts the given MHF "binary8" data.
|
||||
func DecryptBin8(data []byte, key byte) []byte {
|
||||
curKey := uint32(((54323 * uint(key)) + 1) & 0xFFFFFFFF)
|
||||
|
||||
_key := uint32(key)
|
||||
var output []byte
|
||||
for i := 0; i < len(data); i++ {
|
||||
tmp := (data[i] ^ byte((curKey>>13)&0xFF))
|
||||
rotate(&_key)
|
||||
tmp := data[i] ^ byte((_key>>13)&0xFF)
|
||||
output = append(output, tmp^_bin8Key[i&7])
|
||||
curKey = uint32(((54323 * uint(curKey)) + 1) & 0xFFFFFFFF)
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ func (s *Server) handleEntranceServerConnection(conn net.Conn) {
|
||||
return
|
||||
}
|
||||
|
||||
if s.erupeConfig.DevMode && s.erupeConfig.DevModeOptions.LogInboundMessages {
|
||||
if s.erupeConfig.DebugOptions.LogInboundMessages {
|
||||
fmt.Printf("[Client] -> [Server]\nData [%d bytes]:\n%s\n", len(pkt), hex.Dump(pkt))
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte {
|
||||
}
|
||||
}
|
||||
|
||||
sid := (4096 + serverIdx*256) * 6000
|
||||
if si.IP == "" {
|
||||
si.IP = config.Host
|
||||
}
|
||||
@@ -38,8 +37,8 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte {
|
||||
} else {
|
||||
bf.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(si.IP).To4()))
|
||||
}
|
||||
bf.WriteUint16(16 + uint16(serverIdx))
|
||||
bf.WriteUint16(0x0000)
|
||||
bf.WriteUint16(uint16(serverIdx | 16))
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(uint16(len(si.Channels)))
|
||||
bf.WriteUint8(si.Type)
|
||||
bf.WriteUint8(uint8(((channelserver.TimeAdjusted().Unix() / 86400) + int64(serverIdx)) % 3))
|
||||
@@ -47,20 +46,15 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte {
|
||||
bf.WriteUint8(si.Recommended)
|
||||
}
|
||||
|
||||
if s.erupeConfig.RealClientMode <= _config.F5 {
|
||||
combined := append(stringsupport.UTF8ToSJIS(si.Name), []byte{0x00}...)
|
||||
combined = append(combined, stringsupport.UTF8ToSJIS(si.Description)...)
|
||||
bf.WriteBytes(stringsupport.PaddedString(string(combined), 65, false))
|
||||
} else if s.erupeConfig.RealClientMode <= _config.GG {
|
||||
combined := append(stringsupport.UTF8ToSJIS(si.Name), []byte{0x00}...)
|
||||
combined = append(combined, stringsupport.UTF8ToSJIS(si.Description)...)
|
||||
bf.WriteUint8(uint8(len(combined)))
|
||||
bf.WriteBytes(combined)
|
||||
fullName := append(append(stringsupport.UTF8ToSJIS(si.Name), []byte{0x00}...), stringsupport.UTF8ToSJIS(si.Description)...)
|
||||
if s.erupeConfig.RealClientMode >= _config.G1 && s.erupeConfig.RealClientMode <= _config.G5 {
|
||||
bf.WriteUint8(uint8(len(fullName)))
|
||||
bf.WriteBytes(fullName)
|
||||
} else {
|
||||
bf.WriteUint8(0) // Prevents malformed server name
|
||||
combined := append(stringsupport.UTF8ToSJIS(si.Name), []byte{0x00}...)
|
||||
combined = append(combined, stringsupport.UTF8ToSJIS(si.Description)...)
|
||||
bf.WriteBytes(stringsupport.PaddedString(string(combined), 65, false))
|
||||
if s.erupeConfig.RealClientMode >= _config.G51 {
|
||||
bf.WriteUint8(0) // Ignored
|
||||
}
|
||||
bf.WriteBytes(stringsupport.PaddedString(string(fullName), 65, false))
|
||||
}
|
||||
|
||||
if s.erupeConfig.RealClientMode >= _config.GG {
|
||||
@@ -68,27 +62,31 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte {
|
||||
}
|
||||
|
||||
for channelIdx, ci := range si.Channels {
|
||||
sid = (4096 + serverIdx*256) + (16 + channelIdx)
|
||||
bf.WriteUint16(ci.Port)
|
||||
bf.WriteUint16(16 + uint16(channelIdx))
|
||||
sid := (serverIdx<<8 | 4096) + (channelIdx | 16)
|
||||
if _config.ErupeConfig.DebugOptions.ProxyPort != 0 {
|
||||
bf.WriteUint16(_config.ErupeConfig.DebugOptions.ProxyPort)
|
||||
} else {
|
||||
bf.WriteUint16(ci.Port)
|
||||
}
|
||||
bf.WriteUint16(uint16(channelIdx | 16))
|
||||
bf.WriteUint16(ci.MaxPlayers)
|
||||
var currentPlayers uint16
|
||||
s.db.QueryRow("SELECT current_players FROM servers WHERE server_id=$1", sid).Scan(¤tPlayers)
|
||||
bf.WriteUint16(currentPlayers)
|
||||
bf.WriteUint16(0) // Unk
|
||||
bf.WriteUint16(0) // Unk
|
||||
bf.WriteUint16(0) // Unk
|
||||
bf.WriteUint16(0) // Unk
|
||||
bf.WriteUint16(0) // Unk
|
||||
bf.WriteUint16(0) // Unk
|
||||
bf.WriteUint16(319) // Unk
|
||||
bf.WriteUint16(252) // Unk
|
||||
bf.WriteUint16(248) // Unk
|
||||
bf.WriteUint16(12345) // Unk
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(319) // Unk
|
||||
bf.WriteUint16(254 - currentPlayers) // Unk
|
||||
bf.WriteUint16(255 - currentPlayers) // Unk
|
||||
bf.WriteUint16(12345)
|
||||
}
|
||||
}
|
||||
bf.WriteUint32(uint32(channelserver.TimeAdjusted().Unix()))
|
||||
bf.WriteUint32(0x0000003C)
|
||||
bf.WriteUint32(60)
|
||||
return bf.Data()
|
||||
}
|
||||
|
||||
@@ -132,7 +130,7 @@ func makeSv2Resp(config *_config.Config, s *Server, local bool) []byte {
|
||||
}
|
||||
rawServerData := encodeServerInfo(config, s, local)
|
||||
|
||||
if s.erupeConfig.DevMode && s.erupeConfig.DevModeOptions.LogOutboundMessages {
|
||||
if s.erupeConfig.DebugOptions.LogOutboundMessages {
|
||||
fmt.Printf("[Server] -> [Client]\nData [%d bytes]:\n%s\n", len(rawServerData), hex.Dump(rawServerData))
|
||||
}
|
||||
|
||||
@@ -157,14 +155,14 @@ func makeUsrResp(pkt []byte, s *Server) []byte {
|
||||
var sid uint16
|
||||
err := s.db.QueryRow("SELECT(SELECT server_id FROM sign_sessions WHERE char_id=$1) AS _", cid).Scan(&sid)
|
||||
if err != nil {
|
||||
resp.WriteBytes(make([]byte, 4))
|
||||
resp.WriteUint16(0)
|
||||
} else {
|
||||
resp.WriteUint16(sid)
|
||||
resp.WriteUint16(0)
|
||||
}
|
||||
resp.WriteUint16(0)
|
||||
}
|
||||
|
||||
if s.erupeConfig.DevMode && s.erupeConfig.DevModeOptions.LogOutboundMessages {
|
||||
if s.erupeConfig.DebugOptions.LogOutboundMessages {
|
||||
fmt.Printf("[Server] -> [Client]\nData [%d bytes]:\n%s\n", len(resp.Data()), hex.Dump(resp.Data()))
|
||||
}
|
||||
|
||||
|
||||
@@ -198,17 +198,17 @@ func (s *Server) checkToken(uid uint32) (bool, error) {
|
||||
}
|
||||
|
||||
func (s *Server) registerUidToken(uid uint32) (uint32, string, error) {
|
||||
token := token.Generate(16)
|
||||
_token := token.Generate(16)
|
||||
var tid uint32
|
||||
err := s.db.QueryRow(`INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2) RETURNING id`, uid, token).Scan(&tid)
|
||||
return tid, token, err
|
||||
err := s.db.QueryRow(`INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2) RETURNING id`, uid, _token).Scan(&tid)
|
||||
return tid, _token, err
|
||||
}
|
||||
|
||||
func (s *Server) registerPsnToken(psn string) (uint32, string, error) {
|
||||
token := token.Generate(16)
|
||||
_token := token.Generate(16)
|
||||
var tid uint32
|
||||
err := s.db.QueryRow(`INSERT INTO sign_sessions (psn_id, token) VALUES ($1, $2) RETURNING id`, psn, token).Scan(&tid)
|
||||
return tid, token, err
|
||||
err := s.db.QueryRow(`INSERT INTO sign_sessions (psn_id, token) VALUES ($1, $2) RETURNING id`, psn, _token).Scan(&tid)
|
||||
return tid, _token, err
|
||||
}
|
||||
|
||||
func (s *Server) validateToken(token string, tokenID uint32) bool {
|
||||
@@ -229,9 +229,9 @@ func (s *Server) validateLogin(user string, pass string) (uint32, RespID) {
|
||||
var passDB string
|
||||
err := s.db.QueryRow(`SELECT id, password FROM users WHERE username = $1`, user).Scan(&uid, &passDB)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
s.logger.Info("User not found", zap.String("User", user))
|
||||
if s.erupeConfig.DevMode && s.erupeConfig.DevModeOptions.AutoCreateAccount {
|
||||
if s.erupeConfig.AutoCreateAccount {
|
||||
uid, err = s.registerDBAccount(user, pass)
|
||||
if err == nil {
|
||||
return uid, SIGN_SUCCESS
|
||||
@@ -244,6 +244,15 @@ func (s *Server) validateLogin(user string, pass string) (uint32, RespID) {
|
||||
return 0, SIGN_EABORT
|
||||
} else {
|
||||
if bcrypt.CompareHashAndPassword([]byte(passDB), []byte(pass)) == nil {
|
||||
var bans int
|
||||
err = s.db.QueryRow(`SELECT count(*) FROM bans WHERE user_id=$1 AND expires IS NULL`, uid).Scan(&bans)
|
||||
if err == nil && bans > 0 {
|
||||
return uid, SIGN_EELIMINATE
|
||||
}
|
||||
err = s.db.QueryRow(`SELECT count(*) FROM bans WHERE user_id=$1 AND expires > now()`, uid).Scan(&bans)
|
||||
if err == nil && bans > 0 {
|
||||
return uid, SIGN_ESUSPEND
|
||||
}
|
||||
return uid, SIGN_SUCCESS
|
||||
}
|
||||
return 0, SIGN_EPASS
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (s *Session) makeSignResponse(uid uint32) []byte {
|
||||
@@ -71,7 +72,7 @@ func (s *Session) makeSignResponse(uid uint32) []byte {
|
||||
bf.WriteUint32(char.ID)
|
||||
|
||||
// Exp, HR[x] is split by 0, 1, 30, 50, 99, 299, 998, 999
|
||||
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.MaxLauncherHR {
|
||||
if s.server.erupeConfig.DebugOptions.MaxLauncherHR {
|
||||
bf.WriteUint16(999)
|
||||
} else {
|
||||
bf.WriteUint16(char.HRP)
|
||||
@@ -143,46 +144,59 @@ func (s *Session) makeSignResponse(uid uint32) []byte {
|
||||
s.server.db.QueryRow("SELECT psn_id FROM users WHERE id = $1", uid).Scan(&psnUser)
|
||||
bf.WriteBytes(stringsupport.PaddedString(psnUser, 20, true))
|
||||
}
|
||||
bf.WriteUint16(0xCA10)
|
||||
bf.WriteUint16(0x4E20)
|
||||
ps.Uint16(bf, "", false) // unk key
|
||||
bf.WriteUint8(0x00)
|
||||
bf.WriteUint16(0xCA11)
|
||||
bf.WriteUint16(0x0001)
|
||||
bf.WriteUint16(0x4E20)
|
||||
ps.Uint16(bf, "", false) // unk ipv4
|
||||
|
||||
bf.WriteUint16(s.server.erupeConfig.DebugOptions.CapLink.Values[0])
|
||||
if s.server.erupeConfig.DebugOptions.CapLink.Values[0] == 51728 {
|
||||
bf.WriteUint16(s.server.erupeConfig.DebugOptions.CapLink.Values[1])
|
||||
if s.server.erupeConfig.DebugOptions.CapLink.Values[1] == 20000 || s.server.erupeConfig.DebugOptions.CapLink.Values[1] == 20002 {
|
||||
ps.Uint16(bf, s.server.erupeConfig.DebugOptions.CapLink.Key, false)
|
||||
}
|
||||
}
|
||||
caStruct := []struct {
|
||||
Unk0 uint8
|
||||
Unk1 uint32
|
||||
Unk2 string
|
||||
}{}
|
||||
bf.WriteUint8(uint8(len(caStruct)))
|
||||
for i := range caStruct {
|
||||
bf.WriteUint8(caStruct[i].Unk0)
|
||||
bf.WriteUint32(caStruct[i].Unk1)
|
||||
ps.Uint8(bf, caStruct[i].Unk2, false)
|
||||
}
|
||||
bf.WriteUint16(s.server.erupeConfig.DebugOptions.CapLink.Values[2])
|
||||
bf.WriteUint16(s.server.erupeConfig.DebugOptions.CapLink.Values[3])
|
||||
bf.WriteUint16(s.server.erupeConfig.DebugOptions.CapLink.Values[4])
|
||||
if s.server.erupeConfig.DebugOptions.CapLink.Values[2] == 51729 && s.server.erupeConfig.DebugOptions.CapLink.Values[3] == 1 && s.server.erupeConfig.DebugOptions.CapLink.Values[4] == 20000 {
|
||||
ps.Uint16(bf, fmt.Sprintf(`%s:%d`, s.server.erupeConfig.DebugOptions.CapLink.Host, s.server.erupeConfig.DebugOptions.CapLink.Port), false)
|
||||
}
|
||||
|
||||
bf.WriteUint32(uint32(s.server.getReturnExpiry(uid).Unix()))
|
||||
bf.WriteUint32(0)
|
||||
|
||||
mezfes := s.server.erupeConfig.DevModeOptions.MezFesEvent
|
||||
alt := s.server.erupeConfig.DevModeOptions.MezFesAlt
|
||||
if mezfes {
|
||||
// We can just use the start timestamp as the event ID
|
||||
bf.WriteUint32(uint32(channelserver.TimeWeekStart().Unix()))
|
||||
// Start time
|
||||
bf.WriteUint32(uint32(channelserver.TimeWeekStart().Unix()))
|
||||
// End time
|
||||
bf.WriteUint32(uint32(channelserver.TimeWeekNext().Unix()))
|
||||
bf.WriteUint8(2) // Unk
|
||||
bf.WriteUint32(s.server.erupeConfig.GameplayOptions.MezfesSoloTickets)
|
||||
bf.WriteUint32(s.server.erupeConfig.GameplayOptions.MezfesGroupTickets)
|
||||
bf.WriteUint8(8) // Stalls open
|
||||
bf.WriteUint8(10) // Stall Map
|
||||
bf.WriteUint8(3) // Pachinko
|
||||
bf.WriteUint8(6) // Nyanrendo
|
||||
bf.WriteUint8(9) // Point stall
|
||||
if alt {
|
||||
bf.WriteUint8(2) // Tokotoko Partnya
|
||||
} else {
|
||||
bf.WriteUint8(4) // Volpakkun Together
|
||||
}
|
||||
bf.WriteUint8(8) // Dokkan Battle Cats
|
||||
bf.WriteUint8(5) // Goocoo Scoop
|
||||
bf.WriteUint8(7) // Honey Panic
|
||||
} else {
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(0)
|
||||
tickets := []uint32{
|
||||
s.server.erupeConfig.GameplayOptions.MezFesSoloTickets,
|
||||
s.server.erupeConfig.GameplayOptions.MezFesGroupTickets,
|
||||
}
|
||||
stalls := []uint8{
|
||||
10, 3, 6, 9, 4, 8, 5, 7,
|
||||
}
|
||||
if s.server.erupeConfig.GameplayOptions.MezFesSwitchMinigame {
|
||||
stalls[4] = 2
|
||||
}
|
||||
|
||||
// We can just use the start timestamp as the event ID
|
||||
bf.WriteUint32(uint32(channelserver.TimeWeekStart().Unix()))
|
||||
// Start time
|
||||
bf.WriteUint32(uint32(channelserver.TimeWeekNext().Add(-time.Duration(s.server.erupeConfig.GameplayOptions.MezFesDuration) * time.Second).Unix()))
|
||||
// End time
|
||||
bf.WriteUint32(uint32(channelserver.TimeWeekNext().Unix()))
|
||||
bf.WriteUint8(uint8(len(tickets)))
|
||||
for i := range tickets {
|
||||
bf.WriteUint32(tickets[i])
|
||||
}
|
||||
bf.WriteUint8(uint8(len(stalls)))
|
||||
for i := range stalls {
|
||||
bf.WriteUint8(stalls[i])
|
||||
}
|
||||
return bf.Data()
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ type Session struct {
|
||||
func (s *Session) work() {
|
||||
pkt, err := s.cryptConn.ReadPacket()
|
||||
|
||||
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.LogInboundMessages {
|
||||
if s.server.erupeConfig.DebugOptions.LogInboundMessages {
|
||||
fmt.Printf("\n[Client] -> [Server]\nData [%d bytes]:\n%s\n", len(pkt), hex.Dump(pkt))
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ func (s *Session) handlePacket(pkt []byte) error {
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt)
|
||||
reqType := string(bf.ReadNullTerminatedBytes())
|
||||
switch reqType[:len(reqType)-3] {
|
||||
case "DLTSKEYSIGN:", "DSGN:":
|
||||
case "DLTSKEYSIGN:", "DSGN:", "SIGN:":
|
||||
s.handleDSGN(bf)
|
||||
case "PS3SGN:":
|
||||
s.client = PS3
|
||||
@@ -78,7 +78,7 @@ func (s *Session) handlePacket(pkt []byte) error {
|
||||
}
|
||||
default:
|
||||
s.logger.Warn("Unknown request", zap.String("reqType", reqType))
|
||||
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.LogInboundMessages {
|
||||
if s.server.erupeConfig.DebugOptions.LogInboundMessages {
|
||||
fmt.Printf("\n[Client] -> [Server]\nData [%d bytes]:\n%s\n", len(pkt), hex.Dump(pkt))
|
||||
}
|
||||
}
|
||||
@@ -102,7 +102,7 @@ func (s *Session) authenticate(username string, password string) {
|
||||
default:
|
||||
bf.WriteUint8(uint8(resp))
|
||||
}
|
||||
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.LogOutboundMessages {
|
||||
if s.server.erupeConfig.DebugOptions.LogOutboundMessages {
|
||||
fmt.Printf("\n[Server] -> [Client]\nData [%d bytes]:\n%s\n", len(bf.Data()), hex.Dump(bf.Data()))
|
||||
}
|
||||
_ = s.cryptConn.SendPacket(bf.Data())
|
||||
|
||||
@@ -10,36 +10,40 @@ import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func (s *Server) createNewUser(ctx context.Context, username string, password string) (int, error) {
|
||||
func (s *Server) createNewUser(ctx context.Context, username string, password string) (uint32, uint32, error) {
|
||||
// Create salted hash of user password
|
||||
passwordHash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
var id int
|
||||
var (
|
||||
id uint32
|
||||
rights uint32
|
||||
)
|
||||
err = s.db.QueryRowContext(
|
||||
ctx, `
|
||||
INSERT INTO users (username, password, return_expires)
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING id
|
||||
RETURNING id, rights
|
||||
`,
|
||||
username, string(passwordHash), time.Now().Add(time.Hour*24*30),
|
||||
).Scan(&id)
|
||||
return id, err
|
||||
).Scan(&id, &rights)
|
||||
return id, rights, err
|
||||
}
|
||||
|
||||
func (s *Server) createLoginToken(ctx context.Context, uid int) (string, error) {
|
||||
func (s *Server) createLoginToken(ctx context.Context, uid uint32) (uint32, string, error) {
|
||||
loginToken := token.Generate(16)
|
||||
_, err := s.db.ExecContext(ctx, "INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2)", uid, loginToken)
|
||||
var tid uint32
|
||||
err := s.db.QueryRowContext(ctx, "INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2) RETURNING id", uid, loginToken).Scan(&tid)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return 0, "", err
|
||||
}
|
||||
return loginToken, nil
|
||||
return tid, loginToken, nil
|
||||
}
|
||||
|
||||
func (s *Server) userIDFromToken(ctx context.Context, token string) (int, error) {
|
||||
var userID int
|
||||
func (s *Server) userIDFromToken(ctx context.Context, token string) (uint32, error) {
|
||||
var userID uint32
|
||||
err := s.db.QueryRowContext(ctx, "SELECT user_id FROM sign_sessions WHERE token = $1", token).Scan(&userID)
|
||||
if err == sql.ErrNoRows {
|
||||
return 0, fmt.Errorf("invalid login token")
|
||||
@@ -49,65 +53,47 @@ func (s *Server) userIDFromToken(ctx context.Context, token string) (int, error)
|
||||
return userID, nil
|
||||
}
|
||||
|
||||
func (s *Server) createCharacter(ctx context.Context, userID int) (int, error) {
|
||||
var charID int
|
||||
err := s.db.QueryRowContext(ctx,
|
||||
"SELECT id FROM characters WHERE is_new_character = true AND user_id = $1",
|
||||
func (s *Server) createCharacter(ctx context.Context, userID uint32) (Character, error) {
|
||||
var character Character
|
||||
err := s.db.GetContext(ctx, &character,
|
||||
"SELECT id, name, is_female, weapon_type, hrp, gr, last_login FROM characters WHERE is_new_character = true AND user_id = $1 LIMIT 1",
|
||||
userID,
|
||||
).Scan(&charID)
|
||||
)
|
||||
if err == sql.ErrNoRows {
|
||||
err = s.db.QueryRowContext(ctx, `
|
||||
var count int
|
||||
s.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM characters WHERE user_id = $1", userID).Scan(&count)
|
||||
if count >= 16 {
|
||||
return character, fmt.Errorf("cannot have more than 16 characters")
|
||||
}
|
||||
err = s.db.GetContext(ctx, &character, `
|
||||
INSERT INTO characters (
|
||||
user_id, is_female, is_new_character, name, unk_desc_string,
|
||||
hrp, gr, weapon_type, last_login
|
||||
)
|
||||
VALUES ($1, false, true, '', '', 0, 0, 0, $2)
|
||||
RETURNING id`,
|
||||
RETURNING id, name, is_female, weapon_type, hrp, gr, last_login`,
|
||||
userID, uint32(time.Now().Unix()),
|
||||
).Scan(&charID)
|
||||
)
|
||||
}
|
||||
return charID, err
|
||||
return character, err
|
||||
}
|
||||
|
||||
func (s *Server) deleteCharacter(ctx context.Context, userID int, charID int) error {
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
func (s *Server) deleteCharacter(ctx context.Context, userID uint32, charID uint32) error {
|
||||
var isNew bool
|
||||
err := s.db.QueryRow("SELECT is_new_character FROM characters WHERE id = $1", charID).Scan(&isNew)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
_, err = tx.ExecContext(
|
||||
ctx, `
|
||||
DELETE FROM login_boost_state
|
||||
WHERE char_id = $1`,
|
||||
charID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
if isNew {
|
||||
_, err = s.db.Exec("DELETE FROM characters WHERE id = $1", charID)
|
||||
} else {
|
||||
_, err = s.db.Exec("UPDATE characters SET deleted = true WHERE id = $1", charID)
|
||||
}
|
||||
_, err = tx.ExecContext(
|
||||
ctx, `
|
||||
DELETE FROM guild_characters
|
||||
WHERE character_id = $1`,
|
||||
charID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.ExecContext(
|
||||
ctx, `
|
||||
DELETE FROM characters
|
||||
WHERE user_id = $1 AND id = $2`,
|
||||
userID, charID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Commit()
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Server) getCharactersForUser(ctx context.Context, uid int) ([]Character, error) {
|
||||
characters := make([]Character, 0)
|
||||
func (s *Server) getCharactersForUser(ctx context.Context, uid uint32) ([]Character, error) {
|
||||
var characters []Character
|
||||
err := s.db.SelectContext(
|
||||
ctx, &characters, `
|
||||
SELECT id, name, is_female, weapon_type, hrp, gr, last_login
|
||||
@@ -120,3 +106,30 @@ func (s *Server) getCharactersForUser(ctx context.Context, uid int) ([]Character
|
||||
}
|
||||
return characters, nil
|
||||
}
|
||||
|
||||
func (s *Server) getReturnExpiry(uid uint32) time.Time {
|
||||
var returnExpiry, lastLogin time.Time
|
||||
s.db.Get(&lastLogin, "SELECT COALESCE(last_login, now()) FROM users WHERE id=$1", uid)
|
||||
if time.Now().Add((time.Hour * 24) * -90).After(lastLogin) {
|
||||
returnExpiry = time.Now().Add(time.Hour * 24 * 30)
|
||||
s.db.Exec("UPDATE users SET return_expires=$1 WHERE id=$2", returnExpiry, uid)
|
||||
} else {
|
||||
err := s.db.Get(&returnExpiry, "SELECT return_expires FROM users WHERE id=$1", uid)
|
||||
if err != nil {
|
||||
returnExpiry = time.Now()
|
||||
s.db.Exec("UPDATE users SET return_expires=$1 WHERE id=$2", returnExpiry, uid)
|
||||
}
|
||||
}
|
||||
s.db.Exec("UPDATE users SET last_login=$1 WHERE id=$2", time.Now(), uid)
|
||||
return returnExpiry
|
||||
}
|
||||
|
||||
func (s *Server) exportSave(ctx context.Context, uid uint32, cid uint32) (map[string]interface{}, error) {
|
||||
row := s.db.QueryRowxContext(ctx, "SELECT * FROM characters WHERE id=$1 AND user_id=$2", cid, uid)
|
||||
result := make(map[string]interface{})
|
||||
err := row.MapScan(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
_config "erupe-ce/config"
|
||||
"erupe-ce/server/channelserver"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/lib/pq"
|
||||
@@ -12,52 +15,99 @@ import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type LauncherMessage struct {
|
||||
Message string `json:"message"`
|
||||
Date int64 `json:"date"`
|
||||
Link string `json:"link"`
|
||||
const (
|
||||
NotificationDefault = iota
|
||||
NotificationNew
|
||||
)
|
||||
|
||||
type LauncherResponse struct {
|
||||
Banners []_config.SignV2Banner `json:"banners"`
|
||||
Messages []_config.SignV2Message `json:"messages"`
|
||||
Links []_config.SignV2Link `json:"links"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
TokenID uint32 `json:"tokenId"`
|
||||
Token string `json:"token"`
|
||||
Rights uint32 `json:"rights"`
|
||||
}
|
||||
|
||||
type Character struct {
|
||||
ID int `json:"id"`
|
||||
ID uint32 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
IsFemale bool `json:"isFemale" db:"is_female"`
|
||||
Weapon int `json:"weapon" db:"weapon_type"`
|
||||
HR int `json:"hr" db:"hrp"`
|
||||
GR int `json:"gr"`
|
||||
LastLogin int64 `json:"lastLogin" db:"last_login"`
|
||||
Weapon uint32 `json:"weapon" db:"weapon_type"`
|
||||
HR uint32 `json:"hr" db:"hrp"`
|
||||
GR uint32 `json:"gr"`
|
||||
LastLogin int32 `json:"lastLogin" db:"last_login"`
|
||||
}
|
||||
|
||||
type MezFes struct {
|
||||
ID uint32 `json:"id"`
|
||||
Start uint32 `json:"start"`
|
||||
End uint32 `json:"end"`
|
||||
SoloTickets uint32 `json:"soloTickets"`
|
||||
GroupTickets uint32 `json:"groupTickets"`
|
||||
Stalls []uint32 `json:"stalls"`
|
||||
}
|
||||
|
||||
type AuthData struct {
|
||||
CurrentTS uint32 `json:"currentTs"`
|
||||
ExpiryTS uint32 `json:"expiryTs"`
|
||||
EntranceCount uint32 `json:"entranceCount"`
|
||||
Notices []string `json:"notices"`
|
||||
User User `json:"user"`
|
||||
Characters []Character `json:"characters"`
|
||||
MezFes *MezFes `json:"mezFes"`
|
||||
PatchServer string `json:"patchServer"`
|
||||
}
|
||||
|
||||
type ExportData struct {
|
||||
Character map[string]interface{} `json:"character"`
|
||||
}
|
||||
|
||||
func (s *Server) newAuthData(userID uint32, userRights uint32, userTokenID uint32, userToken string, characters []Character) AuthData {
|
||||
resp := AuthData{
|
||||
CurrentTS: uint32(channelserver.TimeAdjusted().Unix()),
|
||||
ExpiryTS: uint32(s.getReturnExpiry(userID).Unix()),
|
||||
EntranceCount: 1,
|
||||
User: User{
|
||||
Rights: userRights,
|
||||
TokenID: userTokenID,
|
||||
Token: userToken,
|
||||
},
|
||||
Characters: characters,
|
||||
PatchServer: s.erupeConfig.SignV2.PatchServer,
|
||||
Notices: []string{},
|
||||
}
|
||||
if s.erupeConfig.DebugOptions.MaxLauncherHR {
|
||||
for i := range resp.Characters {
|
||||
resp.Characters[i].HR = 7
|
||||
}
|
||||
}
|
||||
stalls := []uint32{10, 3, 6, 9, 4, 8, 5, 7}
|
||||
if s.erupeConfig.GameplayOptions.MezFesSwitchMinigame {
|
||||
stalls[4] = 2
|
||||
}
|
||||
resp.MezFes = &MezFes{
|
||||
ID: uint32(channelserver.TimeWeekStart().Unix()),
|
||||
Start: uint32(channelserver.TimeWeekStart().Add(-time.Duration(s.erupeConfig.GameplayOptions.MezFesDuration) * time.Second).Unix()),
|
||||
End: uint32(channelserver.TimeWeekNext().Unix()),
|
||||
SoloTickets: s.erupeConfig.GameplayOptions.MezFesSoloTickets,
|
||||
GroupTickets: s.erupeConfig.GameplayOptions.MezFesGroupTickets,
|
||||
Stalls: stalls,
|
||||
}
|
||||
if !s.erupeConfig.HideLoginNotice {
|
||||
resp.Notices = append(resp.Notices, strings.Join(s.erupeConfig.LoginNotices[:], "<PAGE>"))
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *Server) Launcher(w http.ResponseWriter, r *http.Request) {
|
||||
var respData struct {
|
||||
Important []LauncherMessage `json:"important"`
|
||||
Normal []LauncherMessage `json:"normal"`
|
||||
}
|
||||
respData.Important = []LauncherMessage{
|
||||
{
|
||||
Message: "Server Update 9 Released!",
|
||||
Date: time.Date(2022, 8, 2, 0, 0, 0, 0, time.UTC).Unix(),
|
||||
Link: "https://discord.com/channels/368424389416583169/929509970624532511/1003985850255818762",
|
||||
},
|
||||
{
|
||||
Message: "Eng 2.0 & Ravi Patch Released!",
|
||||
Date: time.Date(2022, 5, 3, 0, 0, 0, 0, time.UTC).Unix(),
|
||||
Link: "https://discord.com/channels/368424389416583169/929509970624532511/969305400795078656",
|
||||
},
|
||||
{
|
||||
Message: "Launcher Patch V1.0 Released!",
|
||||
Date: time.Date(2022, 4, 24, 0, 0, 0, 0, time.UTC).Unix(),
|
||||
Link: "https://discord.com/channels/368424389416583169/929509970624532511/969286397301248050",
|
||||
},
|
||||
}
|
||||
respData.Normal = []LauncherMessage{
|
||||
{
|
||||
Message: "Join the community Discord for updates!",
|
||||
Date: time.Date(2022, 4, 24, 0, 0, 0, 0, time.UTC).Unix(),
|
||||
Link: "https://discord.gg/CFnzbhQ",
|
||||
},
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
var respData LauncherResponse
|
||||
respData.Banners = s.erupeConfig.SignV2.Banners
|
||||
respData.Messages = s.erupeConfig.SignV2.Messages
|
||||
respData.Links = s.erupeConfig.SignV2.Links
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(respData)
|
||||
}
|
||||
@@ -71,17 +121,17 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) {
|
||||
if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil {
|
||||
s.logger.Error("JSON decode error", zap.Error(err))
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("Invalid data received"))
|
||||
return
|
||||
}
|
||||
var (
|
||||
userID int
|
||||
password string
|
||||
userID uint32
|
||||
userRights uint32
|
||||
password string
|
||||
)
|
||||
err := s.db.QueryRow("SELECT id, password FROM users WHERE username = $1", reqData.Username).Scan(&userID, &password)
|
||||
err := s.db.QueryRow("SELECT id, password, rights FROM users WHERE username = $1", reqData.Username).Scan(&userID, &password, &userRights)
|
||||
if err == sql.ErrNoRows {
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("Username does not exist"))
|
||||
w.Write([]byte("username-error"))
|
||||
return
|
||||
} else if err != nil {
|
||||
s.logger.Warn("SQL query error", zap.Error(err))
|
||||
@@ -90,27 +140,26 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
if bcrypt.CompareHashAndPassword([]byte(password), []byte(reqData.Password)) != nil {
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("Your password is incorrect"))
|
||||
w.Write([]byte("password-error"))
|
||||
return
|
||||
}
|
||||
|
||||
var respData struct {
|
||||
Token string `json:"token"`
|
||||
Characters []Character `json:"characters"`
|
||||
}
|
||||
respData.Token, err = s.createLoginToken(ctx, userID)
|
||||
userTokenID, userToken, err := s.createLoginToken(ctx, userID)
|
||||
if err != nil {
|
||||
s.logger.Warn("Error registering login token", zap.Error(err))
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
respData.Characters, err = s.getCharactersForUser(ctx, userID)
|
||||
characters, err := s.getCharactersForUser(ctx, userID)
|
||||
if err != nil {
|
||||
s.logger.Warn("Error getting characters from DB", zap.Error(err))
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
if characters == nil {
|
||||
characters = []Character{}
|
||||
}
|
||||
respData := s.newAuthData(userID, userRights, userTokenID, userToken, characters)
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(respData)
|
||||
}
|
||||
@@ -124,16 +173,19 @@ func (s *Server) Register(w http.ResponseWriter, r *http.Request) {
|
||||
if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil {
|
||||
s.logger.Error("JSON decode error", zap.Error(err))
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("Invalid data received"))
|
||||
return
|
||||
}
|
||||
if reqData.Username == "" || reqData.Password == "" {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
s.logger.Info("Creating account", zap.String("username", reqData.Username))
|
||||
userID, err := s.createNewUser(ctx, reqData.Username, reqData.Password)
|
||||
userID, userRights, err := s.createNewUser(ctx, reqData.Username, reqData.Password)
|
||||
if err != nil {
|
||||
var pqErr *pq.Error
|
||||
if errors.As(err, &pqErr) && pqErr.Constraint == "users_username_key" {
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("User already exists"))
|
||||
w.Write([]byte("username-exists-error"))
|
||||
return
|
||||
}
|
||||
s.logger.Error("Error checking user", zap.Error(err), zap.String("username", reqData.Username))
|
||||
@@ -141,15 +193,14 @@ func (s *Server) Register(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
var respData struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
respData.Token, err = s.createLoginToken(ctx, userID)
|
||||
userTokenID, userToken, err := s.createLoginToken(ctx, userID)
|
||||
if err != nil {
|
||||
s.logger.Error("Error registering login token", zap.Error(err))
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
respData := s.newAuthData(userID, userRights, userTokenID, userToken, []Character{})
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(respData)
|
||||
}
|
||||
|
||||
@@ -161,37 +212,36 @@ func (s *Server) CreateCharacter(w http.ResponseWriter, r *http.Request) {
|
||||
if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil {
|
||||
s.logger.Error("JSON decode error", zap.Error(err))
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("Invalid data received"))
|
||||
return
|
||||
}
|
||||
|
||||
var respData struct {
|
||||
CharID int `json:"id"`
|
||||
}
|
||||
userID, err := s.userIDFromToken(ctx, reqData.Token)
|
||||
if err != nil {
|
||||
w.WriteHeader(401)
|
||||
return
|
||||
}
|
||||
respData.CharID, err = s.createCharacter(ctx, userID)
|
||||
character, err := s.createCharacter(ctx, userID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to create character", zap.Error(err), zap.String("token", reqData.Token))
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
json.NewEncoder(w).Encode(respData)
|
||||
if s.erupeConfig.DebugOptions.MaxLauncherHR {
|
||||
character.HR = 7
|
||||
}
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(character)
|
||||
}
|
||||
|
||||
func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
var reqData struct {
|
||||
Token string `json:"token"`
|
||||
CharID int `json:"id"`
|
||||
CharID uint32 `json:"charId"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil {
|
||||
s.logger.Error("JSON decode error", zap.Error(err))
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("Invalid data received"))
|
||||
return
|
||||
}
|
||||
userID, err := s.userIDFromToken(ctx, reqData.Token)
|
||||
@@ -200,9 +250,39 @@ func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if err := s.deleteCharacter(ctx, userID, reqData.CharID); err != nil {
|
||||
s.logger.Error("Failed to delete character", zap.Error(err), zap.String("token", reqData.Token), zap.Int("charID", reqData.CharID))
|
||||
s.logger.Error("Failed to delete character", zap.Error(err), zap.String("token", reqData.Token), zap.Uint32("charID", reqData.CharID))
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(struct{}{})
|
||||
}
|
||||
|
||||
func (s *Server) ExportSave(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
var reqData struct {
|
||||
Token string `json:"token"`
|
||||
CharID uint32 `json:"charId"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil {
|
||||
s.logger.Error("JSON decode error", zap.Error(err))
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
userID, err := s.userIDFromToken(ctx, reqData.Token)
|
||||
if err != nil {
|
||||
w.WriteHeader(401)
|
||||
return
|
||||
}
|
||||
character, err := s.exportSave(ctx, userID, reqData.CharID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to export save", zap.Error(err), zap.String("token", reqData.Token), zap.Uint32("charID", reqData.CharID))
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
save := ExportData{
|
||||
Character: character,
|
||||
}
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(save)
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ func (s *Server) Start() error {
|
||||
r.HandleFunc("/register", s.Register)
|
||||
r.HandleFunc("/character/create", s.CreateCharacter)
|
||||
r.HandleFunc("/character/delete", s.DeleteCharacter)
|
||||
r.HandleFunc("/character/export", s.ExportSave)
|
||||
handler := handlers.CORS(handlers.AllowedHeaders([]string{"Content-Type"}))(r)
|
||||
s.httpServer.Handler = handlers.LoggingHandler(os.Stdout, handler)
|
||||
s.httpServer.Addr = fmt.Sprintf(":%d", s.erupeConfig.SignV2.Port)
|
||||
|
||||
Reference in New Issue
Block a user