Merge branch 'main' into feature/festa

This commit is contained in:
wish
2022-08-19 21:12:39 +10:00
39 changed files with 1293 additions and 673 deletions

View File

@@ -9,10 +9,6 @@ import (
"io"
"net"
"strings"
"io/ioutil"
"math/bits"
"math/rand"
"time"
"erupe-ce/common/byteframe"
@@ -20,6 +16,9 @@ import (
"go.uber.org/zap"
"golang.org/x/text/encoding/japanese"
"golang.org/x/text/transform"
"io/ioutil"
"math/bits"
"math/rand"
)
// Temporary function to just return no results for a MSG_MHF_ENUMERATE* packet
@@ -80,7 +79,7 @@ func doAckSimpleFail(s *Session, ackHandle uint32, data []byte) {
func updateRights(s *Session) {
update := &mhfpacket.MsgSysUpdateRight{
ClientRespAckHandle: 0,
Unk1: s.rights,
Bitfield: s.rights,
Rights: []mhfpacket.ClientRight{
{
ID: 1,
@@ -192,8 +191,12 @@ func handleMsgSysLogout(s *Session, p mhfpacket.MHFPacket) {
}
func logoutPlayer(s *Session) {
delete(s.server.sessions, s.rawConn)
s.rawConn.Close()
if _, exists := s.server.sessions[s.rawConn]; exists {
delete(s.server.sessions, s.rawConn)
s.rawConn.Close()
} else {
return // Prevent re-running logout logic on real logouts
}
_, err := s.server.db.Exec("UPDATE sign_sessions SET server_id=NULL, char_id=NULL WHERE token=$1", s.token)
if err != nil {
@@ -206,13 +209,13 @@ func logoutPlayer(s *Session) {
}
var timePlayed int
var sessionTime int
_ = s.server.db.QueryRow("SELECT time_played FROM characters WHERE id = $1", s.charID).Scan(&timePlayed)
timePlayed = (int(Time_Current_Adjusted().Unix()) - int(s.sessionStart)) + timePlayed
sessionTime = int(Time_Current_Adjusted().Unix()) - int(s.sessionStart)
timePlayed += sessionTime
var rpGained int
if s.rights > 0x40000000 { // N Course
if s.rights >= 0x40000000 { // N Course
rpGained = timePlayed / 900
timePlayed = timePlayed % 900
} else {
@@ -220,10 +223,10 @@ func logoutPlayer(s *Session) {
timePlayed = timePlayed % 1800
}
_, err = s.server.db.Exec("UPDATE characters SET time_played = $1 WHERE id = $2", timePlayed, s.charID)
if err != nil {
panic(err)
}
s.server.db.Exec("UPDATE characters SET time_played = $1 WHERE id = $2", timePlayed, s.charID)
s.server.db.Exec("UPDATE characters SET cafe_time=cafe_time+$1 WHERE id=$2", sessionTime, s.charID)
treasureHuntUnregister(s)
if s.stage == nil {
return
@@ -243,7 +246,6 @@ func logoutPlayer(s *Session) {
removeSessionFromSemaphore(s)
removeSessionFromStage(s)
treasureHuntUnregister(s)
saveData, err := GetCharacterSaveData(s, s.charID)
if err != nil {
@@ -421,6 +423,9 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) {
}
for session := range stage.clients {
count++
hrp := uint16(1)
gr := uint16(0)
s.server.db.QueryRow("SELECT hrp, gr FROM characters WHERE id=$1", session.charID).Scan(&hrp, &gr)
sessionStage := stringsupport.UTF8ToSJIS(session.stageID)
sessionName := stringsupport.UTF8ToSJIS(session.Name)
resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4()))
@@ -433,8 +438,8 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) {
resp.WriteBytes(make([]byte, 48))
resp.WriteNullTerminatedBytes(sessionStage)
resp.WriteNullTerminatedBytes(sessionName)
resp.WriteUint16(999) // HR
resp.WriteUint16(999) // GR
resp.WriteUint16(hrp)
resp.WriteUint16(gr)
resp.WriteBytes([]byte{0x06, 0x10, 0x00}) // Unk
}
}
@@ -443,12 +448,23 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) {
}
case 4: // Find Party
bf := byteframe.NewByteFrameFromBytes(pkt.MessageData)
bf.ReadUint8()
setting := bf.ReadUint8()
maxResults := bf.ReadUint16()
bf.ReadUint8()
bf.ReadUint8()
bf.Seek(2, 1)
partyType := bf.ReadUint16()
_ = bf.DataFromCurrent() // Restrictions
rankRestriction := uint16(0)
if setting >= 2 {
bf.Seek(2, 1)
rankRestriction = bf.ReadUint16()
}
targets := make([]uint16, 4)
if setting >= 3 {
bf.Seek(1, 1)
lenTargets := int(bf.ReadUint8())
for i := 0; i < lenTargets; i++ {
targets[i] = bf.ReadUint16()
}
}
var stagePrefix string
switch partyType {
case 0: // Public Bar
@@ -468,31 +484,41 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) {
break
}
if strings.HasPrefix(stage.id, stagePrefix) {
sb3 := byteframe.NewByteFrameFromBytes(stage.rawBinaryData[stageBinaryKey{1, 3}])
sb3.Seek(4, 0)
stageRankRestriction := sb3.ReadUint16()
stageTarget := sb3.ReadUint16()
if rankRestriction != 0xFFFF && stageRankRestriction < rankRestriction {
continue
}
count++
sessionStage := stringsupport.UTF8ToSJIS(stage.id)
resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4()))
resp.WriteUint16(c.Port)
// TODO: This is half right, could be trimmed
resp.WriteUint16(0)
resp.WriteUint16(uint16(len(stage.clients)))
resp.WriteUint16(0) // Static?
resp.WriteUint16(0) // Unk
resp.WriteUint16(uint16(len(stage.clients)))
resp.WriteUint16(stage.maxPlayers)
resp.WriteUint16(0)
resp.WriteUint16(uint16(len(stage.clients)))
//
resp.WriteUint16(uint16(len(sessionStage) + 1))
resp.WriteUint8(1)
resp.WriteUint16(0) // Num clients entered from stage
resp.WriteUint16(stage.maxPlayers)
resp.WriteUint8(1) // Static?
resp.WriteUint8(uint8(len(sessionStage) + 1))
resp.WriteUint8(uint8(len(stage.rawBinaryData[stageBinaryKey{1, 0}])))
resp.WriteUint8(uint8(len(stage.rawBinaryData[stageBinaryKey{1, 1}])))
resp.WriteBytes(make([]byte, 16))
resp.WriteUint16(stageRankRestriction)
resp.WriteUint16(stageTarget)
resp.WriteBytes(make([]byte, 12))
resp.WriteNullTerminatedBytes(sessionStage)
resp.WriteBytes([]byte{0x00})
resp.WriteBytes(stage.rawBinaryData[stageBinaryKey{1, 0}])
resp.WriteBytes(stage.rawBinaryData[stageBinaryKey{1, 1}])
}
}
}
}
if (pkt.SearchType == 1 || pkt.SearchType == 3) && count == 0 {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return
}
resp.Seek(0, io.SeekStart)
resp.WriteUint16(count)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
@@ -641,86 +667,57 @@ func handleMsgMhfUpdateUnionItem(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfAcquireCafeItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireCafeItem)
var netcafe_points int
err := s.server.db.QueryRow("UPDATE characters SET netcafe_points = netcafe_points - $1 WHERE id = $2 RETURNING netcafe_points", pkt.PointCost, s.charID).Scan(&netcafe_points)
if err != nil {
s.logger.Fatal("Failed to get plate data savedata from db", zap.Error(err))
}
resp := byteframe.NewByteFrame()
resp.WriteUint32(uint32(netcafe_points))
doAckSimpleSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfUpdateCafepoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateCafepoint)
var netcafe_points int
err := s.server.db.QueryRow("SELECT COALESCE(netcafe_points, 0) FROM characters WHERE id = $1", s.charID).Scan(&netcafe_points)
if err != nil {
s.logger.Fatal("Failed to get plate data savedata from db", zap.Error(err))
}
resp := byteframe.NewByteFrame()
resp.WriteUint32(0)
resp.WriteUint32(uint32(netcafe_points))
doAckSimpleSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfCheckDailyCafepoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCheckDailyCafepoint)
// I am not sure exactly what this does, but all responses I have seen include this exact sequence of bytes
// 1 daily, 5 daily halk pots, 3 point boosted quests, also adds 5 netcafe points but not sent to client
// available once after midday every day
// get next midday
var t = Time_static()
year, month, day := t.Date()
midday := time.Date(year, month, day, 12, 0, 0, 0, t.Location())
if t.After(midday) {
midday = midday.Add(24 * time.Hour)
}
// get time after which daily claiming would be valid from db
var dailyTime time.Time
err := s.server.db.QueryRow("SELECT COALESCE(daily_time, $2) FROM characters WHERE id = $1", s.charID, time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)).Scan(&dailyTime)
if err != nil {
s.logger.Fatal("Failed to get daily_time savedata from db", zap.Error(err))
}
if t.After(dailyTime) {
// +5 netcafe points and setting next valid window
_, err := s.server.db.Exec("UPDATE characters SET daily_time=$1, netcafe_points=netcafe_points::int + 5 WHERE id=$2", midday, s.charID)
if err != nil {
s.logger.Fatal("Failed to update daily_time and netcafe_points savedata in db", zap.Error(err))
}
doAckBufSucceed(s, pkt.AckHandle, []byte{0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01})
} else {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
}
}
func handleMsgMhfGetCogInfo(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfCheckMonthlyItem(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfAcquireMonthlyItem(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfCheckWeeklyStamp(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCheckWeeklyStamp)
resp := byteframe.NewByteFrame()
resp.WriteUint16(0x000E)
resp.WriteUint16(0x0001)
resp.WriteUint16(0x0000)
resp.WriteUint16(0x0000) // 0x0000 stops the vaguely annoying log in pop up
resp.WriteUint32(0)
resp.WriteUint32(0x5dddcbb3) // Timestamp
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
weekCurrentStart := TimeWeekStart()
weekNextStart := TimeWeekNext()
var total, redeemed, updated uint16
var nextClaim time.Time
err := s.server.db.QueryRow(fmt.Sprintf("SELECT %s_next FROM stamps WHERE character_id=$1", pkt.StampType), s.charID).Scan(&nextClaim)
if err != nil {
s.server.db.Exec("INSERT INTO stamps (character_id, hl_next, ex_next) VALUES ($1, $2, $2)", s.charID, weekNextStart)
nextClaim = weekNextStart
}
if nextClaim.Before(weekCurrentStart) {
s.server.db.Exec(fmt.Sprintf("UPDATE stamps SET %s_total=%s_total+1, %s_next=$1 WHERE character_id=$2", pkt.StampType, pkt.StampType, pkt.StampType), weekNextStart, s.charID)
updated = 1
}
s.server.db.QueryRow(fmt.Sprintf("SELECT %s_total, %s_redeemed FROM stamps WHERE character_id=$1", pkt.StampType, pkt.StampType), s.charID).Scan(&total, &redeemed)
bf := byteframe.NewByteFrame()
bf.WriteUint16(total)
bf.WriteUint16(redeemed)
bf.WriteUint16(updated)
bf.WriteUint32(0) // Unk
bf.WriteUint32(uint32(weekCurrentStart.Unix()))
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfExchangeWeeklyStamp(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfExchangeWeeklyStamp(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfExchangeWeeklyStamp)
var total, redeemed uint16
var tktStack mhfpacket.WarehouseStack
if pkt.Unk1 == 0xA { // Yearly Sub Ex
s.server.db.QueryRow("UPDATE stamps SET hl_total=hl_total-48, hl_redeemed=hl_redeemed-48 WHERE character_id=$1 RETURNING hl_total, hl_redeemed", s.charID).Scan(&total, &redeemed)
tktStack = mhfpacket.WarehouseStack{ItemID: 0x08A2, Quantity: 1}
} else {
s.server.db.QueryRow(fmt.Sprintf("UPDATE stamps SET %s_redeemed=%s_redeemed+8 WHERE character_id=$1 RETURNING %s_total, %s_redeemed", pkt.StampType, pkt.StampType, pkt.StampType, pkt.StampType), s.charID).Scan(&total, &redeemed)
if pkt.StampType == "hl" {
tktStack = mhfpacket.WarehouseStack{ItemID: 0x065E, Quantity: 5}
} else {
tktStack = mhfpacket.WarehouseStack{ItemID: 0x065F, Quantity: 5}
}
}
addWarehouseGift(s, "item", tktStack)
bf := byteframe.NewByteFrame()
bf.WriteUint16(total)
bf.WriteUint16(redeemed)
bf.WriteUint16(0)
bf.WriteUint32(0) // Unk, but has possible values
bf.WriteUint32(uint32(TimeWeekStart().Unix()))
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func getGookData(s *Session, cid uint32) (uint16, []byte) {
var data []byte

View File

@@ -0,0 +1,239 @@
package channelserver
import (
"erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
"io"
"time"
)
func handleMsgMhfAcquireCafeItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireCafeItem)
var netcafePoints uint32
err := s.server.db.QueryRow("UPDATE characters SET netcafe_points = netcafe_points - $1 WHERE id = $2 RETURNING netcafe_points", pkt.PointCost, s.charID).Scan(&netcafePoints)
if err != nil {
s.logger.Fatal("Failed to get netcafe points from db", zap.Error(err))
}
resp := byteframe.NewByteFrame()
resp.WriteUint32(netcafePoints)
doAckSimpleSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfUpdateCafepoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateCafepoint)
var netcafePoints uint32
err := s.server.db.QueryRow("SELECT COALESCE(netcafe_points, 0) FROM characters WHERE id = $1", s.charID).Scan(&netcafePoints)
if err != nil {
s.logger.Fatal("Failed to get netcate points from db", zap.Error(err))
}
resp := byteframe.NewByteFrame()
resp.WriteUint32(netcafePoints)
doAckSimpleSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfCheckDailyCafepoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCheckDailyCafepoint)
// I am not sure exactly what this does, but all responses I have seen include this exact sequence of bytes
// 1 daily, 5 daily halk pots, 3 point boosted quests, also adds 5 netcafe points but not sent to client
// available once after midday every day
// get next midday
var t = Time_static()
year, month, day := t.Date()
midday := time.Date(year, month, day, 12, 0, 0, 0, t.Location())
if t.After(midday) {
midday = midday.Add(24 * time.Hour)
}
// get time after which daily claiming would be valid from db
var dailyTime time.Time
err := s.server.db.QueryRow("SELECT COALESCE(daily_time, $2) FROM characters WHERE id = $1", s.charID, time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)).Scan(&dailyTime)
if err != nil {
s.logger.Fatal("Failed to get daily_time savedata from db", zap.Error(err))
}
if t.After(dailyTime) {
// +5 netcafe points and setting next valid window
_, err := s.server.db.Exec("UPDATE characters SET daily_time=$1, netcafe_points=netcafe_points+5 WHERE id=$2", midday, s.charID)
if err != nil {
s.logger.Fatal("Failed to update daily_time and netcafe_points savedata in db", zap.Error(err))
}
doAckBufSucceed(s, pkt.AckHandle, []byte{0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01})
} else {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
}
}
func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetCafeDuration)
bf := byteframe.NewByteFrame()
var cafeTime uint32
err := s.server.db.QueryRow("SELECT cafe_time FROM characters WHERE id = $1", s.charID).Scan(&cafeTime)
if err != nil {
panic(err)
}
cafeTime = uint32(Time_Current_Adjusted().Unix()) - uint32(s.sessionStart) + cafeTime
bf.WriteUint32(cafeTime) // Total cafe time
bf.WriteUint16(0)
ps.Uint16(bf, "Resets at next maintenance", true)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
type CafeBonus struct {
ID uint32 `db:"id"`
TimeReq uint32 `db:"time_req"`
ItemType uint32 `db:"item_type"`
ItemID uint32 `db:"item_id"`
Quantity uint32 `db:"quantity"`
Claimed bool `db:"claimed"`
}
func handleMsgMhfGetCafeDurationBonusInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetCafeDurationBonusInfo)
bf := byteframe.NewByteFrame()
var count uint32
rows, err := s.server.db.Queryx(`
SELECT cb.id, time_req, item_type, item_id, quantity,
(
SELECT count(*)
FROM cafe_accepted ca
WHERE cb.id = ca.cafe_id AND ca.character_id = $1
)::int::bool AS claimed
FROM cafebonus cb ORDER BY id ASC;`, s.charID)
if err != nil {
s.logger.Error("Error getting cafebonus", zap.Error(err))
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
} else {
for rows.Next() {
count++
cafeBonus := &CafeBonus{}
err = rows.StructScan(&cafeBonus)
if err != nil {
s.logger.Error("Error scanning cafebonus", zap.Error(err))
}
bf.WriteUint32(cafeBonus.TimeReq)
bf.WriteUint32(cafeBonus.ItemType)
bf.WriteUint32(cafeBonus.ItemID)
bf.WriteUint32(cafeBonus.Quantity)
bf.WriteBool(cafeBonus.Claimed)
}
resp := byteframe.NewByteFrame()
resp.WriteUint32(0)
resp.WriteUint32(uint32(time.Now().Unix()))
resp.WriteUint32(count)
resp.WriteBytes(bf.Data())
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
}
func handleMsgMhfReceiveCafeDurationBonus(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReceiveCafeDurationBonus)
bf := byteframe.NewByteFrame()
var count uint32
bf.WriteUint32(0)
rows, err := s.server.db.Queryx(`
SELECT c.id, time_req, item_type, item_id, quantity
FROM cafebonus c
WHERE (
SELECT count(*)
FROM cafe_accepted ca
WHERE c.id = ca.cafe_id AND ca.character_id = $1
) < 1 AND (
SELECT ch.cafe_time + $2
FROM characters ch
WHERE ch.id = $1
) >= time_req`, s.charID, Time_Current_Adjusted().Unix()-s.sessionStart)
if err != nil {
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} else {
for rows.Next() {
cafeBonus := &CafeBonus{}
err = rows.StructScan(cafeBonus)
if err != nil {
continue
}
count++
bf.WriteUint32(cafeBonus.ID)
bf.WriteUint32(cafeBonus.ItemType)
bf.WriteUint32(cafeBonus.ItemID)
bf.WriteUint32(cafeBonus.Quantity)
}
bf.Seek(0, io.SeekStart)
bf.WriteUint32(count)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
}
func handleMsgMhfPostCafeDurationBonusReceived(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPostCafeDurationBonusReceived)
var cafeBonus CafeBonus
for _, cbID := range pkt.CafeBonusID {
err := s.server.db.QueryRow(`
SELECT cb.id, item_type, quantity FROM cafebonus cb WHERE cb.id=$1
`, cbID).Scan(&cafeBonus.ID, &cafeBonus.ItemType, &cafeBonus.Quantity)
if err == nil {
if cafeBonus.ItemType == 17 {
s.server.db.Exec("UPDATE characters SET netcafe_points=netcafe_points+$1 WHERE id=$2", cafeBonus.Quantity, s.charID)
}
}
s.server.db.Exec("INSERT INTO public.cafe_accepted VALUES ($1, $2)", cbID, s.charID)
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfStartBoostTime(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfStartBoostTime)
bf := byteframe.NewByteFrame()
boostLimit := Time_Current_Adjusted().Add(100 * time.Minute)
s.server.db.Exec("UPDATE characters SET boost_time=$1 WHERE id=$2", boostLimit, s.charID)
bf.WriteUint32(uint32(boostLimit.Unix()))
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfGetBoostTime(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetBoostTime)
doAckBufSucceed(s, pkt.AckHandle, []byte{})
}
func handleMsgMhfGetBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetBoostTimeLimit)
bf := byteframe.NewByteFrame()
var boostLimit time.Time
err := s.server.db.QueryRow("SELECT boost_time FROM characters WHERE id=$1", s.charID).Scan(&boostLimit)
if err != nil {
bf.WriteUint32(0)
} else {
bf.WriteUint32(uint32(boostLimit.Unix()))
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfGetBoostRight(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetBoostRight)
var boostLimit time.Time
err := s.server.db.QueryRow("SELECT boost_time FROM characters WHERE id=$1", s.charID).Scan(&boostLimit)
if err != nil {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
return
}
if boostLimit.Unix() > Time_Current_Adjusted().Unix() {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x01})
} else {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x02})
}
}
func handleMsgMhfPostBoostTimeQuestReturn(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPostBoostTimeQuestReturn)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfPostBoostTime(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfPostBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -165,6 +165,66 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
fmt.Printf("Got chat message: %+v\n", chatMessage)
// Flush all objects and users and reload
if strings.HasPrefix(chatMessage.Message, "!reload") {
sendServerChatMessage(s, "Reloading players...")
var temp mhfpacket.MHFPacket
deleteNotif := byteframe.NewByteFrame()
for _, object := range s.stage.objects {
if object.ownerCharID == s.charID {
continue
}
temp = &mhfpacket.MsgSysDeleteObject{ObjID: object.id}
deleteNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(deleteNotif, s.clientContext)
}
for _, session := range s.server.sessions {
if s == session {
continue
}
temp = &mhfpacket.MsgSysDeleteUser{CharID: session.charID}
deleteNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(deleteNotif, s.clientContext)
}
deleteNotif.WriteUint16(0x0010)
s.QueueSend(deleteNotif.Data())
time.Sleep(500 * time.Millisecond)
reloadNotif := byteframe.NewByteFrame()
for _, session := range s.server.sessions {
if s == session {
continue
}
temp = &mhfpacket.MsgSysInsertUser{CharID: session.charID}
reloadNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(reloadNotif, s.clientContext)
for i := 0; i < 3; i++ {
temp = &mhfpacket.MsgSysNotifyUserBinary{
CharID: session.charID,
BinaryType: uint8(i + 1),
}
reloadNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(reloadNotif, s.clientContext)
}
}
for _, obj := range s.stage.objects {
if obj.ownerCharID == s.charID {
continue
}
temp = &mhfpacket.MsgSysDuplicateObject{
ObjID: obj.id,
X: obj.x,
Y: obj.y,
Z: obj.z,
Unk0: 0,
OwnerCharID: obj.ownerCharID,
}
reloadNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(reloadNotif, s.clientContext)
}
reloadNotif.WriteUint16(0x0010)
s.QueueSend(reloadNotif.Data())
}
// Set account rights
if strings.HasPrefix(chatMessage.Message, "!rights") {
var v uint32
@@ -180,7 +240,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
}
// Discord integration
if chatMessage.Type == binpacket.ChatTypeLocal || chatMessage.Type == binpacket.ChatTypeParty {
if (pkt.BroadcastType == BroadcastTypeStage && s.stage.id == "sl1Ns200p0a0u0") || pkt.BroadcastType == BroadcastTypeWorld {
s.server.DiscordChannelSend(chatMessage.SenderName, chatMessage.Message)
}

View File

@@ -56,7 +56,7 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) {
s.logger.Fatal("Failed to update savedata in db", zap.Error(err))
}
s.logger.Info("Wrote recompressed savedata back to DB.")
dumpSaveData(s, pkt.RawDataPayload, "")
dumpSaveData(s, pkt.RawDataPayload, "savedata")
_, err = s.server.db.Exec("UPDATE characters SET weapon_type=$1 WHERE id=$2", uint16(decompressedData[128789]), s.charID)
if err != nil {
@@ -275,8 +275,8 @@ func dumpSaveData(s *Session, data []byte, suffix string) {
if !s.server.erupeConfig.DevModeOptions.SaveDumps.Enabled {
return
} else {
dir := filepath.Join(s.server.erupeConfig.DevModeOptions.SaveDumps.OutputDir, fmt.Sprintf("%s_", s.Name))
path := filepath.Join(s.server.erupeConfig.DevModeOptions.SaveDumps.OutputDir, fmt.Sprintf("%s_", s.Name), fmt.Sprintf("%d_%s_%s%s.bin", s.charID, s.Name, Time_Current().Format("2006-01-02_15.04.05"), suffix))
dir := filepath.Join(s.server.erupeConfig.DevModeOptions.SaveDumps.OutputDir, fmt.Sprintf("%d_%s", s.charID, s.Name))
path := filepath.Join(s.server.erupeConfig.DevModeOptions.SaveDumps.OutputDir, fmt.Sprintf("%d_%s", s.charID, s.Name), fmt.Sprintf("%d_%s_%s.bin", s.charID, s.Name, suffix))
if _, err := os.Stat(dir); os.IsNotExist(err) {
os.Mkdir(dir, os.ModeDir)
@@ -297,10 +297,11 @@ func handleMsgMhfLoaddata(s *Session, p mhfpacket.MHFPacket) {
}
var data []byte
err := s.server.db.QueryRow("SELECT savedata FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
s.logger.Fatal("Failed to get savedata from db", zap.Error(err))
if err != nil || len(data) == 0 {
s.logger.Warn(fmt.Sprintf("Failed to load savedata (CID: %d)", s.charID), zap.Error(err))
s.rawConn.Close() // Terminate the connection
return
}
doAckBufSucceed(s, pkt.AckHandle, data)
@@ -310,11 +311,11 @@ func handleMsgMhfLoaddata(s *Session, p mhfpacket.MHFPacket) {
}
bf := byteframe.NewByteFrameFromBytes(decompSaveData)
bf.Seek(88, io.SeekStart)
binary1 := bf.ReadNullTerminatedBytes()
name := bf.ReadNullTerminatedBytes()
s.server.userBinaryPartsLock.Lock()
s.server.userBinaryParts[userBinaryPartID{charID: s.charID, index: 1}] = append(binary1, []byte{0x00}...)
s.server.userBinaryParts[userBinaryPartID{charID: s.charID, index: 1}] = append(name, []byte{0x00}...)
s.server.userBinaryPartsLock.Unlock()
s.Name = stringsupport.SJISToUTF8(binary1)
s.Name = stringsupport.SJISToUTF8(name)
}
func handleMsgMhfSaveScenarioData(s *Session, p mhfpacket.MHFPacket) {

View File

@@ -2,87 +2,45 @@ package channelserver
import (
"fmt"
"github.com/bwmarrin/discordgo"
"sort"
"strings"
"github.com/bwmarrin/discordgo"
"unicode"
)
type Character struct {
ID uint32 `db:"id"`
IsFemale bool `db:"is_female"`
IsNewCharacter bool `db:"is_new_character"`
Name string `db:"name"`
UnkDescString string `db:"unk_desc_string"`
HRP uint16 `db:"hrp"`
GR uint16 `db:"gr"`
WeaponType uint16 `db:"weapon_type"`
LastLogin uint32 `db:"last_login"`
type Player struct {
CharName string
QuestID int
}
var weapons = []string{
"<:gs:970861408227049477>",
"<:hbg:970861408281563206>",
"<:hm:970861408239628308>",
"<:lc:970861408298352660>",
"<:sns:970861408319315988>",
"<:lbg:970861408327725137>",
"<:ds:970861408277368883>",
"<:ls:970861408319311872>",
"<:hh:970861408222863400>",
"<:gl:970861408327720980>",
"<:bw:970861408294174780>",
"<:tf:970861408424177744>",
"<:sw:970861408340283472>",
"<:ms:970861408411594842>",
}
func getPlayerSlice(s *Server) []Player {
var p []Player
var questIndex int
func (s *Server) getCharacterForUser(uid int) (*Character, error) {
character := Character{}
err := s.db.Get(&character, "SELECT id, is_female, is_new_character, name, unk_desc_string, hrp, gr, weapon_type, last_login FROM characters WHERE id = $1", uid)
if err != nil {
return nil, err
for _, channel := range s.Channels {
for _, stage := range channel.stages {
if len(stage.clients) == 0 {
continue
}
questID := 0
if stage.isQuest() {
questIndex++
questID = questIndex
}
for client := range stage.clients {
p = append(p, Player{
CharName: client.Name,
QuestID: questID,
})
}
}
}
return &character, nil
return p
}
func CountChars(s *Server) string {
count := 0
for _, stage := range s.stages {
count += len(stage.clients)
}
message := fmt.Sprintf("Server [%s]: %d players;", s.name, count)
return message
}
type ListPlayer struct {
CharName string
InQuest bool
WeaponEmoji string
QuestEmoji string
StageName string
}
func (p *ListPlayer) toString(length int) string {
status := ""
if p.InQuest {
status = "(in quest " + p.QuestEmoji + ")"
} else {
status = p.StageName
}
missingSpace := length - len(p.CharName)
whitespaces := strings.Repeat(" ", missingSpace+5)
return fmt.Sprintf("%s %s %s %s", p.WeaponEmoji, p.CharName, whitespaces, status)
}
func getPlayerList(s *Server) ([]ListPlayer, int) {
list := []ListPlayer{}
func getCharacterList(s *Server) string {
questEmojis := []string{
":person_in_lotus_position:",
":white_circle:",
":red_circle:",
":blue_circle:",
@@ -91,255 +49,41 @@ func getPlayerList(s *Server) ([]ListPlayer, int) {
":purple_circle:",
":yellow_circle:",
":orange_circle:",
":black_circle:",
}
bigNameLen := 0
playerSlice := getPlayerSlice(s)
for _, stage := range s.stages {
if len(stage.clients) == 0 {
continue
}
questEmoji := ":black_circle:"
if len(questEmojis) > 0 {
questEmoji = questEmojis[len(questEmojis)-1]
questEmojis = questEmojis[:len(questEmojis)-1]
}
isQuest := stage.isQuest()
for client := range stage.clients {
char, err := s.getCharacterForUser(int(client.charID))
if err == nil {
if len(char.Name) > bigNameLen {
bigNameLen = len(char.Name)
}
list = append(list, ListPlayer{
CharName: char.Name,
InQuest: isQuest,
QuestEmoji: questEmoji,
WeaponEmoji: weapons[char.WeaponType],
StageName: stage.GetName(),
})
}
}
}
return list, bigNameLen
}
func PlayerList(s *Server) string {
list := ""
count := 0
listPlayers, bigNameLen := getPlayerList(s)
sort.SliceStable(listPlayers, func(i, j int) bool {
return listPlayers[i].CharName < listPlayers[j].CharName
sort.SliceStable(playerSlice, func(i, j int) bool {
return playerSlice[i].QuestID < playerSlice[j].QuestID
})
for _, lp := range listPlayers {
list += lp.toString(bigNameLen) + "\n"
count++
message := fmt.Sprintf("===== Online: %d =====\n", len(playerSlice))
for _, player := range playerSlice {
message += fmt.Sprintf("%s %s", questEmojis[player.QuestID], player.CharName)
}
message := fmt.Sprintf("<:SnS:822963937360347148> Frontier Hunters Online: [%s ] <:switcha:822963906401533992> \n============== Total %d =============\n", s.name, count)
message += list
return message
}
func debug(s *Server) string {
list := ""
for _, stage := range s.stages {
if !stage.isQuest() && len(stage.objects) == 0 {
continue
}
list += fmt.Sprintf(" -> Stage: %s StageId: %s\n", stage.GetName(), stage.id)
isQuest := "false"
hasDeparted := "false"
if stage.isQuest() {
isQuest = "true"
}
list += fmt.Sprintf(" '-> isQuest: %s\n", isQuest)
if stage.isQuest() {
if len(stage.clients) > 0 {
hasDeparted = "true"
}
list += fmt.Sprintf(" '-> isDeparted: %s\n", hasDeparted)
list += fmt.Sprintf(" '-> reserveSlots (%d/%d)\n", len(stage.reservedClientSlots), stage.maxPlayers)
for charid, _ := range stage.reservedClientSlots {
char, err := s.getCharacterForUser(int(charid))
if err == nil {
list += fmt.Sprintf(" '-> %s\n", char.Name)
}
}
}
list += " '-> objects: \n"
for _, obj := range stage.objects {
objInfo := fmt.Sprintf("X,Y,Z: %f %f %f", obj.x, obj.y, obj.z)
list += fmt.Sprintf(" '-> ObjectId: %d - %s\n", obj.id, objInfo)
}
}
message := fmt.Sprintf("Objects in Server: [%s ]\n", s.name)
message += list
return message
}
func questlist(s *Server) string {
list := ""
for _, stage := range s.stages {
if !stage.isQuest() {
continue
}
hasDeparted := ""
if len(stage.clients) > 0 {
hasDeparted = " - departed"
}
list += fmt.Sprintf(" '-> StageId: %s (%d/%d) %s - %s\n", stage.id, len(stage.reservedClientSlots), stage.maxPlayers, hasDeparted, stage.createdAt)
for charid, _ := range stage.reservedClientSlots {
char, err := s.getCharacterForUser(int(charid))
if err == nil {
list += fmt.Sprintf(" '-> %s\n", char.Name)
}
}
}
message := fmt.Sprintf("Quests in Server: [%s ]\n", s.name)
message += list
return message
}
func removeStageById(s *Server, stageId string) string {
if s.stages[stageId] != nil {
delete(s.stages, stageId)
return "Stage deleted!"
}
return "Stage not found!"
}
func cleanStr(str string) string {
return strings.ToLower(strings.Trim(str, " "))
}
func getCharInfo(server *Server, charName string) string {
var s *Stage
var c *Session
for _, stage := range server.stages {
for client := range stage.clients {
if client.Name == "" {
continue
}
if cleanStr(client.Name) == cleanStr(charName) {
s = stage
c = client
}
}
}
if s == nil {
return "Character not found"
}
objInfo := ""
obj := server.FindObjectByChar(c.charID)
// server.logger.Info("Found object: %+v", zap.Object("obj", obj))
if obj != nil {
objInfo = fmt.Sprintf("X,Y,Z: %f %f %f", obj.x, obj.y, obj.z)
}
return fmt.Sprintf("Character: %s\nStage: %s\nStageId: %s\n%s", c.Name, s.GetName(), s.id, objInfo)
}
func (s *Server) isDiscordAdmin(ds *discordgo.Session, m *discordgo.MessageCreate) bool {
for _, role := range m.Member.Roles {
for _, id := range s.erupeConfig.Discord.DevRoles {
if id == role {
return true
}
}
}
return false
}
// onDiscordMessage handles receiving messages from discord and forwarding them ingame.
func (s *Server) onDiscordMessage(ds *discordgo.Session, m *discordgo.MessageCreate) {
// Ignore messages from our bot, or ones that are not in the correct channel.
if m.Author.ID == ds.State.User.ID || !s.enable {
if m.Author.Bot || m.ChannelID != s.erupeConfig.Discord.RealtimeChannelID {
return
}
// Ignore other channels in devMode
if s.erupeConfig.Discord.DevMode && m.ChannelID != s.erupeConfig.Discord.RealtimeChannelID {
return
}
args := strings.Split(m.Content, " ")
commandName := args[0]
// Move to slash commadns
if commandName == "!players" {
ds.ChannelMessageSend(m.ChannelID, PlayerList(s))
return
}
if commandName == "-char" {
if len(args) < 2 {
ds.ChannelMessageSend(m.ChannelID, "Usage: !char <char name>")
return
paddedName := strings.TrimSpace(strings.Map(func(r rune) rune {
if r > unicode.MaxASCII {
return -1
}
return r
}, m.Author.Username))
charName := strings.Join(args[1:], " ")
ds.ChannelMessageSend(m.ChannelID, getCharInfo(s, charName))
return
for i := 0; i < 8-len(m.Author.Username); i++ {
paddedName += " "
}
if commandName == "!debug" && s.isDiscordAdmin(ds, m) {
ds.ChannelMessageSend(m.ChannelID, debug(s))
return
}
if commandName == "!questlist" && s.isDiscordAdmin(ds, m) {
ds.ChannelMessageSend(m.ChannelID, questlist(s))
return
}
if commandName == "!remove-stage" && s.isDiscordAdmin(ds, m) {
if len(args) < 2 {
ds.ChannelMessageSend(m.ChannelID, "Usage: !remove-stage <stage id>")
return
}
stageId := strings.Join(args[1:], " ")
ds.ChannelMessageSend(m.ChannelID, removeStageById(s, stageId))
return
}
if m.ChannelID == s.erupeConfig.Discord.RealtimeChannelID {
message := fmt.Sprintf("[DISCORD] %s: %s", m.Author.Username, m.Content)
s.BroadcastChatMessage(s.discordBot.NormalizeDiscordMessage(message))
}
message := fmt.Sprintf("[D] %s > %s", paddedName, m.Content)
s.BroadcastChatMessage(s.discordBot.NormalizeDiscordMessage(message))
}

View File

@@ -303,34 +303,6 @@ func handleMsgMhfGetUdInfo(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfGetBoostTime(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetBoostTime)
doAckBufSucceed(s, pkt.AckHandle, []byte{})
updateRights(s)
}
func handleMsgMhfGetBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetBoostTimeLimit)
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfGetBoostRight(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetBoostRight)
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfPostBoostTimeQuestReturn(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPostBoostTimeQuestReturn)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfStartBoostTime(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfPostBoostTime(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfPostBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetRestrictionEvent(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfSetRestrictionEvent(s *Session, p mhfpacket.MHFPacket) {

View File

@@ -1892,11 +1892,25 @@ func handleMsgMhfSetGuildManageRight(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfCheckMonthlyItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCheckMonthlyItem)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x01})
// TODO: Implement month-by-month tracker, 0 = Not claimed, 1 = Claimed
}
func handleMsgMhfAcquireMonthlyItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireMonthlyItem)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfEnumerateInvGuild(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateInvGuild)
stubEnumerateNoResults(s, pkt.AckHandle)
}
func handleMsgMhfOperationInvGuild(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfOperationInvGuild(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfOperationInvGuild)
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfUpdateGuildcard(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -5,11 +5,37 @@ import (
ps "erupe-ce/common/pascalstring"
"erupe-ce/common/stringsupport"
"erupe-ce/network/mhfpacket"
"fmt"
"go.uber.org/zap"
"io"
"time"
)
const warehouseNamesQuery = `
SELECT
COALESCE(item0name, ''),
COALESCE(item1name, ''),
COALESCE(item2name, ''),
COALESCE(item3name, ''),
COALESCE(item4name, ''),
COALESCE(item5name, ''),
COALESCE(item6name, ''),
COALESCE(item7name, ''),
COALESCE(item8name, ''),
COALESCE(item9name, ''),
COALESCE(equip0name, ''),
COALESCE(equip1name, ''),
COALESCE(equip2name, ''),
COALESCE(equip3name, ''),
COALESCE(equip4name, ''),
COALESCE(equip5name, ''),
COALESCE(equip6name, ''),
COALESCE(equip7name, ''),
COALESCE(equip8name, ''),
COALESCE(equip9name, '')
FROM warehouse
`
func handleMsgMhfUpdateInterior(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateInterior)
_, err := s.server.db.Exec("UPDATE characters SET house=$1 WHERE id=$2", pkt.InteriorData, s.charID)
@@ -66,7 +92,7 @@ func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) {
}
case 3:
house := HouseData{}
row := s.server.db.QueryRowx("SELECT id, hrp, gr, name FROM characters WHERE name=$1", pkt.Name)
row := s.server.db.QueryRowx("SELECT id, hrp, gr, name FROM characters WHERE name ILIKE $1", fmt.Sprintf(`%%%s%%`, pkt.Name))
err := row.StructScan(&house)
if err != nil {
panic(err)
@@ -341,8 +367,190 @@ func handleMsgMhfAcquireTitle(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfResetTitle(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfOperateWarehouse(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfOperateWarehouse(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfOperateWarehouse)
var t int
err := s.server.db.QueryRow("SELECT character_id FROM warehouse WHERE character_id=$1", s.charID).Scan(&t)
if err != nil {
s.server.db.Exec("INSERT INTO warehouse (character_id) VALUES ($1)", s.charID)
}
bf := byteframe.NewByteFrame()
bf.WriteUint8(pkt.Operation)
switch pkt.Operation {
case 0:
var count uint8
itemNames := make([]string, 10)
equipNames := make([]string, 10)
s.server.db.QueryRow(fmt.Sprintf("%s WHERE character_id=$1", warehouseNamesQuery), s.charID).Scan(&itemNames[0],
&itemNames[1], &itemNames[2], &itemNames[3], &itemNames[4], &itemNames[5], &itemNames[6], &itemNames[7], &itemNames[8], &itemNames[9], &equipNames[0],
&equipNames[1], &equipNames[2], &equipNames[3], &equipNames[4], &equipNames[5], &equipNames[6], &equipNames[7], &equipNames[8], &equipNames[9])
bf.WriteUint32(0)
bf.WriteUint16(10000) // Usages
temp := byteframe.NewByteFrame()
for i, name := range itemNames {
if len(name) > 0 {
count++
temp.WriteUint8(0)
temp.WriteUint8(uint8(i))
ps.Uint8(temp, name, true)
}
}
for i, name := range equipNames {
if len(name) > 0 {
count++
temp.WriteUint8(1)
temp.WriteUint8(uint8(i))
ps.Uint8(temp, name, true)
}
}
bf.WriteUint8(count)
bf.WriteBytes(temp.Data())
case 1:
bf.WriteUint8(0)
case 2:
s.server.db.Exec(fmt.Sprintf("UPDATE warehouse SET %s%dname=$1 WHERE character_id=$2", pkt.BoxType, pkt.BoxIndex), pkt.Name, s.charID)
case 3:
bf.WriteUint32(0) // Usage renewal time, >1 = disabled
bf.WriteUint16(10000) // Usages
case 4:
bf.WriteUint32(0)
bf.WriteUint16(10000) // Usages
bf.WriteUint8(0)
}
// Opcodes
// 0 = Get box names
// 1 = Commit usage
// 2 = Rename
// 3 = Get usage limit
// 4 = Get gift box names (doesn't do anything?)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfEnumerateWarehouse(s *Session, p mhfpacket.MHFPacket) {}
func addWarehouseGift(s *Session, boxType string, giftStack mhfpacket.WarehouseStack) {
giftBox := getWarehouseBox(s, boxType, 10)
if boxType == "item" {
exists := false
for i, stack := range giftBox {
if stack.ItemID == giftStack.ItemID {
exists = true
giftBox[i].Quantity += giftStack.Quantity
break
}
}
if exists == false {
giftBox = append(giftBox, giftStack)
}
} else {
giftBox = append(giftBox, giftStack)
}
s.server.db.Exec(fmt.Sprintf("UPDATE warehouse SET %s10=$1 WHERE character_id=$2", boxType), boxToBytes(giftBox, boxType), s.charID)
}
func handleMsgMhfUpdateWarehouse(s *Session, p mhfpacket.MHFPacket) {}
func getWarehouseBox(s *Session, boxType string, boxIndex uint8) []mhfpacket.WarehouseStack {
var data []byte
s.server.db.QueryRow(fmt.Sprintf("SELECT %s%d FROM warehouse WHERE character_id=$1", boxType, boxIndex), s.charID).Scan(&data)
if len(data) > 0 {
box := byteframe.NewByteFrameFromBytes(data)
numStacks := box.ReadUint16()
stacks := make([]mhfpacket.WarehouseStack, numStacks)
for i := 0; i < int(numStacks); i++ {
if boxType == "item" {
stacks[i].ID = box.ReadUint32()
stacks[i].Index = box.ReadUint16()
stacks[i].ItemID = box.ReadUint16()
stacks[i].Quantity = box.ReadUint16()
box.ReadUint16()
} else {
stacks[i].ID = box.ReadUint32()
stacks[i].Index = box.ReadUint16()
stacks[i].EquipType = box.ReadUint16()
stacks[i].ItemID = box.ReadUint16()
stacks[i].Data = box.ReadBytes(56)
}
}
return stacks
} else {
return make([]mhfpacket.WarehouseStack, 0)
}
}
func boxToBytes(stacks []mhfpacket.WarehouseStack, boxType string) []byte {
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(len(stacks)))
for i, stack := range stacks {
if boxType == "item" {
bf.WriteUint32(stack.ID)
bf.WriteUint16(uint16(i + 1))
bf.WriteUint16(stack.ItemID)
bf.WriteUint16(stack.Quantity)
bf.WriteUint16(0)
} else {
bf.WriteUint32(stack.ID)
bf.WriteUint16(uint16(i + 1))
bf.WriteUint16(stack.EquipType)
bf.WriteUint16(stack.ItemID)
bf.WriteBytes(stack.Data)
}
}
bf.WriteUint16(0)
return bf.Data()
}
func handleMsgMhfEnumerateWarehouse(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateWarehouse)
box := getWarehouseBox(s, pkt.BoxType, pkt.BoxIndex)
if len(box) > 0 {
doAckBufSucceed(s, pkt.AckHandle, boxToBytes(box, pkt.BoxType))
} else {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
}
}
func handleMsgMhfUpdateWarehouse(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateWarehouse)
box := getWarehouseBox(s, pkt.BoxType, pkt.BoxIndex)
// Update existing stacks
var newStacks []mhfpacket.WarehouseStack
for _, update := range pkt.Updates {
exists := false
if pkt.BoxType == "item" {
for i, stack := range box {
if stack.Index == update.Index {
exists = true
box[i].Quantity = update.Quantity
break
}
}
} else {
for i, stack := range box {
if stack.Index == update.Index {
exists = true
box[i].ItemID = update.ItemID
break
}
}
}
if exists == false {
newStacks = append(newStacks, update)
}
}
// Append new stacks
for _, stack := range newStacks {
box = append(box, stack)
}
// Slice empty stacks
var cleanedBox []mhfpacket.WarehouseStack
for _, stack := range box {
if pkt.BoxType == "item" {
if stack.Quantity > 0 {
cleanedBox = append(cleanedBox, stack)
}
} else {
if stack.ItemID != 0 {
cleanedBox = append(cleanedBox, stack)
}
}
}
s.server.db.Exec(fmt.Sprintf("UPDATE warehouse SET %s%d=$1 WHERE character_id=$2", pkt.BoxType, pkt.BoxIndex), boxToBytes(cleanedBox, pkt.BoxType), s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}

View File

@@ -39,7 +39,7 @@ func handleMsgMhfLoadPartner(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfSavePartner(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSavePartner)
dumpSaveData(s, pkt.RawDataPayload, "_partner")
dumpSaveData(s, pkt.RawDataPayload, "partner")
_, err := s.server.db.Exec("UPDATE characters SET partner=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
if err != nil {
@@ -75,7 +75,7 @@ func handleMsgMhfLoadHunterNavi(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfSaveHunterNavi(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSaveHunterNavi)
dumpSaveData(s, pkt.RawDataPayload, "_hunternavi")
dumpSaveData(s, pkt.RawDataPayload, "hunternavi")
if pkt.IsDataDiff {
var data []byte
@@ -121,10 +121,30 @@ func handleMsgMhfSaveHunterNavi(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfMercenaryHuntdata(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfMercenaryHuntdata)
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0x0A))
if pkt.Unk0 == 1 {
// Format:
// uint8 Hunts
// struct Hunt
// uint32 HuntID
// uint32 MonID
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
} else {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0))
}
}
func handleMsgMhfEnumerateMercenaryLog(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfEnumerateMercenaryLog(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateMercenaryLog)
bf := byteframe.NewByteFrame()
bf.WriteUint32(0)
// Format:
// struct Log
// uint32 Timestamp
// []byte Name (len 18)
// uint8 Unk
// uint8 Unk
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfCreateMercenary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCreateMercenary)
@@ -208,6 +228,7 @@ func handleMsgMhfLoadOtomoAirou(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfSaveOtomoAirou(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSaveOtomoAirou)
dumpSaveData(s, pkt.RawDataPayload, "otomoairou")
decomp, err := nullcomp.Decompress(pkt.RawDataPayload[1:])
if err != nil {
s.logger.Error("Failed to decompress airou", zap.Error(err))

View File

@@ -11,7 +11,7 @@ func handleMsgSysOperateRegister(s *Session, p mhfpacket.MHFPacket) {
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
s.server.raviente.Lock()
switch pkt.SemaphoreID {
case 3:
case 4:
resp := byteframe.NewByteFrame()
size := 6
for i := 0; i < len(bf.Data())-1; i += size {
@@ -49,7 +49,7 @@ func handleMsgSysOperateRegister(s *Session, p mhfpacket.MHFPacket) {
}
resp.WriteUint8(0)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
case 4:
case 5:
resp := byteframe.NewByteFrame()
size := 6
for i := 0; i < len(bf.Data())-1; i += size {
@@ -74,7 +74,7 @@ func handleMsgSysOperateRegister(s *Session, p mhfpacket.MHFPacket) {
}
resp.WriteUint8(0)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
case 5:
case 6:
resp := byteframe.NewByteFrame()
size := 6
for i := 0; i < len(bf.Data())-1; i += size {
@@ -242,15 +242,15 @@ func handleMsgSysLoadRegister(s *Session, p mhfpacket.MHFPacket) {
func (s *Session) notifyRavi() {
var temp mhfpacket.MHFPacket
raviNotif := byteframe.NewByteFrame()
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 3}
raviNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(raviNotif, s.clientContext)
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 4}
raviNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(raviNotif, s.clientContext)
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 5}
raviNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(raviNotif, s.clientContext)
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 6}
raviNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(raviNotif, s.clientContext)
raviNotif.WriteUint16(0x0010) // End it.
sema := getRaviSemaphore(s)
if sema != "" {
@@ -262,7 +262,7 @@ func (s *Session) notifyRavi() {
func getRaviSemaphore(s *Session) string {
for _, semaphore := range s.server.semaphore {
if strings.HasPrefix(semaphore.id_semaphore, "hs_l0u3B5") && strings.HasSuffix(semaphore.id_semaphore, "3") {
if strings.HasPrefix(semaphore.id_semaphore, "hs_l0u3B5") && strings.HasSuffix(semaphore.id_semaphore, "4") {
return semaphore.id_semaphore
}
}

View File

@@ -1,6 +1,8 @@
package channelserver
import (
ps "erupe-ce/common/pascalstring"
"fmt"
"io/ioutil"
"path/filepath"
@@ -17,7 +19,19 @@ func handleMsgMhfSaveRengokuData(s *Session, p mhfpacket.MHFPacket) {
if err != nil {
s.logger.Fatal("Failed to update rengokudata savedata in db", zap.Error(err))
}
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
bf.Seek(71, 0)
maxStageMp := bf.ReadUint32()
maxScoreMp := bf.ReadUint32()
bf.Seek(4, 1)
maxStageSp := bf.ReadUint32()
maxScoreSp := bf.ReadUint32()
var t int
err = s.server.db.QueryRow("SELECT character_id FROM rengoku_score WHERE character_id=$1", s.charID).Scan(&t)
if err != nil {
s.server.db.Exec("INSERT INTO rengoku_score (character_id) VALUES ($1)", s.charID)
}
s.server.db.Exec("UPDATE rengoku_score SET max_stages_mp=$1, max_points_mp=$2, max_stages_sp=$3, max_points_sp=$4 WHERE character_id=$5", maxStageMp, maxScoreMp, maxStageSp, maxScoreSp, s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
@@ -81,16 +95,191 @@ func handleMsgMhfGetRengokuBinary(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, data)
}
const rengokuScoreQuery = `
SELECT max_stages_mp, max_points_mp, max_stages_sp, max_points_sp, c.name, gc.guild_id
FROM rengoku_score rs
LEFT JOIN characters c ON c.id = rs.character_id
LEFT JOIN guild_characters gc ON gc.character_id = rs.character_id
`
type RengokuScore struct {
Name string `db:"name"`
GuildID int `db:"guild_id"`
MaxStagesMP uint32 `db:"max_stages_mp"`
MaxPointsMP uint32 `db:"max_points_mp"`
MaxStagesSP uint32 `db:"max_stages_sp"`
MaxPointsSP uint32 `db:"max_points_sp"`
}
func handleMsgMhfEnumerateRengokuRanking(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateRengokuRanking)
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
guild, _ := GetGuildInfoByCharacterId(s, s.charID)
isApplicant, _ := guild.HasApplicationForCharID(s, s.charID)
if isApplicant {
guild = nil
}
var score RengokuScore
i := uint32(1)
bf := byteframe.NewByteFrame()
scoreData := byteframe.NewByteFrame()
switch pkt.Leaderboard {
case 0: // Max stage overall MP
rows, _ := s.server.db.Queryx(fmt.Sprintf("%s ORDER BY max_stages_mp DESC", rengokuScoreQuery))
for rows.Next() {
rows.StructScan(&score)
if score.Name == s.Name {
bf.WriteUint32(i)
bf.WriteUint32(score.MaxStagesMP)
ps.Uint8(bf, s.Name, true)
ps.Uint8(bf, "", false)
}
scoreData.WriteUint32(i)
scoreData.WriteUint32(score.MaxStagesMP)
ps.Uint8(scoreData, score.Name, true)
ps.Uint8(scoreData, "", false)
i++
}
case 1: // Max RdP overall MP
rows, _ := s.server.db.Queryx(fmt.Sprintf("%s ORDER BY max_points_mp DESC", rengokuScoreQuery))
for rows.Next() {
rows.StructScan(&score)
if score.Name == s.Name {
bf.WriteUint32(i)
bf.WriteUint32(score.MaxPointsMP)
ps.Uint8(bf, s.Name, true)
ps.Uint8(bf, "", false)
}
scoreData.WriteUint32(i)
scoreData.WriteUint32(score.MaxPointsMP)
ps.Uint8(scoreData, score.Name, true)
ps.Uint8(scoreData, "", false)
i++
}
case 2: // Max stage guild MP
if guild != nil {
rows, _ := s.server.db.Queryx(fmt.Sprintf("%s WHERE guild_id=$1 ORDER BY max_stages_mp DESC", rengokuScoreQuery), guild.ID)
for rows.Next() {
rows.StructScan(&score)
if score.Name == s.Name {
bf.WriteUint32(i)
bf.WriteUint32(score.MaxStagesMP)
ps.Uint8(bf, s.Name, true)
ps.Uint8(bf, "", false)
}
scoreData.WriteUint32(i)
scoreData.WriteUint32(score.MaxStagesMP)
ps.Uint8(scoreData, score.Name, true)
ps.Uint8(scoreData, "", false)
i++
}
} else {
bf.WriteBytes(make([]byte, 11))
}
case 3: // Max RdP guild MP
if guild != nil {
rows, _ := s.server.db.Queryx(fmt.Sprintf("%s WHERE guild_id=$1 ORDER BY max_points_mp DESC", rengokuScoreQuery), guild.ID)
for rows.Next() {
rows.StructScan(&score)
if score.Name == s.Name {
bf.WriteUint32(i)
bf.WriteUint32(score.MaxPointsMP)
ps.Uint8(bf, s.Name, true)
ps.Uint8(bf, "", false)
}
scoreData.WriteUint32(i)
scoreData.WriteUint32(score.MaxPointsMP)
ps.Uint8(scoreData, score.Name, true)
ps.Uint8(scoreData, "", false)
i++
}
} else {
bf.WriteBytes(make([]byte, 11))
}
case 4: // Max stage overall SP
rows, _ := s.server.db.Queryx(fmt.Sprintf("%s ORDER BY max_stages_sp DESC", rengokuScoreQuery))
for rows.Next() {
rows.StructScan(&score)
if score.Name == s.Name {
bf.WriteUint32(i)
bf.WriteUint32(score.MaxStagesSP)
ps.Uint8(bf, s.Name, true)
ps.Uint8(bf, "", false)
}
scoreData.WriteUint32(i)
scoreData.WriteUint32(score.MaxStagesSP)
ps.Uint8(scoreData, score.Name, true)
ps.Uint8(scoreData, "", false)
i++
}
case 5: // Max RdP overall SP
rows, _ := s.server.db.Queryx(fmt.Sprintf("%s ORDER BY max_points_sp DESC", rengokuScoreQuery))
for rows.Next() {
rows.StructScan(&score)
if score.Name == s.Name {
bf.WriteUint32(i)
bf.WriteUint32(score.MaxPointsSP)
ps.Uint8(bf, s.Name, true)
ps.Uint8(bf, "", false)
}
scoreData.WriteUint32(i)
scoreData.WriteUint32(score.MaxPointsSP)
ps.Uint8(scoreData, score.Name, true)
ps.Uint8(scoreData, "", false)
i++
}
case 6: // Max stage guild SP
if guild != nil {
rows, _ := s.server.db.Queryx(fmt.Sprintf("%s WHERE guild_id=$1 ORDER BY max_stages_sp DESC", rengokuScoreQuery), guild.ID)
for rows.Next() {
rows.StructScan(&score)
if score.Name == s.Name {
bf.WriteUint32(i)
bf.WriteUint32(score.MaxStagesSP)
ps.Uint8(bf, s.Name, true)
ps.Uint8(bf, "", false)
}
scoreData.WriteUint32(i)
scoreData.WriteUint32(score.MaxStagesSP)
ps.Uint8(scoreData, score.Name, true)
ps.Uint8(scoreData, "", false)
i++
}
} else {
bf.WriteBytes(make([]byte, 11))
}
case 7: // Max RdP guild SP
if guild != nil {
rows, _ := s.server.db.Queryx(fmt.Sprintf("%s WHERE guild_id=$1 ORDER BY max_points_sp DESC", rengokuScoreQuery), guild.ID)
for rows.Next() {
rows.StructScan(&score)
if score.Name == s.Name {
bf.WriteUint32(i)
bf.WriteUint32(score.MaxPointsSP)
ps.Uint8(bf, s.Name, true)
ps.Uint8(bf, "", false)
}
scoreData.WriteUint32(i)
scoreData.WriteUint32(score.MaxPointsSP)
ps.Uint8(scoreData, score.Name, true)
ps.Uint8(scoreData, "", false)
i++
}
} else {
bf.WriteBytes(make([]byte, 11))
}
}
bf.WriteUint8(uint8(i) - 1)
bf.WriteBytes(scoreData.Data())
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfGetRengokuRankingRank(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetRengokuRankingRank)
resp := byteframe.NewByteFrame()
resp.WriteBytes([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
// What is this for?
bf := byteframe.NewByteFrame()
bf.WriteUint32(0) // Max stage overall MP rank
bf.WriteUint32(0) // Max RdP overall MP rank
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}

View File

@@ -45,12 +45,10 @@ func destructEmptySemaphores(s *Session) {
}
func releaseRaviSemaphore(s *Session, sema *Semaphore) {
if !strings.HasSuffix(sema.id_semaphore, "5") {
delete(sema.reservedClientSlots, s.charID)
delete(sema.clients, s)
}
if len(sema.reservedClientSlots) == 0 && len(sema.clients) == 0 {
s.logger.Debug("Raviente semaphore is empty, resetting")
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)
}
}
@@ -91,7 +89,7 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
suffix, _ := strconv.ParseUint(pkt.SemaphoreID[len(pkt.SemaphoreID)-1:], 10, 32)
s.server.semaphore[SemaphoreID] = &Semaphore{
id_semaphore: pkt.SemaphoreID,
id: uint32(suffix),
id: uint32(suffix + 1),
clients: make(map[*Session]uint32),
reservedClientSlots: make(map[uint32]interface{}),
maxPlayers: 32,

View File

@@ -135,7 +135,6 @@ func removeSessionFromStage(s *Session) {
// Remove client from old stage.
s.stage.Lock()
delete(s.stage.clients, s)
delete(s.stage.reservedClientSlots, s.charID)
// Delete old stage objects owned by the client.
s.logger.Info("Sending notification to old stage clients")
@@ -157,6 +156,7 @@ func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) {
if s.stageID == "" {
s.stageMoveStack.Set(pkt.StageID)
} else {
s.stage.reservedClientSlots[s.charID] = false
s.stageMoveStack.Push(s.stageID)
s.stageMoveStack.Lock()
}
@@ -175,11 +175,18 @@ func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) {
// Transfer back to the saved stage ID before the previous move or enter.
s.stageMoveStack.Unlock()
backStage, err := s.stageMoveStack.Pop()
if err != nil {
panic(err)
}
if _, exists := s.stage.reservedClientSlots[s.charID]; exists {
delete(s.stage.reservedClientSlots, s.charID)
}
if _, exists := s.server.stages[backStage].reservedClientSlots[s.charID]; exists {
delete(s.server.stages[backStage].reservedClientSlots, s.charID)
}
doStageTransfer(s, pkt.AckHandle, backStage)
}

View File

@@ -2,7 +2,6 @@ package channelserver
import (
"encoding/hex"
"erupe-ce/network/mhfpacket"
)
@@ -58,11 +57,3 @@ func handleMsgMhfGetUdTacticsRanking(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfSetUdTacticsFollower(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetUdTacticsLog(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetCafeDurationBonusInfo(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfReceiveCafeDurationBonus(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfPostCafeDurationBonusReceived(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -15,23 +15,6 @@ import (
"go.uber.org/zap"
)
type StageIdType = string
const (
// GlobalStage is the stage that is used for all users.
MezeportaStageId StageIdType = "sl1Ns200p0a0u0"
GuildHallLv1StageId StageIdType = "sl1Ns202p0a0u0"
GuildHallLv2StageId StageIdType = "sl1Ns203p0a0u0"
GuildHallLv3StageId StageIdType = "sl1Ns204p0a0u0"
PugiFarmStageId StageIdType = "sl1Ns205p0a0u0"
RastaBarStageId StageIdType = "sl1Ns211p0a0u0"
PalloneCaravanStageId StageIdType = "sl1Ns260p0a0u0"
GookFarmStageId StageIdType = "sl1Ns265p0a0u0"
DivaFountainStageId StageIdType = "sl2Ns379p0a0u0"
DivaHallStageId StageIdType = "sl1Ns445p0a0u0"
MezFesStageId StageIdType = "sl1Ns462p0a0u0"
)
// Config struct allows configuring the server.
type Config struct {
ID uint16
@@ -80,8 +63,7 @@ type Server struct {
// Discord chat integration
discordBot *discordbot.DiscordBot
name string
enable bool
name string
raviente *Raviente
}
@@ -154,43 +136,30 @@ func NewServer(config *Config) *Server {
stages: make(map[string]*Stage),
userBinaryParts: make(map[userBinaryPartID][]byte),
semaphore: make(map[string]*Semaphore),
semaphoreIndex: 5,
semaphoreIndex: 7,
discordBot: config.DiscordBot,
name: config.Name,
enable: config.Enable,
raviente: NewRaviente(),
}
// Mezeporta
s.stages["sl1Ns200p0a0u0"] = NewStage("sl1Ns200p0a0u0")
// Guild Hall LV1
s.stages["sl1Ns202p0a0u0"] = NewStage("sl1Ns202p0a0u0")
// Guild Hall LV2
s.stages["sl1Ns203p0a0u0"] = NewStage("sl1Ns203p0a0u0")
// Guild Hall LV3
s.stages["sl1Ns204p0a0u0"] = NewStage("sl1Ns204p0a0u0")
// Pugi Farm
s.stages["sl1Ns205p0a0u0"] = NewStage("sl1Ns205p0a0u0")
// Rasta bar stage
s.stages["sl1Ns211p0a0u0"] = NewStage("sl1Ns211p0a0u0")
// Pallone Carvan
s.stages["sl1Ns260p0a0u0"] = NewStage("sl1Ns260p0a0u0")
// Gook Farm
s.stages["sl1Ns265p0a0u0"] = NewStage("sl1Ns265p0a0u0")
// Pallone Guest House 1st Floor
s.stages["sl1Ns262p0a0u0"] = NewStage("sl1Ns262p0a0u0")
// Pallone Guest House 2nd Floor
s.stages["sl1Ns263p0a0u0"] = NewStage("sl1Ns263p0a0u0")
// Diva fountain / prayer fountain.
s.stages["sl2Ns379p0a0u0"] = NewStage("sl2Ns379p0a0u0")
// Diva Hall
s.stages["sl1Ns445p0a0u0"] = NewStage("sl1Ns445p0a0u0")
// MezFes
s.stages["sl1Ns462p0a0u0"] = NewStage("sl1Ns462p0a0u0")
@@ -367,7 +336,7 @@ func (s *Server) BroadcastRaviente(ip uint32, port uint16, stage []byte, _type u
func (s *Server) DiscordChannelSend(charName string, content string) {
if s.erupeConfig.Discord.Enabled && s.discordBot != nil {
message := fmt.Sprintf("**%s** : %s", charName, content)
message := fmt.Sprintf("**%s**: %s", charName, content)
s.discordBot.RealtimeChannelSend(message)
}
}
@@ -413,6 +382,9 @@ func (s *Server) NextSemaphoreID() uint32 {
for {
exists := false
s.semaphoreIndex = s.semaphoreIndex + 1
if s.semaphoreIndex == 0 {
s.semaphoreIndex = 7 // Skip reserved indexes
}
for _, semaphore := range s.semaphore {
if semaphore.id == s.semaphoreIndex {
exists = true

View File

@@ -98,35 +98,6 @@ func (s *Stage) isQuest() bool {
return len(s.reservedClientSlots) > 0
}
func (s *Stage) GetName() string {
switch s.id {
case MezeportaStageId:
return "Mezeporta"
case GuildHallLv1StageId:
return "Guild Hall Lv1"
case GuildHallLv2StageId:
return "Guild Hall Lv2"
case GuildHallLv3StageId:
return "Guild Hall Lv3"
case PugiFarmStageId:
return "Pugi Farm"
case RastaBarStageId:
return "Rasta Bar"
case PalloneCaravanStageId:
return "Pallone Caravan"
case GookFarmStageId:
return "Gook Farm"
case DivaFountainStageId:
return "Diva Fountain"
case DivaHallStageId:
return "Diva Hall"
case MezFesStageId:
return "Mez Fes"
default:
return ""
}
}
func (s *Stage) NextObjectID() uint32 {
s.objectIndex = s.objectIndex + 1
// Objects beyond 127 do not duplicate correctly

View File

@@ -31,6 +31,16 @@ func Time_Current_Midnight() time.Time {
return time.Date(baseTime.Year(), baseTime.Month(), baseTime.Day(), 0, 0, 0, 0, baseTime.Location())
}
func TimeWeekStart() time.Time {
midnight := Time_Current_Midnight()
offset := (int(midnight.Weekday()) - 1) * -24
return midnight.Add(time.Hour * time.Duration(offset))
}
func TimeWeekNext() time.Time {
return TimeWeekStart().Add(time.Hour * 24 * 7)
}
func Time_Current_Week_uint8() uint8 {
baseTime := time.Now().In(time.FixedZone(fmt.Sprintf("UTC+%d", Offset), Offset*60*60)).AddDate(YearAdjust, MonthAdjust, DayAdjust)

View File

@@ -1,11 +1,10 @@
package discordbot
import (
"regexp"
"erupe-ce/config"
"github.com/bwmarrin/discordgo"
"go.uber.org/zap"
"regexp"
)
type DiscordBot struct {
@@ -16,12 +15,12 @@ type DiscordBot struct {
RealtimeChannel *discordgo.Channel
}
type DiscordBotOptions struct {
type Options struct {
Config *config.Config
Logger *zap.Logger
}
func NewDiscordBot(options DiscordBotOptions) (discordBot *DiscordBot, err error) {
func NewDiscordBot(options Options) (discordBot *DiscordBot, err error) {
session, err := discordgo.New("Bot " + options.Config.Discord.BotToken)
if err != nil {
@@ -29,13 +28,6 @@ func NewDiscordBot(options DiscordBotOptions) (discordBot *DiscordBot, err error
return nil, err
}
mainGuild, err := session.Guild(options.Config.Discord.ServerID)
if err != nil {
options.Logger.Fatal("Discord failed to get main guild", zap.Error(err))
return nil, err
}
realtimeChannel, err := session.Channel(options.Config.Discord.RealtimeChannelID)
if err != nil {
@@ -47,7 +39,6 @@ func NewDiscordBot(options DiscordBotOptions) (discordBot *DiscordBot, err error
config: options.Config,
logger: options.Logger,
Session: session,
MainGuild: mainGuild,
RealtimeChannel: realtimeChannel,
}
@@ -60,21 +51,10 @@ func (bot *DiscordBot) Start() (err error) {
return
}
func (bot *DiscordBot) FindRoleByID(id string) *discordgo.Role {
for _, role := range bot.MainGuild.Roles {
if role.ID == id {
return role
}
}
return nil
}
// Replace 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}>)?`)
roleRegex := regexp.MustCompile(`<@&(\d{17,19})>`)
result := ReplaceTextAll(message, userRegex, func(userId string) string {
user, err := bot.Session.User(userId)
@@ -90,17 +70,7 @@ func (bot *DiscordBot) NormalizeDiscordMessage(message string) string {
return ":" + emojiName + ":"
})
result = ReplaceTextAll(result, roleRegex, func(roleId string) string {
role := bot.FindRoleByID(roleId)
if role != nil {
return "@!" + role.Name
}
return "@!unknown"
})
return string(result)
return result
}
func (bot *DiscordBot) RealtimeChannelSend(message string) (err error) {