mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-02-06 10:06:53 +01:00
Merge branch 'main' into feature/diva
This commit is contained in:
@@ -258,6 +258,9 @@ func logoutPlayer(s *Session) {
|
||||
return
|
||||
}
|
||||
saveData.RP += uint16(rpGained)
|
||||
if saveData.RP >= 50000 {
|
||||
saveData.RP = 50000
|
||||
}
|
||||
saveData.Save(s)
|
||||
}
|
||||
|
||||
@@ -525,8 +528,6 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgCaExchangeItem(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfPresentBox(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfServerCommand(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfAnnounce(s *Session, p mhfpacket.MHFPacket) {
|
||||
@@ -585,10 +586,11 @@ func handleMsgMhfEnumerateUnionItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
err := s.server.db.QueryRow("SELECT item_box FROM users, characters WHERE characters.id = $1 AND users.id = characters.user_id", int(s.charID)).Scan(&boxContents)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get shared item box contents from db", zap.Error(err))
|
||||
s.logger.Error("Failed to get shared item box contents from db", zap.Error(err))
|
||||
bf.WriteBytes(make([]byte, 4))
|
||||
} else {
|
||||
if len(boxContents) == 0 {
|
||||
bf.WriteUint32(0x00)
|
||||
bf.WriteBytes(make([]byte, 4))
|
||||
} else {
|
||||
amount := len(boxContents) / 4
|
||||
bf.WriteUint16(uint16(amount))
|
||||
@@ -613,7 +615,9 @@ func handleMsgMhfUpdateUnionItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
err := s.server.db.QueryRow("SELECT item_box FROM users, characters WHERE characters.id = $1 AND users.id = characters.user_id", int(s.charID)).Scan(&boxContents)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get shared item box contents from db", zap.Error(err))
|
||||
s.logger.Error("Failed to get shared item box contents from db", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
} else {
|
||||
amount := len(boxContents) / 4
|
||||
oldItems = make([]Item, amount)
|
||||
@@ -661,9 +665,9 @@ func handleMsgMhfUpdateUnionItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
// Upload new item cache
|
||||
_, err = s.server.db.Exec("UPDATE users SET item_box = $1 FROM characters WHERE users.id = characters.user_id AND characters.id = $2", bf.Data(), int(s.charID))
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update shared item box contents in db", zap.Error(err))
|
||||
s.logger.Error("Failed to update shared item box contents in db", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfGetCogInfo(s *Session, p mhfpacket.MHFPacket) {}
|
||||
@@ -1499,25 +1503,74 @@ func handleMsgMhfInfoScenarioCounter(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfGetEtcPoints(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetEtcPoints)
|
||||
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(0x3) // Maybe a count of uint32(s)?
|
||||
resp.WriteUint32(0) // Point bonus quests
|
||||
resp.WriteUint32(0) // Daily quests
|
||||
resp.WriteUint32(0) // HS promotion points
|
||||
var dailyTime time.Time
|
||||
_ = 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 Time_Current_Adjusted().After(dailyTime) {
|
||||
s.server.db.Exec("UPDATE characters SET bonus_quests = 0, daily_quests = 0 WHERE id=$1", s.charID)
|
||||
}
|
||||
|
||||
var bonusQuests, dailyQuests, promoPoints uint32
|
||||
_ = s.server.db.QueryRow(`SELECT bonus_quests, daily_quests, promo_points FROM characters WHERE id = $1`, s.charID).Scan(&bonusQuests, &dailyQuests, &promoPoints)
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(3) // Maybe a count of uint32(s)?
|
||||
resp.WriteUint32(bonusQuests)
|
||||
resp.WriteUint32(dailyQuests)
|
||||
resp.WriteUint32(promoPoints)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfUpdateEtcPoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfUpdateEtcPoint)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
|
||||
var column string
|
||||
switch pkt.PointType {
|
||||
case 0:
|
||||
column = "bonus_quests"
|
||||
case 1:
|
||||
column = "daily_quests"
|
||||
case 2:
|
||||
column = "promo_points"
|
||||
}
|
||||
|
||||
var value int
|
||||
err := s.server.db.QueryRow(fmt.Sprintf(`SELECT %s FROM characters WHERE id = $1`, column), s.charID).Scan(&value)
|
||||
if err == nil {
|
||||
if value-int(pkt.Delta) < 0 {
|
||||
s.server.db.Exec(fmt.Sprintf(`UPDATE characters SET %s = 0 WHERE id = $1`, column), s.charID)
|
||||
} else {
|
||||
s.server.db.Exec(fmt.Sprintf(`UPDATE characters SET %s = %s + $1 WHERE id = $2`, column, column), pkt.Delta, s.charID)
|
||||
}
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfStampcardStamp(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfStampcardStamp)
|
||||
// TODO: Work out where it gets existing stamp count from, its format and then
|
||||
// update the actual sent values to be correct
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x03, 0xe7, 0x03, 0xe7, 0x02, 0x99, 0x02, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x14, 0xf8, 0x69, 0x54})
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(pkt.HR)
|
||||
bf.WriteUint16(pkt.GR)
|
||||
var stamps uint16
|
||||
_ = s.server.db.QueryRow(`SELECT stampcard FROM characters WHERE id = $1`, s.charID).Scan(&stamps)
|
||||
bf.WriteUint16(stamps)
|
||||
stamps += pkt.Stamps
|
||||
bf.WriteUint16(stamps)
|
||||
s.server.db.Exec(`UPDATE characters SET stampcard = $1 WHERE id = $2`, stamps, s.charID)
|
||||
if stamps%30 == 0 {
|
||||
bf.WriteUint16(2)
|
||||
bf.WriteUint16(pkt.Reward2)
|
||||
bf.WriteUint16(pkt.Item2)
|
||||
bf.WriteUint16(pkt.Quantity2)
|
||||
addWarehouseGift(s, "item", mhfpacket.WarehouseStack{ItemID: pkt.Item2, Quantity: pkt.Quantity2})
|
||||
} else if stamps%15 == 0 {
|
||||
bf.WriteUint16(1)
|
||||
bf.WriteUint16(pkt.Reward1)
|
||||
bf.WriteUint16(pkt.Item1)
|
||||
bf.WriteUint16(pkt.Quantity1)
|
||||
addWarehouseGift(s, "item", mhfpacket.WarehouseStack{ItemID: pkt.Item1, Quantity: pkt.Quantity1})
|
||||
} else {
|
||||
bf.WriteBytes(make([]byte, 8))
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfStampcardPrize(s *Session, p mhfpacket.MHFPacket) {}
|
||||
@@ -1569,7 +1622,7 @@ func handleMsgMhfGetEarthStatus(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
s.QueueAck(pkt.AckHandle, resp.Data())
|
||||
*/
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{})
|
||||
}
|
||||
|
||||
func handleMsgMhfRegistSpabiTime(s *Session, p mhfpacket.MHFPacket) {}
|
||||
@@ -1651,14 +1704,6 @@ func handleMsgMhfPostNotice(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfGetRandFromTable(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfGetTinyBin(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetTinyBin)
|
||||
// requested after conquest quests
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{})
|
||||
}
|
||||
|
||||
func handleMsgMhfPostTinyBin(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfGetSenyuDailyCount(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfGetSeibattle(s *Session, p mhfpacket.MHFPacket) {
|
||||
@@ -1668,45 +1713,6 @@ func handleMsgMhfGetSeibattle(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgMhfPostSeibattle(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfGetRyoudama(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetRyoudama)
|
||||
// likely guild related
|
||||
// REQ: 00 04 13 53 8F 18 00
|
||||
// RSP: 0A 21 8E AD 00 00 00 00 00 00 00 00 00 00 00 01 00 01 FE 4E
|
||||
// REQ: 00 06 13 53 8F 18 00
|
||||
// RSP: 0A 21 8E AD 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00
|
||||
// REQ: 00 05 13 53 8F 18 00
|
||||
// RSP: 0A 21 8E AD 00 00 00 00 00 00 00 00 00 00 00 0E 2A 15 9E CC 00 00 00 01 82 79 83 4E 83 8A 81 5B 83 69 00 00 00 00 1E 55 B0 2F 00 00 00 01 8D F7 00 00 00 00 00 00 00 00 00 00 00 00 2A 15 9E CC 00 00 00 02 82 79 83 4E 83 8A 81 5B 83 69 00 00 00 00 03 D5 30 56 00 00 00 02 95 BD 91 F2 97 42 00 00 00 00 00 00 00 00 3F 57 76 9F 00 00 00 03 93 56 92 6E 96 B3 97 70 00 00 00 00 00 00 38 D9 0E C4 00 00 00 03 87 64 83 78 83 42 00 00 00 00 00 00 00 00 23 F3 B9 77 00 00 00 04 82 B3 82 CC 82 DC 82 E9 81 99 00 00 00 00 3F 1B 17 9C 00 00 00 04 82 B1 82 A4 82 BD 00 00 00 00 00 00 00 00 00 B9 F9 C0 00 00 00 05 82 CD 82 E9 82 A9 00 00 00 00 00 00 00 00 23 9F 9A EA 00 00 00 05 83 70 83 62 83 4C 83 83 83 49 00 00 00 00 38 D9 0E C4 00 00 00 06 87 64 83 78 83 42 00 00 00 00 00 00 00 00 1E 55 B0 2F 00 00 00 06 8D F7 00 00 00 00 00 00 00 00 00 00 00 00 03 D5 30 56 00 00 00 07 95 BD 91 F2 97 42 00 00 00 00 00 00 00 00 02 D3 B8 77 00 00 00 07 6F 77 6C 32 35 32 35 00 00 00 00 00 00 00
|
||||
data, _ := hex.DecodeString("0A218EAD0000000000000000000000010000000000000000")
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfPostRyoudama(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfGetTenrouirai(s *Session, p mhfpacket.MHFPacket) {
|
||||
// if the game gets bad responses for this it breaks the ability to save
|
||||
pkt := p.(*mhfpacket.MsgMhfGetTenrouirai)
|
||||
var data []byte
|
||||
var err error
|
||||
if pkt.Unk0 == 1 {
|
||||
data, err = hex.DecodeString("0A218EAD000000000000000000000001010000000000060010")
|
||||
} else if pkt.Unk2 == 4 {
|
||||
data, err = hex.DecodeString("0A218EAD0000000000000000000000210101005000000202010102020104001000000202010102020106003200000202010002020104000C003202020101020201030032000002020101020202059C4000000202010002020105C35000320202010102020201003C00000202010102020203003200000201010001020203002800320201010101020204000C00000201010101020206002800000201010001020101003C00320201020101020105C35000000301020101020106003200000301020001020104001000320301020101020105C350000003010201010202030028000003010200010201030032003203010201010202059C4000000301020101010206002800000301020001010201003C00320301020101010206003200000301020101010204000C000003010200010101010050003203010201010101059C40000003010201010101030032000003010200010101040010003203010001010101060032000003010001010102030028000003010001010101010050003203010000010102059C4000000301000001010206002800000301000001010010")
|
||||
} else {
|
||||
data = []byte{0x00, 0x00, 0x00, 0x00}
|
||||
s.logger.Info("GET_TENROUIRAI request for unknown type")
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfPostTenrouirai(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostTenrouirai)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfGetDailyMissionMaster(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfGetDailyMissionPersonal(s *Session, p mhfpacket.MHFPacket) {}
|
||||
@@ -1722,7 +1728,8 @@ func handleMsgMhfGetEquipSkinHist(s *Session, p mhfpacket.MHFPacket) {
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT COALESCE(skin_hist::bytea, $2::bytea) FROM characters WHERE id = $1", s.charID, make([]byte, 0xC80)).Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get skin_hist savedata from db", zap.Error(err))
|
||||
s.logger.Error("Failed to load skin_hist", zap.Error(err))
|
||||
data = make([]byte, 3200)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
@@ -1733,7 +1740,9 @@ func handleMsgMhfUpdateEquipSkinHist(s *Session, p mhfpacket.MHFPacket) {
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT COALESCE(skin_hist, $2) FROM characters WHERE id = $1", s.charID, make([]byte, 0xC80)).Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get skin_hist from db", zap.Error(err))
|
||||
s.logger.Error("Failed to save skin_hist", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
return
|
||||
}
|
||||
|
||||
var bit int
|
||||
@@ -1787,8 +1796,8 @@ func handleMsgMhfGetEnhancedMinidata(s *Session, p mhfpacket.MHFPacket) {
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT minidata FROM characters WHERE id = $1", pkt.CharID).Scan(&data)
|
||||
if err != nil {
|
||||
data = make([]byte, 0x400) // returning empty might avoid a client softlock
|
||||
//s.logger.Fatal("Failed to get minidata from db", zap.Error(err))
|
||||
s.logger.Error("Failed to load minidata")
|
||||
data = make([]byte, 1)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
@@ -1798,7 +1807,7 @@ func handleMsgMhfSetEnhancedMinidata(s *Session, p mhfpacket.MHFPacket) {
|
||||
dumpSaveData(s, pkt.RawDataPayload, "minidata")
|
||||
_, err := s.server.db.Exec("UPDATE characters SET minidata=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update minidata in db", zap.Error(err))
|
||||
s.logger.Error("Failed to save minidata", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ func handleMsgMhfAcquireCafeItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
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))
|
||||
s.logger.Error("Failed to get netcafe points from db", zap.Error(err))
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(netcafePoints)
|
||||
@@ -27,7 +27,7 @@ func handleMsgMhfUpdateCafepoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
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))
|
||||
s.logger.Error("Failed to get netcate points from db", zap.Error(err))
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(netcafePoints)
|
||||
@@ -37,10 +37,6 @@ func handleMsgMhfUpdateCafepoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
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()
|
||||
@@ -53,19 +49,25 @@ func handleMsgMhfCheckDailyCafepoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
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))
|
||||
s.logger.Error("Failed to get daily_time savedata from db", zap.Error(err))
|
||||
}
|
||||
|
||||
var bondBonus, bonusQuests, dailyQuests uint32
|
||||
bf := byteframe.NewByteFrame()
|
||||
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})
|
||||
addPointNetcafe(s, 5)
|
||||
bondBonus = 5 // Bond point bonus quests
|
||||
bonusQuests = 3 // HRP bonus quests?
|
||||
dailyQuests = 1 // Daily quests
|
||||
s.server.db.Exec("UPDATE characters SET daily_time=$1, bonus_quests = $2, daily_quests = $3 WHERE id=$4", midday, bonusQuests, dailyQuests, s.charID)
|
||||
bf.WriteBool(true) // Success?
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
bf.WriteBool(false)
|
||||
}
|
||||
bf.WriteUint32(bondBonus)
|
||||
bf.WriteUint32(bonusQuests)
|
||||
bf.WriteUint32(dailyQuests)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) {
|
||||
@@ -194,7 +196,7 @@ func handleMsgMhfPostCafeDurationBonusReceived(s *Session, p mhfpacket.MHFPacket
|
||||
`, 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)
|
||||
addPointNetcafe(s, int(cafeBonus.Quantity))
|
||||
}
|
||||
}
|
||||
s.server.db.Exec("INSERT INTO public.cafe_accepted VALUES ($1, $2)", cbID, s.charID)
|
||||
@@ -202,6 +204,21 @@ func handleMsgMhfPostCafeDurationBonusReceived(s *Session, p mhfpacket.MHFPacket
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func addPointNetcafe(s *Session, p int) error {
|
||||
var points int
|
||||
err := s.server.db.QueryRow("SELECT netcafe_points FROM characters WHERE id = $1", s.charID).Scan(&points)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if points+p > 100000 {
|
||||
points = 100000
|
||||
} else {
|
||||
points += p
|
||||
}
|
||||
s.server.db.Exec("UPDATE characters SET netcafe_points=$1 WHERE id=$2", points, s.charID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleMsgMhfStartBoostTime(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfStartBoostTime)
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
@@ -1,9 +1,36 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
func handleMsgMhfGetRyoudama(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetRyoudama)
|
||||
// likely guild related
|
||||
// REQ: 00 04 13 53 8F 18 00
|
||||
// RSP: 0A 21 8E AD 00 00 00 00 00 00 00 00 00 00 00 01 00 01 FE 4E
|
||||
// REQ: 00 06 13 53 8F 18 00
|
||||
// RSP: 0A 21 8E AD 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00
|
||||
// REQ: 00 05 13 53 8F 18 00
|
||||
// RSP: 0A 21 8E AD 00 00 00 00 00 00 00 00 00 00 00 0E 2A 15 9E CC 00 00 00 01 82 79 83 4E 83 8A 81 5B 83 69 00 00 00 00 1E 55 B0 2F 00 00 00 01 8D F7 00 00 00 00 00 00 00 00 00 00 00 00 2A 15 9E CC 00 00 00 02 82 79 83 4E 83 8A 81 5B 83 69 00 00 00 00 03 D5 30 56 00 00 00 02 95 BD 91 F2 97 42 00 00 00 00 00 00 00 00 3F 57 76 9F 00 00 00 03 93 56 92 6E 96 B3 97 70 00 00 00 00 00 00 38 D9 0E C4 00 00 00 03 87 64 83 78 83 42 00 00 00 00 00 00 00 00 23 F3 B9 77 00 00 00 04 82 B3 82 CC 82 DC 82 E9 81 99 00 00 00 00 3F 1B 17 9C 00 00 00 04 82 B1 82 A4 82 BD 00 00 00 00 00 00 00 00 00 B9 F9 C0 00 00 00 05 82 CD 82 E9 82 A9 00 00 00 00 00 00 00 00 23 9F 9A EA 00 00 00 05 83 70 83 62 83 4C 83 83 83 49 00 00 00 00 38 D9 0E C4 00 00 00 06 87 64 83 78 83 42 00 00 00 00 00 00 00 00 1E 55 B0 2F 00 00 00 06 8D F7 00 00 00 00 00 00 00 00 00 00 00 00 03 D5 30 56 00 00 00 07 95 BD 91 F2 97 42 00 00 00 00 00 00 00 00 02 D3 B8 77 00 00 00 07 6F 77 6C 32 35 32 35 00 00 00 00 00 00 00
|
||||
data, _ := hex.DecodeString("0A218EAD0000000000000000000000010000000000000000")
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfPostRyoudama(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfGetTinyBin(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetTinyBin)
|
||||
// requested after conquest quests
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{})
|
||||
}
|
||||
|
||||
func handleMsgMhfPostTinyBin(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostTinyBin)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfCaravanMyScore(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfCaravanRanking(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
@@ -53,7 +53,7 @@ func init() {
|
||||
}
|
||||
|
||||
func sendDisabledCommandMessage(s *Session, cmd config.Command) {
|
||||
sendServerChatMessage(s, fmt.Sprintf("%s command is disabled", cmd.Name))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandDisabled"], cmd.Name))
|
||||
}
|
||||
|
||||
func sendServerChatMessage(s *Session, message string) {
|
||||
@@ -78,6 +78,233 @@ func sendServerChatMessage(s *Session, message string) {
|
||||
s.QueueSendMHF(castedBin)
|
||||
}
|
||||
|
||||
func parseChatCommand(s *Session, command string) {
|
||||
if strings.HasPrefix(command, commands["Reload"].Prefix) {
|
||||
// Flush all objects and users and reload
|
||||
if commands["Reload"].Enabled {
|
||||
sendServerChatMessage(s, s.server.dict["commandReload"])
|
||||
var temp mhfpacket.MHFPacket
|
||||
deleteNotif := byteframe.NewByteFrame()
|
||||
for _, object := range s.stage.objects {
|
||||
if object.ownerCharID == s.charID {
|
||||
continue
|
||||
}
|
||||
temp = &mhfpacket.MsgSysDeleteObject{ObjID: object.id}
|
||||
deleteNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(deleteNotif, s.clientContext)
|
||||
}
|
||||
for _, session := range s.server.sessions {
|
||||
if s == session {
|
||||
continue
|
||||
}
|
||||
temp = &mhfpacket.MsgSysDeleteUser{CharID: session.charID}
|
||||
deleteNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(deleteNotif, s.clientContext)
|
||||
}
|
||||
deleteNotif.WriteUint16(0x0010)
|
||||
s.QueueSend(deleteNotif.Data())
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
reloadNotif := byteframe.NewByteFrame()
|
||||
for _, session := range s.server.sessions {
|
||||
if s == session {
|
||||
continue
|
||||
}
|
||||
temp = &mhfpacket.MsgSysInsertUser{CharID: session.charID}
|
||||
reloadNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(reloadNotif, s.clientContext)
|
||||
for i := 0; i < 3; i++ {
|
||||
temp = &mhfpacket.MsgSysNotifyUserBinary{
|
||||
CharID: session.charID,
|
||||
BinaryType: uint8(i + 1),
|
||||
}
|
||||
reloadNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(reloadNotif, s.clientContext)
|
||||
}
|
||||
}
|
||||
for _, obj := range s.stage.objects {
|
||||
if obj.ownerCharID == s.charID {
|
||||
continue
|
||||
}
|
||||
temp = &mhfpacket.MsgSysDuplicateObject{
|
||||
ObjID: obj.id,
|
||||
X: obj.x,
|
||||
Y: obj.y,
|
||||
Z: obj.z,
|
||||
Unk0: 0,
|
||||
OwnerCharID: obj.ownerCharID,
|
||||
}
|
||||
reloadNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(reloadNotif, s.clientContext)
|
||||
}
|
||||
reloadNotif.WriteUint16(0x0010)
|
||||
s.QueueSend(reloadNotif.Data())
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Reload"])
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(command, commands["KeyQuest"].Prefix) {
|
||||
if commands["KeyQuest"].Enabled {
|
||||
if strings.HasPrefix(command, fmt.Sprintf("%s get", commands["KeyQuest"].Prefix)) {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandKqfGet"], s.kqf))
|
||||
} else if strings.HasPrefix(command, fmt.Sprintf("%s set", commands["KeyQuest"].Prefix)) {
|
||||
var hexs string
|
||||
n, numerr := fmt.Sscanf(command, fmt.Sprintf("%s set %%s", commands["KeyQuest"].Prefix), &hexs)
|
||||
if numerr != nil || n != 1 || len(hexs) != 16 {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandKqfSetError"], commands["KeyQuest"].Prefix))
|
||||
} else {
|
||||
hexd, _ := hex.DecodeString(hexs)
|
||||
s.kqf = hexd
|
||||
s.kqfOverride = true
|
||||
sendServerChatMessage(s, s.server.dict["commandKqfSetSuccess"])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["KeyQuest"])
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(command, commands["Rights"].Prefix) {
|
||||
// Set account rights
|
||||
if commands["Rights"].Enabled {
|
||||
var v uint32
|
||||
n, err := fmt.Sscanf(command, fmt.Sprintf("%s %%d", commands["Rights"].Prefix), &v)
|
||||
if err != nil || n != 1 {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsError"], commands["Rights"].Prefix))
|
||||
} else {
|
||||
_, err = s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", v, s.charID)
|
||||
if err == nil {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsSuccess"], v))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Rights"])
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(command, commands["Course"].Prefix) {
|
||||
if commands["Course"].Enabled {
|
||||
var name string
|
||||
n, err := fmt.Sscanf(command, fmt.Sprintf("%s %%s", commands["Course"].Prefix), &name)
|
||||
if err != nil || n != 1 {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseError"], commands["Course"].Prefix))
|
||||
} else {
|
||||
name = strings.ToLower(name)
|
||||
for _, course := range mhfpacket.Courses() {
|
||||
for _, alias := range course.Aliases {
|
||||
if strings.ToLower(name) == strings.ToLower(alias) {
|
||||
if slices.Contains(s.server.erupeConfig.Courses, config.Course{Name: course.Aliases[0], Enabled: true}) {
|
||||
if s.FindCourse(name).ID != 0 {
|
||||
ei := slices.IndexFunc(s.courses, func(c mhfpacket.Course) bool {
|
||||
for _, alias := range c.Aliases {
|
||||
if strings.ToLower(name) == strings.ToLower(alias) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
if ei != -1 {
|
||||
s.courses = append(s.courses[:ei], s.courses[ei+1:]...)
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseDisabled"], course.Aliases[0]))
|
||||
}
|
||||
} else {
|
||||
s.courses = append(s.courses, course)
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseEnabled"], course.Aliases[0]))
|
||||
}
|
||||
var newInt uint32
|
||||
for _, course := range s.courses {
|
||||
newInt += uint32(math.Pow(2, float64(course.ID)))
|
||||
}
|
||||
s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", newInt, s.charID)
|
||||
updateRights(s)
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseLocked"], course.Aliases[0]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Course"])
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(command, commands["Raviente"].Prefix) {
|
||||
if commands["Raviente"].Enabled {
|
||||
if getRaviSemaphore(s.server) != nil {
|
||||
s.server.raviente.Lock()
|
||||
if !strings.HasPrefix(command, "!ravi ") {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviNoCommand"])
|
||||
} else {
|
||||
if strings.HasPrefix(command, "!ravi start") {
|
||||
if s.server.raviente.register.startTime == 0 {
|
||||
s.server.raviente.register.startTime = s.server.raviente.register.postTime
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviStartSuccess"])
|
||||
s.notifyRavi()
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviStartError"])
|
||||
}
|
||||
} else if strings.HasPrefix(command, "!ravi cm") || strings.HasPrefix(command, "!ravi checkmultiplier") {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRaviMultiplier"], s.server.raviente.GetRaviMultiplier(s.server)))
|
||||
} else if strings.HasPrefix(command, "!ravi sr") || strings.HasPrefix(command, "!ravi sendres") {
|
||||
if s.server.raviente.state.stateData[28] > 0 {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviResSuccess"])
|
||||
s.server.raviente.state.stateData[28] = 0
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviResError"])
|
||||
}
|
||||
} else if strings.HasPrefix(command, "!ravi ss") || strings.HasPrefix(command, "!ravi sendsed") {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviSedSuccess"])
|
||||
// Total BerRavi HP
|
||||
HP := s.server.raviente.state.stateData[0] + s.server.raviente.state.stateData[1] + s.server.raviente.state.stateData[2] + s.server.raviente.state.stateData[3] + s.server.raviente.state.stateData[4]
|
||||
s.server.raviente.support.supportData[1] = HP
|
||||
} else if strings.HasPrefix(command, "!ravi rs") || strings.HasPrefix(command, "!ravi reqsed") {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviRequest"])
|
||||
// Total BerRavi HP
|
||||
HP := s.server.raviente.state.stateData[0] + s.server.raviente.state.stateData[1] + s.server.raviente.state.stateData[2] + s.server.raviente.state.stateData[3] + s.server.raviente.state.stateData[4]
|
||||
s.server.raviente.support.supportData[1] = HP + 12
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviError"])
|
||||
}
|
||||
}
|
||||
s.server.raviente.Unlock()
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviNoPlayers"])
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Raviente"])
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(command, commands["Teleport"].Prefix) {
|
||||
if commands["Teleport"].Enabled {
|
||||
var x, y int16
|
||||
n, err := fmt.Sscanf(command, fmt.Sprintf("%s %%d %%d", commands["Teleport"].Prefix), &x, &y)
|
||||
if err != nil || n != 2 {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandTeleportError"], commands["Teleport"].Prefix))
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandTeleportSuccess"], x, y))
|
||||
|
||||
// Make the inside of the casted binary
|
||||
payload := byteframe.NewByteFrame()
|
||||
payload.SetLE()
|
||||
payload.WriteUint8(2) // SetState type(position == 2)
|
||||
payload.WriteInt16(x) // X
|
||||
payload.WriteInt16(y) // Y
|
||||
payloadBytes := payload.Data()
|
||||
|
||||
s.QueueSendMHF(&mhfpacket.MsgSysCastedBinary{
|
||||
CharID: s.charID,
|
||||
MessageType: BinaryMessageTypeState,
|
||||
RawDataPayload: payloadBytes,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Teleport"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysCastBinary)
|
||||
tmp := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
|
||||
@@ -144,6 +371,18 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
roll.WriteNullTerminatedBytes([]byte(dice))
|
||||
roll.WriteNullTerminatedBytes(tmp.ReadNullTerminatedBytes())
|
||||
realPayload = roll.Data()
|
||||
} else {
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
|
||||
bf.SetLE()
|
||||
chatMessage := &binpacket.MsgBinChat{}
|
||||
chatMessage.Parse(bf)
|
||||
if strings.HasPrefix(chatMessage.Message, "!") {
|
||||
parseChatCommand(s, chatMessage.Message)
|
||||
return
|
||||
}
|
||||
if (pkt.BroadcastType == BroadcastTypeStage && s.stage.id == "sl1Ns200p0a0u0") || pkt.BroadcastType == BroadcastTypeWorld {
|
||||
s.server.DiscordChannelSend(chatMessage.SenderName, chatMessage.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,8 +406,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
case BroadcastTypeServer:
|
||||
if pkt.MessageType == 1 {
|
||||
raviSema := getRaviSemaphore(s)
|
||||
if raviSema != "" {
|
||||
if getRaviSemaphore(s.server) != nil {
|
||||
s.server.BroadcastMHF(resp, s)
|
||||
}
|
||||
} else {
|
||||
@@ -190,266 +428,6 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
// Handle chat
|
||||
if pkt.MessageType == BinaryMessageTypeChat {
|
||||
bf := byteframe.NewByteFrameFromBytes(realPayload)
|
||||
|
||||
// IMPORTANT! Casted binary objects are sent _as they are in memory_,
|
||||
// this means little endian for LE CPUs, might be different for PS3/PS4/PSP/XBOX.
|
||||
bf.SetLE()
|
||||
|
||||
chatMessage := &binpacket.MsgBinChat{}
|
||||
chatMessage.Parse(bf)
|
||||
|
||||
fmt.Printf("Got chat message: %+v\n", chatMessage)
|
||||
|
||||
// Discord integration
|
||||
if (pkt.BroadcastType == BroadcastTypeStage && s.stage.id == "sl1Ns200p0a0u0") || pkt.BroadcastType == BroadcastTypeWorld {
|
||||
s.server.DiscordChannelSend(chatMessage.SenderName, chatMessage.Message)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(chatMessage.Message, commands["Reload"].Prefix) {
|
||||
// Flush all objects and users and reload
|
||||
if commands["Reload"].Enabled {
|
||||
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())
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Reload"])
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(chatMessage.Message, commands["KeyQuest"].Prefix) {
|
||||
if commands["KeyQuest"].Enabled {
|
||||
if strings.HasPrefix(chatMessage.Message, "!kqf get") {
|
||||
sendServerChatMessage(s, fmt.Sprintf("KQF: %x", s.kqf))
|
||||
} else if strings.HasPrefix(chatMessage.Message, "!kqf set") {
|
||||
var hexs string
|
||||
n, numerr := fmt.Sscanf(chatMessage.Message, "!kqf set %s", &hexs)
|
||||
if numerr != nil || n != 1 || len(hexs) != 16 {
|
||||
sendServerChatMessage(s, "Error in command. Format: !kqf set xxxxxxxxxxxxxxxx")
|
||||
} else {
|
||||
hexd, _ := hex.DecodeString(hexs)
|
||||
s.kqf = hexd
|
||||
s.kqfOverride = true
|
||||
sendServerChatMessage(s, "KQF set, please switch Land/World")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["KeyQuest"])
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(chatMessage.Message, commands["Rights"].Prefix) {
|
||||
// Set account rights
|
||||
if commands["Rights"].Enabled {
|
||||
var v uint32
|
||||
n, err := fmt.Sscanf(chatMessage.Message, "!rights %d", &v)
|
||||
if err != nil || n != 1 {
|
||||
sendServerChatMessage(s, "Error in command. Format: !rights n")
|
||||
} else {
|
||||
_, err = s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", v, s.charID)
|
||||
if err == nil {
|
||||
sendServerChatMessage(s, fmt.Sprintf("Set rights integer: %d", v))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Rights"])
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(chatMessage.Message, commands["Course"].Prefix) {
|
||||
if commands["Course"].Enabled {
|
||||
var name string
|
||||
n, err := fmt.Sscanf(chatMessage.Message, "!course %s", &name)
|
||||
if err != nil || n != 1 {
|
||||
sendServerChatMessage(s, "Error in command. Format: !course <name>")
|
||||
} else {
|
||||
name = strings.ToLower(name)
|
||||
for _, course := range mhfpacket.Courses() {
|
||||
for _, alias := range course.Aliases {
|
||||
if strings.ToLower(name) == strings.ToLower(alias) {
|
||||
if slices.Contains(s.server.erupeConfig.Courses, config.Course{Name: course.Aliases[0], Enabled: true}) {
|
||||
if s.FindCourse(name).ID != 0 {
|
||||
ei := slices.IndexFunc(s.courses, func(c mhfpacket.Course) bool {
|
||||
for _, alias := range c.Aliases {
|
||||
if strings.ToLower(name) == strings.ToLower(alias) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
if ei != -1 {
|
||||
s.courses = append(s.courses[:ei], s.courses[ei+1:]...)
|
||||
sendServerChatMessage(s, fmt.Sprintf(`%s Course disabled`, course.Aliases[0]))
|
||||
}
|
||||
} else {
|
||||
s.courses = append(s.courses, course)
|
||||
sendServerChatMessage(s, fmt.Sprintf(`%s Course enabled`, course.Aliases[0]))
|
||||
}
|
||||
var newInt uint32
|
||||
for _, course := range s.courses {
|
||||
newInt += uint32(math.Pow(2, float64(course.ID)))
|
||||
}
|
||||
s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", newInt, s.charID)
|
||||
updateRights(s)
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(`%s Course is locked`, course.Aliases[0]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Course"])
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(chatMessage.Message, commands["Raviente"].Prefix) {
|
||||
if commands["Raviente"].Enabled {
|
||||
if getRaviSemaphore(s) != "" {
|
||||
s.server.raviente.Lock()
|
||||
if !strings.HasPrefix(chatMessage.Message, "!ravi ") {
|
||||
sendServerChatMessage(s, "No Raviente command specified!")
|
||||
} else {
|
||||
if strings.HasPrefix(chatMessage.Message, "!ravi start") {
|
||||
if s.server.raviente.register.startTime == 0 {
|
||||
s.server.raviente.register.startTime = s.server.raviente.register.postTime
|
||||
sendServerChatMessage(s, "The Great Slaying will begin in a moment")
|
||||
s.notifyRavi()
|
||||
} else {
|
||||
sendServerChatMessage(s, "The Great Slaying has already begun!")
|
||||
}
|
||||
} else if strings.HasPrefix(chatMessage.Message, "!ravi sm") || strings.HasPrefix(chatMessage.Message, "!ravi setmultiplier") {
|
||||
var num uint16
|
||||
n, numerr := fmt.Sscanf(chatMessage.Message, "!ravi sm %d", &num)
|
||||
if numerr != nil || n != 1 {
|
||||
sendServerChatMessage(s, "Error in command. Format: !ravi sm n")
|
||||
} else if s.server.raviente.state.damageMultiplier == 1 {
|
||||
if num > 32 {
|
||||
sendServerChatMessage(s, "Raviente multiplier too high, defaulting to 32x")
|
||||
s.server.raviente.state.damageMultiplier = 32
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf("Raviente multiplier set to %dx", num))
|
||||
s.server.raviente.state.damageMultiplier = uint32(num)
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf("Raviente multiplier is already set to %dx!", s.server.raviente.state.damageMultiplier))
|
||||
}
|
||||
} else if strings.HasPrefix(chatMessage.Message, "!ravi cm") || strings.HasPrefix(chatMessage.Message, "!ravi checkmultiplier") {
|
||||
sendServerChatMessage(s, fmt.Sprintf("Raviente multiplier is currently %dx", s.server.raviente.state.damageMultiplier))
|
||||
} else if strings.HasPrefix(chatMessage.Message, "!ravi sr") || strings.HasPrefix(chatMessage.Message, "!ravi sendres") {
|
||||
if s.server.raviente.state.stateData[28] > 0 {
|
||||
sendServerChatMessage(s, "Sending resurrection support!")
|
||||
s.server.raviente.state.stateData[28] = 0
|
||||
} else {
|
||||
sendServerChatMessage(s, "Resurrection support has not been requested!")
|
||||
}
|
||||
} else if strings.HasPrefix(chatMessage.Message, "!ravi ss") || strings.HasPrefix(chatMessage.Message, "!ravi sendsed") {
|
||||
sendServerChatMessage(s, "Sending sedation support if requested!")
|
||||
// Total BerRavi HP
|
||||
HP := s.server.raviente.state.stateData[0] + s.server.raviente.state.stateData[1] + s.server.raviente.state.stateData[2] + s.server.raviente.state.stateData[3] + s.server.raviente.state.stateData[4]
|
||||
s.server.raviente.support.supportData[1] = HP
|
||||
} else if strings.HasPrefix(chatMessage.Message, "!ravi rs") || strings.HasPrefix(chatMessage.Message, "!ravi reqsed") {
|
||||
sendServerChatMessage(s, "Requesting sedation support!")
|
||||
// Total BerRavi HP
|
||||
HP := s.server.raviente.state.stateData[0] + s.server.raviente.state.stateData[1] + s.server.raviente.state.stateData[2] + s.server.raviente.state.stateData[3] + s.server.raviente.state.stateData[4]
|
||||
s.server.raviente.support.supportData[1] = HP + 12
|
||||
} else {
|
||||
sendServerChatMessage(s, "Raviente command not recognised!")
|
||||
}
|
||||
}
|
||||
s.server.raviente.Unlock()
|
||||
} else {
|
||||
sendServerChatMessage(s, "No one has joined the Great Slaying!")
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Raviente"])
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(chatMessage.Message, commands["Teleport"].Prefix) {
|
||||
if commands["Teleport"].Enabled {
|
||||
var x, y int16
|
||||
n, err := fmt.Sscanf(chatMessage.Message, "!tele %d %d", &x, &y)
|
||||
if err != nil || n != 2 {
|
||||
sendServerChatMessage(s, "Invalid command. Usage:\"!tele 500 500\"")
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf("Teleporting to %d %d", x, y))
|
||||
|
||||
// Make the inside of the casted binary
|
||||
payload := byteframe.NewByteFrame()
|
||||
payload.SetLE()
|
||||
payload.WriteUint8(2) // SetState type(position == 2)
|
||||
payload.WriteInt16(x) // X
|
||||
payload.WriteInt16(y) // Y
|
||||
payloadBytes := payload.Data()
|
||||
|
||||
s.QueueSendMHF(&mhfpacket.MsgSysCastedBinary{
|
||||
CharID: s.charID,
|
||||
MessageType: BinaryMessageTypeState,
|
||||
RawDataPayload: payloadBytes,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Teleport"])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgSysCastedBinary(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
@@ -2,6 +2,7 @@ package channelserver
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"erupe-ce/common/bfutil"
|
||||
"erupe-ce/common/stringsupport"
|
||||
|
||||
@@ -58,6 +59,7 @@ func GetCharacterSaveData(s *Session, charID uint32) (*CharacterSaveData, error)
|
||||
}
|
||||
defer result.Close()
|
||||
if !result.Next() {
|
||||
err = errors.New("no savedata found")
|
||||
s.logger.Error("No savedata found", zap.Uint32("charID", charID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -29,7 +29,9 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) {
|
||||
// diffs themselves are also potentially compressed
|
||||
diff, err := nullcomp.Decompress(pkt.RawDataPayload)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to decompress diff", zap.Error(err))
|
||||
s.logger.Error("Failed to decompress diff", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
// Perform diff.
|
||||
s.logger.Info("Diffing...")
|
||||
@@ -39,12 +41,20 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) {
|
||||
// Regular blob update.
|
||||
saveData, err := nullcomp.Decompress(pkt.RawDataPayload)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to decompress savedata from packet", zap.Error(err))
|
||||
s.logger.Error("Failed to decompress savedata from packet", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
s.logger.Info("Updating save with blob")
|
||||
characterSaveData.decompSave = saveData
|
||||
}
|
||||
characterSaveData.updateStructWithSaveData()
|
||||
|
||||
// Bypass name-checker if new
|
||||
if characterSaveData.IsNewCharacter == true {
|
||||
s.Name = characterSaveData.Name
|
||||
}
|
||||
|
||||
if characterSaveData.Name == s.Name {
|
||||
characterSaveData.Save(s)
|
||||
s.logger.Info("Wrote recompressed savedata back to DB.")
|
||||
@@ -58,9 +68,9 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
_, err = s.server.db.Exec("UPDATE characters SET name=$1 WHERE id=$2", characterSaveData.Name, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update character name in db", zap.Error(err))
|
||||
s.logger.Error("Failed to update character name in db", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func grpToGR(n uint32) uint16 {
|
||||
@@ -230,7 +240,7 @@ func dumpSaveData(s *Session, data []byte, suffix string) {
|
||||
_, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err = os.Mkdir(dir, os.ModeDir)
|
||||
err = os.Mkdir(dir, os.ModePerm)
|
||||
if err != nil {
|
||||
s.logger.Warn("Error dumping savedata, could not create folder")
|
||||
return
|
||||
@@ -282,15 +292,9 @@ func handleMsgMhfSaveScenarioData(s *Session, p mhfpacket.MHFPacket) {
|
||||
dumpSaveData(s, pkt.RawDataPayload, "scenario")
|
||||
_, err := s.server.db.Exec("UPDATE characters SET scenariodata = $1 WHERE id = $2", pkt.RawDataPayload, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update scenario data in db", zap.Error(err))
|
||||
s.logger.Error("Failed to update scenario data in db", zap.Error(err))
|
||||
}
|
||||
// Do this ack manually because it uses a non-(0|1) error code
|
||||
s.QueueSendMHF(&mhfpacket.MsgSysAck{
|
||||
AckHandle: pkt.AckHandle,
|
||||
IsBufferResponse: false,
|
||||
ErrorCode: 0x40,
|
||||
AckData: []byte{0x00, 0x00, 0x00, 0x40},
|
||||
})
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfLoadScenarioData(s *Session, p mhfpacket.MHFPacket) {
|
||||
@@ -299,13 +303,10 @@ func handleMsgMhfLoadScenarioData(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
err := s.server.db.QueryRow("SELECT scenariodata FROM characters WHERE id = $1", s.charID).Scan(&scenarioData)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get scenario data contents in db", zap.Error(err))
|
||||
s.logger.Error("Failed to load scenariodata", zap.Error(err))
|
||||
bf.WriteBytes(make([]byte, 10))
|
||||
} else {
|
||||
if len(scenarioData) == 0 {
|
||||
bf.WriteUint32(0x00)
|
||||
} else {
|
||||
bf.WriteBytes(scenarioData)
|
||||
}
|
||||
bf.WriteBytes(scenarioData)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
@@ -94,6 +94,35 @@ func handleMsgMhfApplyDistItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
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)
|
||||
|
||||
@@ -736,7 +736,10 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
// TODO: This doesn't implement blocking, if someone unlocked the same outfit at the same time
|
||||
s.server.db.Exec(`UPDATE guilds SET pugi_outfits=pugi_outfits+$1 WHERE id=$2`, int(math.Pow(float64(pkt.Data1.ReadUint32()), 2)), guild.ID)
|
||||
case mhfpacket.OPERATE_GUILD_DONATE_EVENT:
|
||||
bf.WriteBytes(handleDonateRP(s, uint16(pkt.Data1.ReadUint32()), guild, true))
|
||||
quantity := uint16(pkt.Data1.ReadUint32())
|
||||
bf.WriteBytes(handleDonateRP(s, quantity, guild, true))
|
||||
// TODO: Move this value onto rp_yesterday and reset to 0... daily?
|
||||
s.server.db.Exec(`UPDATE guild_characters SET rp_today=rp_today+$1 WHERE character_id=$2`, quantity, s.charID)
|
||||
case mhfpacket.OPERATE_GUILD_EVENT_EXCHANGE:
|
||||
rp := uint16(pkt.Data1.ReadUint32())
|
||||
var balance uint32
|
||||
@@ -1443,8 +1446,9 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteUint16(0)
|
||||
}
|
||||
|
||||
for range guildMembers {
|
||||
bf.WriteUint32(0x00) // Unk
|
||||
for _, member := range guildMembers {
|
||||
bf.WriteUint16(member.RPToday)
|
||||
bf.WriteUint16(member.RPYesterday)
|
||||
}
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
@@ -1523,10 +1527,11 @@ func handleMsgMhfEnumerateGuildItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
err := s.server.db.QueryRow("SELECT item_box FROM guilds WHERE id = $1", int(pkt.GuildId)).Scan(&boxContents)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get guild item box contents from db", zap.Error(err))
|
||||
s.logger.Error("Failed to get guild item box contents from db", zap.Error(err))
|
||||
bf.WriteBytes(make([]byte, 4))
|
||||
} else {
|
||||
if len(boxContents) == 0 {
|
||||
bf.WriteUint32(0x00)
|
||||
bf.WriteBytes(make([]byte, 4))
|
||||
} else {
|
||||
amount := len(boxContents) / 4
|
||||
bf.WriteUint16(uint16(amount))
|
||||
@@ -1556,7 +1561,9 @@ func handleMsgMhfUpdateGuildItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
var oldItems []Item
|
||||
err := s.server.db.QueryRow("SELECT item_box FROM guilds WHERE id = $1", int(pkt.GuildId)).Scan(&boxContents)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get guild item box contents from db", zap.Error(err))
|
||||
s.logger.Error("Failed to get guild item box contents from db", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
} else {
|
||||
amount := len(boxContents) / 4
|
||||
oldItems = make([]Item, amount)
|
||||
@@ -1604,7 +1611,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))
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update guild item box contents in db", zap.Error(err))
|
||||
s.logger.Error("Failed to update guild item box contents in db", zap.Error(err))
|
||||
}
|
||||
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
@@ -1729,7 +1736,8 @@ func handleMsgMhfLoadGuildCooking(s *Session, p mhfpacket.MHFPacket) {
|
||||
guild, _ := GetGuildInfoByCharacterId(s, s.charID)
|
||||
data, err := s.server.db.Queryx("SELECT id, meal_id, level, expires FROM guild_meals WHERE guild_id = $1", guild.ID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get guild meals from db", zap.Error(err))
|
||||
s.logger.Error("Failed to get guild meals from db", zap.Error(err))
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 2))
|
||||
}
|
||||
temp := byteframe.NewByteFrame()
|
||||
count := 0
|
||||
@@ -1737,7 +1745,7 @@ func handleMsgMhfLoadGuildCooking(s *Session, p mhfpacket.MHFPacket) {
|
||||
mealData := &GuildMeal{}
|
||||
err = data.StructScan(&mealData)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to scan meal data", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
if mealData.Expires > uint32(Time_Current_Adjusted().Add(-60*time.Minute).Unix()) {
|
||||
count++
|
||||
@@ -1759,12 +1767,12 @@ func handleMsgMhfRegistGuildCooking(s *Session, p mhfpacket.MHFPacket) {
|
||||
if pkt.OverwriteID != 0 {
|
||||
_, err := s.server.db.Exec("DELETE FROM guild_meals WHERE id = $1", pkt.OverwriteID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to delete meal in db", zap.Error(err))
|
||||
s.logger.Error("Failed to delete meal in db", zap.Error(err))
|
||||
}
|
||||
}
|
||||
_, err := s.server.db.Exec("INSERT INTO guild_meals (guild_id, meal_id, level, expires) VALUES ($1, $2, $3, $4)", guild.ID, pkt.MealID, pkt.Success, Time_Current_Adjusted().Add(30*time.Minute).Unix())
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to register meal in db", zap.Error(err))
|
||||
s.logger.Error("Failed to register meal in db", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x01, 0x00})
|
||||
}
|
||||
@@ -1803,58 +1811,51 @@ func handleMsgMhfGuildHuntdata(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
|
||||
type MessageBoardPost struct {
|
||||
Type uint32 `db:"post_type"`
|
||||
StampID uint32 `db:"stamp_id"`
|
||||
Title string `db:"title"`
|
||||
Body string `db:"body"`
|
||||
AuthorID uint32 `db:"author_id"`
|
||||
Timestamp uint64 `db:"created_at"`
|
||||
LikedBy string `db:"liked_by"`
|
||||
ID uint32 `db:"id"`
|
||||
StampID uint32 `db:"stamp_id"`
|
||||
Title string `db:"title"`
|
||||
Body string `db:"body"`
|
||||
AuthorID uint32 `db:"author_id"`
|
||||
Timestamp time.Time `db:"created_at"`
|
||||
LikedBy string `db:"liked_by"`
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateGuildMessageBoard(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateGuildMessageBoard)
|
||||
guild, _ := GetGuildInfoByCharacterId(s, s.charID)
|
||||
|
||||
msgs, err := s.server.db.Queryx("SELECT post_type, stamp_id, title, body, author_id, (EXTRACT(epoch FROM created_at)::int) as created_at, liked_by FROM guild_posts WHERE guild_id = $1 AND post_type = $2 ORDER BY created_at DESC", guild.ID, int(pkt.BoardType))
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get guild messages from db", zap.Error(err))
|
||||
if pkt.BoardType == 1 {
|
||||
pkt.MaxPosts = 4
|
||||
}
|
||||
|
||||
msgs, err := s.server.db.Queryx("SELECT id, stamp_id, title, body, author_id, created_at, liked_by FROM guild_posts WHERE guild_id = $1 AND post_type = $2 ORDER BY created_at DESC", guild.ID, int(pkt.BoardType))
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get guild messages from db", zap.Error(err))
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
s.server.db.Exec("UPDATE characters SET guild_post_checked = $1 WHERE id = $2", time.Now(), s.charID)
|
||||
bf := byteframe.NewByteFrame()
|
||||
noMsgs := true
|
||||
postCount := 0
|
||||
var postCount uint32
|
||||
for msgs.Next() {
|
||||
noMsgs = false
|
||||
postCount++
|
||||
postData := &MessageBoardPost{}
|
||||
err = msgs.StructScan(&postData)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get guild messages from db", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
bf.WriteUint32(postData.Type)
|
||||
postCount++
|
||||
bf.WriteUint32(postData.ID)
|
||||
bf.WriteUint32(postData.AuthorID)
|
||||
bf.WriteUint64(postData.Timestamp)
|
||||
likedBySlice := strings.Split(postData.LikedBy, ",")
|
||||
if likedBySlice[0] == "" {
|
||||
bf.WriteUint32(0)
|
||||
} else {
|
||||
bf.WriteUint32(uint32(len(likedBySlice)))
|
||||
}
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(uint32(postData.Timestamp.Unix()))
|
||||
bf.WriteUint32(uint32(stringsupport.CSVLength(postData.LikedBy)))
|
||||
bf.WriteBool(stringsupport.CSVContains(postData.LikedBy, int(s.charID)))
|
||||
bf.WriteUint32(postData.StampID)
|
||||
ps.Uint32(bf, postData.Title, true)
|
||||
ps.Uint32(bf, postData.Body, true)
|
||||
}
|
||||
if noMsgs {
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
} else {
|
||||
data := byteframe.NewByteFrame()
|
||||
data.WriteUint32(uint32(postCount))
|
||||
data.WriteBytes(bf.Data())
|
||||
doAckBufSucceed(s, pkt.AckHandle, data.Data())
|
||||
}
|
||||
data := byteframe.NewByteFrame()
|
||||
data.WriteUint32(postCount)
|
||||
data.WriteBytes(bf.Data())
|
||||
doAckBufSucceed(s, pkt.AckHandle, data.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfUpdateGuildMessageBoard(s *Session, p mhfpacket.MHFPacket) {
|
||||
@@ -1869,101 +1870,62 @@ func handleMsgMhfUpdateGuildMessageBoard(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
var titleConv, bodyConv string
|
||||
switch pkt.MessageOp {
|
||||
case 0: // Create message
|
||||
postType := bf.ReadUint32() // 0 = message, 1 = news
|
||||
stampId := bf.ReadUint32()
|
||||
stampID := bf.ReadUint32()
|
||||
titleLength := bf.ReadUint32()
|
||||
bodyLength := bf.ReadUint32()
|
||||
title := bf.ReadBytes(uint(titleLength))
|
||||
body := bf.ReadBytes(uint(bodyLength))
|
||||
titleConv = stringsupport.SJISToUTF8(title)
|
||||
bodyConv = stringsupport.SJISToUTF8(body)
|
||||
_, err := s.server.db.Exec("INSERT INTO guild_posts (guild_id, author_id, stamp_id, post_type, title, body) VALUES ($1, $2, $3, $4, $5, $6)", guild.ID, s.charID, int(stampId), int(postType), titleConv, bodyConv)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to add new guild message to db", zap.Error(err))
|
||||
}
|
||||
title := stringsupport.SJISToUTF8(bf.ReadBytes(uint(titleLength)))
|
||||
body := stringsupport.SJISToUTF8(bf.ReadBytes(uint(bodyLength)))
|
||||
s.server.db.Exec("INSERT INTO guild_posts (guild_id, author_id, stamp_id, post_type, title, body) VALUES ($1, $2, $3, $4, $5, $6)", guild.ID, s.charID, stampID, postType, title, body)
|
||||
// TODO: if there are too many messages, purge excess
|
||||
_, err = s.server.db.Exec("")
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to remove excess guild messages from db", zap.Error(err))
|
||||
}
|
||||
case 1: // Delete message
|
||||
postType := bf.ReadUint32()
|
||||
timestamp := bf.ReadUint64()
|
||||
_, err := s.server.db.Exec("DELETE FROM guild_posts WHERE post_type = $1 AND (EXTRACT(epoch FROM created_at)::int) = $2 AND guild_id = $3", int(postType), int(timestamp), guild.ID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to delete guild message from db", zap.Error(err))
|
||||
}
|
||||
postID := bf.ReadUint32()
|
||||
s.server.db.Exec("DELETE FROM guild_posts WHERE id = $1", postID)
|
||||
case 2: // Update message
|
||||
postType := bf.ReadUint32()
|
||||
timestamp := bf.ReadUint64()
|
||||
postID := bf.ReadUint32()
|
||||
bf.ReadBytes(8)
|
||||
titleLength := bf.ReadUint32()
|
||||
bodyLength := bf.ReadUint32()
|
||||
title := bf.ReadBytes(uint(titleLength))
|
||||
body := bf.ReadBytes(uint(bodyLength))
|
||||
titleConv = stringsupport.SJISToUTF8(title)
|
||||
bodyConv = stringsupport.SJISToUTF8(body)
|
||||
_, err := s.server.db.Exec("UPDATE guild_posts SET title = $1, body = $2 WHERE post_type = $3 AND (EXTRACT(epoch FROM created_at)::int) = $4 AND guild_id = $5", titleConv, bodyConv, int(postType), int(timestamp), guild.ID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update guild message in db", zap.Error(err))
|
||||
}
|
||||
title := stringsupport.SJISToUTF8(bf.ReadBytes(uint(titleLength)))
|
||||
body := stringsupport.SJISToUTF8(bf.ReadBytes(uint(bodyLength)))
|
||||
s.server.db.Exec("UPDATE guild_posts SET title = $1, body = $2 WHERE id = $3", title, body, postID)
|
||||
case 3: // Update stamp
|
||||
postType := bf.ReadUint32()
|
||||
timestamp := bf.ReadUint64()
|
||||
stampId := bf.ReadUint32()
|
||||
_, err := s.server.db.Exec("UPDATE guild_posts SET stamp_id = $1 WHERE post_type = $2 AND (EXTRACT(epoch FROM created_at)::int) = $3 AND guild_id = $4", int(stampId), int(postType), int(timestamp), guild.ID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update guild message stamp in db", zap.Error(err))
|
||||
}
|
||||
postID := bf.ReadUint32()
|
||||
bf.ReadBytes(8)
|
||||
stampID := bf.ReadUint32()
|
||||
s.server.db.Exec("UPDATE guild_posts SET stamp_id = $1 WHERE id = $2", stampID, postID)
|
||||
case 4: // Like message
|
||||
postType := bf.ReadUint32()
|
||||
timestamp := bf.ReadUint64()
|
||||
postID := bf.ReadUint32()
|
||||
bf.ReadBytes(8)
|
||||
likeState := bf.ReadBool()
|
||||
var likedBy string
|
||||
err := s.server.db.QueryRow("SELECT liked_by FROM guild_posts WHERE post_type = $1 AND (EXTRACT(epoch FROM created_at)::int) = $2 AND guild_id = $3", int(postType), int(timestamp), guild.ID).Scan(&likedBy)
|
||||
err := s.server.db.QueryRow("SELECT liked_by FROM guild_posts WHERE id = $1", postID).Scan(&likedBy)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get guild message like data from db", zap.Error(err))
|
||||
s.logger.Error("Failed to get guild message like data from db", zap.Error(err))
|
||||
} else {
|
||||
if likeState {
|
||||
likedBy = stringsupport.CSVAdd(likedBy, int(s.charID))
|
||||
_, err := s.server.db.Exec("UPDATE guild_posts SET liked_by = $1 WHERE post_type = $2 AND (EXTRACT(epoch FROM created_at)::int) = $3 AND guild_id = $4", likedBy, int(postType), int(timestamp), guild.ID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to like guild message in db", zap.Error(err))
|
||||
}
|
||||
s.server.db.Exec("UPDATE guild_posts SET liked_by = $1 WHERE id = $2", likedBy, postID)
|
||||
} else {
|
||||
likedBy = stringsupport.CSVRemove(likedBy, int(s.charID))
|
||||
_, err := s.server.db.Exec("UPDATE guild_posts SET liked_by = $1 WHERE post_type = $2 AND (EXTRACT(epoch FROM created_at)::int) = $3 AND guild_id = $4", likedBy, int(postType), int(timestamp), guild.ID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to unlike guild message in db", zap.Error(err))
|
||||
}
|
||||
s.server.db.Exec("UPDATE guild_posts SET liked_by = $1 WHERE id = $2", likedBy, postID)
|
||||
}
|
||||
}
|
||||
case 5: // Check for new messages
|
||||
var timeChecked int
|
||||
var timeChecked time.Time
|
||||
var newPosts int
|
||||
err := s.server.db.QueryRow("SELECT (EXTRACT(epoch FROM guild_post_checked)::int) FROM characters WHERE id = $1", s.charID).Scan(&timeChecked)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get last guild post check timestamp from db", zap.Error(err))
|
||||
} else {
|
||||
_, err = s.server.db.Exec("UPDATE characters SET guild_post_checked = $1 WHERE id = $2", time.Now(), s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update guild post check timestamp in db", zap.Error(err))
|
||||
} else {
|
||||
err = s.server.db.QueryRow("SELECT COUNT(*) FROM guild_posts WHERE guild_id = $1 AND (EXTRACT(epoch FROM created_at)::int) > $2 AND author_id != $3", guild.ID, timeChecked, s.charID).Scan(&newPosts)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to check for new guild posts in db", zap.Error(err))
|
||||
} else {
|
||||
if newPosts > 0 {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x01})
|
||||
return
|
||||
}
|
||||
}
|
||||
err := s.server.db.QueryRow("SELECT guild_post_checked FROM characters WHERE id = $1", s.charID).Scan(&timeChecked)
|
||||
if err == nil {
|
||||
s.server.db.QueryRow("SELECT COUNT(*) FROM guild_posts WHERE guild_id = $1 AND (EXTRACT(epoch FROM created_at)::int) > $2", guild.ID, timeChecked.Unix()).Scan(&newPosts)
|
||||
if newPosts > 0 {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x01})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfEntryRookieGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
@@ -23,7 +23,9 @@ func handleMsgMhfLoadGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
|
||||
guild, _ := GetGuildInfoByCharacterId(s, s.charID)
|
||||
data, err := s.server.db.Queryx("SELECT id, destination, charge, depart, return, collected_by FROM guild_adventures WHERE guild_id = $1", guild.ID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get guild adventures from db", zap.Error(err))
|
||||
s.logger.Error("Failed to get guild adventures from db", zap.Error(err))
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
||||
return
|
||||
}
|
||||
temp := byteframe.NewByteFrame()
|
||||
count := 0
|
||||
@@ -32,7 +34,7 @@ func handleMsgMhfLoadGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
|
||||
adventureData := &GuildAdventure{}
|
||||
err = data.StructScan(&adventureData)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to scan adventure data", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
temp.WriteUint32(adventureData.ID)
|
||||
temp.WriteUint32(adventureData.Destination)
|
||||
@@ -52,7 +54,7 @@ func handleMsgMhfRegistGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
|
||||
guild, _ := GetGuildInfoByCharacterId(s, s.charID)
|
||||
_, err := s.server.db.Exec("INSERT INTO guild_adventures (guild_id, destination, depart, return) VALUES ($1, $2, $3, $4)", guild.ID, pkt.Destination, Time_Current_Adjusted().Unix(), Time_Current_Adjusted().Add(6*time.Hour).Unix())
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to register guild adventure", zap.Error(err))
|
||||
s.logger.Error("Failed to register guild adventure", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
@@ -62,12 +64,12 @@ func handleMsgMhfAcquireGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
|
||||
var collectedBy string
|
||||
err := s.server.db.QueryRow("SELECT collected_by FROM guild_adventures WHERE id = $1", pkt.ID).Scan(&collectedBy)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Error parsing adventure collected by", zap.Error(err))
|
||||
s.logger.Error("Error parsing adventure collected by", zap.Error(err))
|
||||
} else {
|
||||
collectedBy = stringsupport.CSVAdd(collectedBy, int(s.charID))
|
||||
_, err := s.server.db.Exec("UPDATE guild_adventures SET collected_by = $1 WHERE id = $2", collectedBy, pkt.ID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to collect adventure in db", zap.Error(err))
|
||||
s.logger.Error("Failed to collect adventure in db", zap.Error(err))
|
||||
}
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
@@ -77,7 +79,7 @@ func handleMsgMhfChargeGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfChargeGuildAdventure)
|
||||
_, err := s.server.db.Exec("UPDATE guild_adventures SET charge = charge + $1 WHERE id = $2", pkt.Amount, pkt.ID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to charge guild adventure", zap.Error(err))
|
||||
s.logger.Error("Failed to charge guild adventure", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
@@ -87,7 +89,7 @@ func handleMsgMhfRegistGuildAdventureDiva(s *Session, p mhfpacket.MHFPacket) {
|
||||
guild, _ := GetGuildInfoByCharacterId(s, s.charID)
|
||||
_, err := s.server.db.Exec("INSERT INTO guild_adventures (guild_id, destination, charge, depart, return) VALUES ($1, $2, $3, $4, $5)", guild.ID, pkt.Destination, pkt.Charge, Time_Current_Adjusted().Unix(), Time_Current_Adjusted().Add(1*time.Hour).Unix())
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to register guild adventure", zap.Error(err))
|
||||
s.logger.Error("Failed to register guild adventure", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
@@ -73,7 +73,8 @@ func buildAllianceObjectFromDbResult(result *sqlx.Rows, err error, s *Session) (
|
||||
|
||||
parentGuild, err := GetGuildInfoByID(s, alliance.ParentGuildID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get parent guild info", zap.Error(err))
|
||||
s.logger.Error("Failed to get parent guild info", zap.Error(err))
|
||||
return nil, err
|
||||
} else {
|
||||
alliance.ParentGuild = *parentGuild
|
||||
alliance.TotalMembers += parentGuild.MemberCount
|
||||
@@ -82,7 +83,8 @@ func buildAllianceObjectFromDbResult(result *sqlx.Rows, err error, s *Session) (
|
||||
if alliance.SubGuild1ID > 0 {
|
||||
subGuild1, err := GetGuildInfoByID(s, alliance.SubGuild1ID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get sub guild 1 info", zap.Error(err))
|
||||
s.logger.Error("Failed to get sub guild 1 info", zap.Error(err))
|
||||
return nil, err
|
||||
} else {
|
||||
alliance.SubGuild1 = *subGuild1
|
||||
alliance.TotalMembers += subGuild1.MemberCount
|
||||
@@ -92,7 +94,8 @@ func buildAllianceObjectFromDbResult(result *sqlx.Rows, err error, s *Session) (
|
||||
if alliance.SubGuild2ID > 0 {
|
||||
subGuild2, err := GetGuildInfoByID(s, alliance.SubGuild2ID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get sub guild 2 info", zap.Error(err))
|
||||
s.logger.Error("Failed to get sub guild 2 info", zap.Error(err))
|
||||
return nil, err
|
||||
} else {
|
||||
alliance.SubGuild2 = *subGuild2
|
||||
alliance.TotalMembers += subGuild2.MemberCount
|
||||
@@ -106,7 +109,7 @@ func handleMsgMhfCreateJoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfCreateJoint)
|
||||
_, err := s.server.db.Exec("INSERT INTO guild_alliances (name, parent_id) VALUES ($1, $2)", pkt.Name, pkt.GuildID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to create guild alliance in db", zap.Error(err))
|
||||
s.logger.Error("Failed to create guild alliance in db", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x01, 0x01, 0x01, 0x01})
|
||||
}
|
||||
@@ -116,11 +119,11 @@ func handleMsgMhfOperateJoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
guild, err := GetGuildInfoByID(s, pkt.GuildID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get guild info", zap.Error(err))
|
||||
s.logger.Error("Failed to get guild info", zap.Error(err))
|
||||
}
|
||||
alliance, err := GetAllianceData(s, pkt.AllianceID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get alliance info", zap.Error(err))
|
||||
s.logger.Error("Failed to get alliance info", zap.Error(err))
|
||||
}
|
||||
|
||||
switch pkt.Action {
|
||||
@@ -128,7 +131,7 @@ func handleMsgMhfOperateJoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
if guild.LeaderCharID == s.charID && alliance.ParentGuildID == guild.ID {
|
||||
_, err = s.server.db.Exec("DELETE FROM guild_alliances WHERE id=$1", alliance.ID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to disband alliance", zap.Error(err))
|
||||
s.logger.Error("Failed to disband alliance", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
} else {
|
||||
|
||||
@@ -13,6 +13,8 @@ type GuildMember struct {
|
||||
CharID uint32 `db:"character_id"`
|
||||
JoinedAt *time.Time `db:"joined_at"`
|
||||
Souls uint32 `db:"souls"`
|
||||
RPToday uint16 `db:"rp_today"`
|
||||
RPYesterday uint16 `db:"rp_yesterday"`
|
||||
Name string `db:"name"`
|
||||
IsApplicant bool `db:"is_applicant"`
|
||||
OrderIndex uint8 `db:"order_index"`
|
||||
@@ -63,6 +65,8 @@ SELECT
|
||||
g.id as guild_id,
|
||||
joined_at,
|
||||
coalesce(souls, 0) as souls,
|
||||
rp_today,
|
||||
rp_yesterday,
|
||||
c.name,
|
||||
character.character_id,
|
||||
coalesce(gc.order_index, 0) as order_index,
|
||||
|
||||
@@ -124,7 +124,10 @@ func treasureHuntUnregister(s *Session) {
|
||||
}
|
||||
var huntID int
|
||||
var hunters string
|
||||
rows, _ := s.server.db.Queryx("SELECT id, hunters FROM guild_hunts WHERE guild_id=$1", guild.ID)
|
||||
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))
|
||||
|
||||
@@ -246,18 +246,12 @@ func handleMsgMhfLoadDecoMyset(s *Session, p mhfpacket.MHFPacket) {
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT decomyset FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get preset decorations savedata from db", zap.Error(err))
|
||||
s.logger.Error("Failed to load decomyset", zap.Error(err))
|
||||
}
|
||||
|
||||
if len(data) > 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
//doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
// set first byte to 1 to avoid pop up every time without save
|
||||
body := make([]byte, 0x226)
|
||||
body[0] = 1
|
||||
doAckBufSucceed(s, pkt.AckHandle, body)
|
||||
if len(data) == 0 {
|
||||
data = []byte{0x01, 0x00}
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfSaveDecoMyset(s *Session, p mhfpacket.MHFPacket) {
|
||||
@@ -267,7 +261,7 @@ func handleMsgMhfSaveDecoMyset(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload[1:]) // skip first unk byte
|
||||
err := s.server.db.QueryRow("SELECT decomyset FROM characters WHERE id = $1", s.charID).Scan(&loadData)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get preset decorations savedata from db", zap.Error(err))
|
||||
s.logger.Error("Failed to load decomyset", zap.Error(err))
|
||||
} else {
|
||||
numSets := bf.ReadUint8() // sets being written
|
||||
// empty save
|
||||
@@ -313,7 +307,7 @@ func handleMsgMhfSaveDecoMyset(s *Session, p mhfpacket.MHFPacket) {
|
||||
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.Fatal("Failed to update decomyset savedata in db", zap.Error(err))
|
||||
s.logger.Error("Failed to save decomyset", zap.Error(err))
|
||||
}
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
|
||||
@@ -12,7 +12,7 @@ func handleMsgMhfAddKouryouPoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
var points int
|
||||
err := s.server.db.QueryRow("UPDATE characters SET kouryou_point=COALESCE(kouryou_point + $1, $1) WHERE id=$2 RETURNING kouryou_point", pkt.KouryouPoints, s.charID).Scan(&points)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update KouryouPoint in db", zap.Error(err))
|
||||
s.logger.Error("Failed to update KouryouPoint in db", zap.Error(err))
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(uint32(points))
|
||||
@@ -24,7 +24,7 @@ func handleMsgMhfGetKouryouPoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
var points int
|
||||
err := s.server.db.QueryRow("SELECT COALESCE(kouryou_point, 0) FROM characters WHERE id = $1", s.charID).Scan(&points)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get kouryou_point savedata from db", zap.Error(err))
|
||||
s.logger.Error("Failed to get kouryou_point savedata from db", zap.Error(err))
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(uint32(points))
|
||||
@@ -37,7 +37,7 @@ func handleMsgMhfExchangeKouryouPoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfExchangeKouryouPoint)
|
||||
err := s.server.db.QueryRow("UPDATE characters SET kouryou_point=kouryou_point - $1 WHERE id=$2 RETURNING kouryou_point", pkt.KouryouPoints, s.charID).Scan(&points)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update platemyset savedata in db", zap.Error(err))
|
||||
s.logger.Error("Failed to update platemyset savedata in db", zap.Error(err))
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(uint32(points))
|
||||
|
||||
@@ -392,24 +392,29 @@ func handleMsgMhfSendMail(s *Session, p mhfpacket.MHFPacket) {
|
||||
if pkt.RecipientID == 0 { // Guild mail
|
||||
g, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get guild info for mail")
|
||||
s.logger.Error("Failed to get guild info for mail")
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
gm, err := GetGuildMembers(s, g.ID, false)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get guild members for mail")
|
||||
s.logger.Error("Failed to get guild members for mail")
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
for i := 0; i < len(gm); i++ {
|
||||
_, err := s.server.db.Exec(query, s.charID, gm[i].CharID, pkt.Subject, pkt.Body, 0, 0, false)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to send mail")
|
||||
s.logger.Error("Failed to send mail")
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_, err := s.server.db.Exec(query, s.charID, pkt.RecipientID, pkt.Subject, pkt.Body, pkt.ItemID, pkt.Quantity, false)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to send mail")
|
||||
s.logger.Error("Failed to send mail")
|
||||
}
|
||||
}
|
||||
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
@@ -11,115 +11,96 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
// THERE ARE [PARTENER] [MERCENARY] [OTOMO AIRU]
|
||||
|
||||
///////////////////////////////////////////
|
||||
/// PARTENER //
|
||||
///////////////////////////////////////////
|
||||
|
||||
func handleMsgMhfLoadPartner(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadPartner)
|
||||
// load partner from database
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT partner FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get partner savedata from db", zap.Error(err))
|
||||
if len(data) == 0 {
|
||||
s.logger.Error("Failed to load partner", zap.Error(err))
|
||||
data = make([]byte, 9)
|
||||
}
|
||||
if len(data) > 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
// TODO(Andoryuuta): Figure out unusual double ack. One sized, one not.
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfSavePartner(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSavePartner)
|
||||
|
||||
dumpSaveData(s, pkt.RawDataPayload, "partner")
|
||||
|
||||
_, err := s.server.db.Exec("UPDATE characters SET partner=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update partner savedata in db", zap.Error(err))
|
||||
s.logger.Error("Failed to save partner", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfLoadLegendDispatch(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadLegendDispatch)
|
||||
data := []byte{0x03, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x01, 0x8d, 0x40, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x02, 0xde, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x04, 0x30, 0x40}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
bf := byteframe.NewByteFrame()
|
||||
legendDispatch := []struct {
|
||||
Unk uint32
|
||||
Timestamp uint32
|
||||
}{
|
||||
{0, uint32(Time_Current_Midnight().Add(-12 * time.Hour).Unix())},
|
||||
{0, uint32(Time_Current_Midnight().Add(12 * time.Hour).Unix())},
|
||||
{0, uint32(Time_Current_Midnight().Add(36 * time.Hour).Unix())},
|
||||
}
|
||||
bf.WriteUint8(uint8(len(legendDispatch)))
|
||||
for _, dispatch := range legendDispatch {
|
||||
bf.WriteUint32(dispatch.Unk)
|
||||
bf.WriteUint32(dispatch.Timestamp)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfLoadHunterNavi(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadHunterNavi)
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT hunternavi FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get hunter navigation savedata from db", zap.Error(err))
|
||||
}
|
||||
|
||||
if len(data) > 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
// set first byte to 1 to avoid pop up every time without save
|
||||
body := make([]byte, 0x226)
|
||||
body[0] = 1
|
||||
doAckBufSucceed(s, pkt.AckHandle, body)
|
||||
if len(data) == 0 {
|
||||
s.logger.Error("Failed to load hunternavi", zap.Error(err))
|
||||
data = make([]byte, 0x226)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfSaveHunterNavi(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSaveHunterNavi)
|
||||
|
||||
dumpSaveData(s, pkt.RawDataPayload, "hunternavi")
|
||||
|
||||
if pkt.IsDataDiff {
|
||||
var data []byte
|
||||
|
||||
// Load existing save
|
||||
err := s.server.db.QueryRow("SELECT hunternavi FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get hunternavi savedata from db", zap.Error(err))
|
||||
s.logger.Error("Failed to load hunternavi", zap.Error(err))
|
||||
}
|
||||
|
||||
// Check if we actually had any hunternavi data, using a blank buffer if not.
|
||||
// This is requried as the client will try to send a diff after character creation without a prior MsgMhfSaveHunterNavi packet.
|
||||
if len(data) == 0 {
|
||||
data = make([]byte, 0x226)
|
||||
data[0] = 1 // set first byte to 1 to avoid pop up every time without save
|
||||
}
|
||||
|
||||
// Perform diff and compress it to write back to db
|
||||
s.logger.Info("Diffing...")
|
||||
saveOutput := deltacomp.ApplyDataDiff(pkt.RawDataPayload, data)
|
||||
|
||||
_, err = s.server.db.Exec("UPDATE characters SET hunternavi=$1 WHERE id=$2", saveOutput, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update hunternavi savedata in db", zap.Error(err))
|
||||
s.logger.Error("Failed to save hunternavi", zap.Error(err))
|
||||
}
|
||||
|
||||
s.logger.Info("Wrote recompressed hunternavi back to DB.")
|
||||
s.logger.Info("Wrote recompressed hunternavi back to DB")
|
||||
} else {
|
||||
dumpSaveData(s, pkt.RawDataPayload, "hunternavi")
|
||||
// simply update database, no extra processing
|
||||
_, err := s.server.db.Exec("UPDATE characters SET hunternavi=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update hunternavi savedata in db", zap.Error(err))
|
||||
s.logger.Error("Failed to save hunternavi", zap.Error(err))
|
||||
}
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
///////////////////////////////////////////
|
||||
|
||||
///////////////////////////////////////////
|
||||
/// MERCENARY //
|
||||
///////////////////////////////////////////
|
||||
|
||||
func handleMsgMhfMercenaryHuntdata(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfMercenaryHuntdata)
|
||||
if pkt.Unk0 == 1 {
|
||||
@@ -149,15 +130,11 @@ func handleMsgMhfEnumerateMercenaryLog(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgMhfCreateMercenary(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfCreateMercenary)
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
var nextID uint32
|
||||
s.server.db.QueryRow("SELECT nextval('rasta_id_seq')").Scan(&nextID)
|
||||
|
||||
bf.WriteUint32(nextID) // New MercID
|
||||
bf.WriteUint32(0xDEADBEEF) // Unk
|
||||
|
||||
_ = s.server.db.QueryRow("SELECT nextval('rasta_id_seq')").Scan(&nextID)
|
||||
s.server.db.Exec("UPDATE characters SET rasta_id=$1 WHERE id=$2", nextID, s.charID)
|
||||
bf.WriteUint32(nextID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
@@ -165,29 +142,66 @@ func handleMsgMhfSaveMercenary(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSaveMercenary)
|
||||
dumpSaveData(s, pkt.MercData, "mercenary")
|
||||
if len(pkt.MercData) > 0 {
|
||||
s.server.db.Exec("UPDATE characters SET savemercenary=$1 WHERE id=$2", pkt.MercData, s.charID)
|
||||
temp := byteframe.NewByteFrameFromBytes(pkt.MercData)
|
||||
s.server.db.Exec("UPDATE characters SET savemercenary=$1, rasta_id=$2 WHERE id=$3", pkt.MercData, temp.ReadUint32(), s.charID)
|
||||
}
|
||||
s.server.db.Exec("UPDATE characters SET gcp=$1 WHERE id=$2", pkt.GCP, s.charID)
|
||||
s.server.db.Exec("UPDATE characters SET gcp=$1, pact_id=$2 WHERE id=$3", pkt.GCP, pkt.PactMercID, s.charID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReadMercenaryW)
|
||||
if pkt.Unk0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 2))
|
||||
if pkt.Op > 0 {
|
||||
bf := byteframe.NewByteFrame()
|
||||
var pactID uint32
|
||||
var name string
|
||||
var cid uint32
|
||||
|
||||
s.server.db.QueryRow("SELECT pact_id FROM characters WHERE id=$1", s.charID).Scan(&pactID)
|
||||
if pactID > 0 {
|
||||
s.server.db.QueryRow("SELECT name, id FROM characters WHERE rasta_id = $1", pactID).Scan(&name, &cid)
|
||||
bf.WriteUint8(1) // numLends
|
||||
bf.WriteUint32(pactID)
|
||||
bf.WriteUint32(cid)
|
||||
bf.WriteBool(false) // ?
|
||||
bf.WriteUint32(uint32(Time_Current_Adjusted().Add(time.Hour * 24 * -8).Unix()))
|
||||
bf.WriteUint32(uint32(Time_Current_Adjusted().Add(time.Hour * 24 * -1).Unix()))
|
||||
bf.WriteBytes(stringsupport.PaddedString(name, 18, true))
|
||||
} else {
|
||||
bf.WriteUint8(0)
|
||||
}
|
||||
|
||||
if pkt.Op < 2 {
|
||||
var loans uint8
|
||||
temp := byteframe.NewByteFrame()
|
||||
rows, _ := s.server.db.Query("SELECT name, id, pact_id FROM characters WHERE pact_id=(SELECT rasta_id FROM characters WHERE id=$1)", s.charID)
|
||||
for rows.Next() {
|
||||
loans++
|
||||
rows.Scan(&name, &cid, &pactID)
|
||||
temp.WriteUint32(pactID)
|
||||
temp.WriteUint32(cid)
|
||||
temp.WriteUint32(uint32(Time_Current_Adjusted().Add(time.Hour * 24 * -8).Unix()))
|
||||
temp.WriteUint32(uint32(Time_Current_Adjusted().Add(time.Hour * 24 * -1).Unix()))
|
||||
temp.WriteBytes(stringsupport.PaddedString(name, 18, true))
|
||||
}
|
||||
bf.WriteUint8(loans)
|
||||
bf.WriteBytes(temp.Data())
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
return
|
||||
}
|
||||
var data []byte
|
||||
var gcp uint32
|
||||
s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
s.server.db.QueryRow("SELECT COALESCE(gcp, 0) FROM characters WHERE id = $1", s.charID).Scan(&gcp)
|
||||
s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id=$1", s.charID).Scan(&data)
|
||||
s.server.db.QueryRow("SELECT COALESCE(gcp, 0) FROM characters WHERE id=$1", s.charID).Scan(&gcp)
|
||||
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint16(0)
|
||||
if len(data) == 0 {
|
||||
resp.WriteBytes(make([]byte, 3))
|
||||
resp.WriteBool(false)
|
||||
} else {
|
||||
resp.WriteBytes(data[1:])
|
||||
resp.WriteUint32(0) // Unk
|
||||
resp.WriteBool(true)
|
||||
resp.WriteBytes(data)
|
||||
}
|
||||
resp.WriteUint32(gcp)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
@@ -201,31 +215,33 @@ func handleMsgMhfReadMercenaryM(s *Session, p mhfpacket.MHFPacket) {
|
||||
if len(data) == 0 {
|
||||
resp.WriteBool(false)
|
||||
} else {
|
||||
resp.WriteBytes(data[4:])
|
||||
resp.WriteBytes(data)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfContractMercenary(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
///////////////////////////////////////////
|
||||
|
||||
///////////////////////////////////////////
|
||||
/// OTOMO AIRU //
|
||||
///////////////////////////////////////////
|
||||
func handleMsgMhfContractMercenary(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfContractMercenary)
|
||||
switch pkt.Op {
|
||||
case 0:
|
||||
s.server.db.Exec("UPDATE characters SET pact_id=$1 WHERE id=$2", pkt.PactMercID, s.charID)
|
||||
case 1: // Cancel lend
|
||||
s.server.db.Exec("UPDATE characters SET pact_id=0 WHERE id=$1", s.charID)
|
||||
case 2: // Cancel loan
|
||||
s.server.db.Exec("UPDATE characters SET pact_id=0 WHERE id=$1", pkt.CID)
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfLoadOtomoAirou(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadOtomoAirou)
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT otomoairou FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get partnyaa savedata from db", zap.Error(err))
|
||||
}
|
||||
if len(data) > 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
if len(data) == 0 {
|
||||
s.logger.Error("Failed to load otomoairou", zap.Error(err))
|
||||
data = make([]byte, 10)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfSaveOtomoAirou(s *Session, p mhfpacket.MHFPacket) {
|
||||
@@ -411,5 +427,3 @@ func GetCatDetails(bf *byteframe.ByteFrame) []CatDefinition {
|
||||
}
|
||||
return cats
|
||||
}
|
||||
|
||||
///////////////////////////////////////////
|
||||
|
||||
@@ -12,28 +12,23 @@ func handleMsgMhfLoadPlateData(s *Session, p mhfpacket.MHFPacket) {
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT platedata FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get plate data savedata from db", zap.Error(err))
|
||||
}
|
||||
|
||||
if len(data) > 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
s.logger.Error("Failed to load platedata", zap.Error(err))
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSavePlateData)
|
||||
|
||||
dumpSaveData(s, pkt.RawDataPayload, "platedata")
|
||||
|
||||
if pkt.IsDataDiff {
|
||||
var data []byte
|
||||
|
||||
// Load existing save
|
||||
err := s.server.db.QueryRow("SELECT platedata FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get platedata savedata from db", zap.Error(err))
|
||||
s.logger.Error("Failed to load platedata", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
return
|
||||
}
|
||||
|
||||
if len(data) > 0 {
|
||||
@@ -41,7 +36,9 @@ func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) {
|
||||
s.logger.Info("Decompressing...")
|
||||
data, err = nullcomp.Decompress(data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to decompress savedata from db", zap.Error(err))
|
||||
s.logger.Error("Failed to decompress platedata", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// create empty save if absent
|
||||
@@ -52,20 +49,25 @@ func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) {
|
||||
s.logger.Info("Diffing...")
|
||||
saveOutput, err := nullcomp.Compress(deltacomp.ApplyDataDiff(pkt.RawDataPayload, data))
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to diff and compress platedata savedata", zap.Error(err))
|
||||
s.logger.Error("Failed to diff and compress platedata", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
return
|
||||
}
|
||||
|
||||
_, err = s.server.db.Exec("UPDATE characters SET platedata=$1 WHERE id=$2", saveOutput, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update platedata savedata in db", zap.Error(err))
|
||||
s.logger.Error("Failed to save platedata", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Info("Wrote recompressed platedata back to DB.")
|
||||
s.logger.Info("Wrote recompressed platedata back to DB")
|
||||
} else {
|
||||
dumpSaveData(s, pkt.RawDataPayload, "platedata")
|
||||
// simply update database, no extra processing
|
||||
_, err := s.server.db.Exec("UPDATE characters SET platedata=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update platedata savedata in db", zap.Error(err))
|
||||
s.logger.Error("Failed to save platedata", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,28 +79,23 @@ func handleMsgMhfLoadPlateBox(s *Session, p mhfpacket.MHFPacket) {
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT platebox FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get sigil box savedata from db", zap.Error(err))
|
||||
}
|
||||
|
||||
if len(data) > 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
s.logger.Error("Failed to load platebox", zap.Error(err))
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSavePlateBox)
|
||||
|
||||
dumpSaveData(s, pkt.RawDataPayload, "platebox")
|
||||
|
||||
if pkt.IsDataDiff {
|
||||
var data []byte
|
||||
|
||||
// Load existing save
|
||||
err := s.server.db.QueryRow("SELECT platebox FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get sigil box savedata from db", zap.Error(err))
|
||||
s.logger.Error("Failed to load platebox", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
return
|
||||
}
|
||||
|
||||
// Decompress
|
||||
@@ -107,7 +104,9 @@ func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) {
|
||||
s.logger.Info("Decompressing...")
|
||||
data, err = nullcomp.Decompress(data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to decompress savedata from db", zap.Error(err))
|
||||
s.logger.Error("Failed to decompress platebox", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// create empty save if absent
|
||||
@@ -118,20 +117,25 @@ func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) {
|
||||
s.logger.Info("Diffing...")
|
||||
saveOutput, err := nullcomp.Compress(deltacomp.ApplyDataDiff(pkt.RawDataPayload, data))
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to diff and compress savedata", zap.Error(err))
|
||||
s.logger.Error("Failed to diff and compress platebox", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
return
|
||||
}
|
||||
|
||||
_, err = s.server.db.Exec("UPDATE characters SET platebox=$1 WHERE id=$2", saveOutput, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update platebox savedata in db", zap.Error(err))
|
||||
s.logger.Error("Failed to save platebox", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Info("Wrote recompressed platebox back to DB.")
|
||||
s.logger.Info("Wrote recompressed platebox back to DB")
|
||||
} else {
|
||||
dumpSaveData(s, pkt.RawDataPayload, "platebox")
|
||||
// simply update database, no extra processing
|
||||
_, err := s.server.db.Exec("UPDATE characters SET platebox=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update platedata savedata in db", zap.Error(err))
|
||||
s.logger.Error("Failed to save platebox", zap.Error(err))
|
||||
}
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
@@ -141,16 +145,11 @@ func handleMsgMhfLoadPlateMyset(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadPlateMyset)
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT platemyset FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get presets sigil savedata from db", zap.Error(err))
|
||||
}
|
||||
|
||||
if len(data) > 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
blankData := make([]byte, 0x780)
|
||||
doAckBufSucceed(s, pkt.AckHandle, blankData)
|
||||
if len(data) == 0 {
|
||||
s.logger.Error("Failed to load platemyset", zap.Error(err))
|
||||
data = make([]byte, 0x780)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfSavePlateMyset(s *Session, p mhfpacket.MHFPacket) {
|
||||
@@ -159,7 +158,7 @@ func handleMsgMhfSavePlateMyset(s *Session, p mhfpacket.MHFPacket) {
|
||||
dumpSaveData(s, pkt.RawDataPayload, "platemyset")
|
||||
_, err := s.server.db.Exec("UPDATE characters SET platemyset=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update platemyset savedata in db", zap.Error(err))
|
||||
s.logger.Error("Failed to save platemyset", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -21,7 +21,7 @@ func handleMsgSysOperateRegister(s *Session, p mhfpacket.MHFPacket) {
|
||||
resp.WriteUint8(1)
|
||||
resp.WriteUint8(dest)
|
||||
ref := &s.server.raviente.state.stateData[dest]
|
||||
damageMultiplier := s.server.raviente.state.damageMultiplier
|
||||
damageMultiplier := s.server.raviente.GetRaviMultiplier(s.server)
|
||||
switch op {
|
||||
case 2:
|
||||
resp.WriteUint32(*ref)
|
||||
@@ -252,21 +252,21 @@ func (s *Session) notifyRavi() {
|
||||
raviNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(raviNotif, s.clientContext)
|
||||
raviNotif.WriteUint16(0x0010) // End it.
|
||||
sema := getRaviSemaphore(s)
|
||||
if sema != "" {
|
||||
for session := range s.server.semaphore[sema].clients {
|
||||
sema := getRaviSemaphore(s.server)
|
||||
if sema != nil {
|
||||
for session := range sema.clients {
|
||||
session.QueueSend(raviNotif.Data())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getRaviSemaphore(s *Session) string {
|
||||
for _, semaphore := range s.server.semaphore {
|
||||
if strings.HasPrefix(semaphore.id_semaphore, "hs_l0u3B5") && strings.HasSuffix(semaphore.id_semaphore, "4") {
|
||||
return semaphore.id_semaphore
|
||||
func getRaviSemaphore(s *Server) *Semaphore {
|
||||
for _, semaphore := range s.semaphore {
|
||||
if strings.HasPrefix(semaphore.id_semaphore, "hs_l0u3B5") && strings.HasSuffix(semaphore.id_semaphore, "3") {
|
||||
return semaphore
|
||||
}
|
||||
}
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func resetRavi(s *Session) {
|
||||
@@ -278,7 +278,6 @@ func resetRavi(s *Session) {
|
||||
s.server.raviente.register.ravienteType = 0
|
||||
s.server.raviente.register.maxPlayers = 0
|
||||
s.server.raviente.register.carveQuest = 0
|
||||
s.server.raviente.state.damageMultiplier = 1
|
||||
s.server.raviente.register.register = []uint32{0, 0, 0, 0, 0}
|
||||
s.server.raviente.state.stateData = []uint32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
s.server.raviente.support.supportData = []uint32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
|
||||
@@ -19,7 +19,9 @@ func handleMsgMhfSaveRengokuData(s *Session, p mhfpacket.MHFPacket) {
|
||||
dumpSaveData(s, pkt.RawDataPayload, "rengoku")
|
||||
_, err := s.server.db.Exec("UPDATE characters SET rengokudata=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update rengokudata savedata in db", zap.Error(err))
|
||||
s.logger.Error("Failed to save rengokudata", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
|
||||
bf.Seek(71, 0)
|
||||
@@ -34,7 +36,7 @@ func handleMsgMhfSaveRengokuData(s *Session, p mhfpacket.MHFPacket) {
|
||||
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})
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfLoadRengokuData(s *Session, p mhfpacket.MHFPacket) {
|
||||
@@ -42,7 +44,7 @@ func handleMsgMhfLoadRengokuData(s *Session, p mhfpacket.MHFPacket) {
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT rengokudata FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get rengokudata savedata from db", zap.Error(err))
|
||||
s.logger.Error("Failed to load rengokudata", zap.Error(err))
|
||||
}
|
||||
if len(data) > 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
|
||||
@@ -49,7 +49,3 @@ func handleMsgMhfAcquireMonthlyReward(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
|
||||
func handleMsgMhfAcceptReadReward(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfGetBreakSeibatuLevelReward(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfGetWeeklySeibatuRankingReward(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
@@ -2,14 +2,10 @@ package channelserver
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
"time"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"github.com/lib/pq"
|
||||
"github.com/sachaos/lottery"
|
||||
"go.uber.org/zap"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
type ShopItem struct {
|
||||
@@ -28,16 +24,36 @@ type ShopItem struct {
|
||||
}
|
||||
|
||||
type Gacha struct {
|
||||
ID uint32 `db:"id"`
|
||||
MinGR uint32 `db:"min_gr"`
|
||||
MinHR uint32 `db:"min_hr"`
|
||||
Name string `db:"name"`
|
||||
Link1 string `db:"link1"`
|
||||
Link2 string `db:"link2"`
|
||||
Link3 string `db:"link3"`
|
||||
Icon uint16 `db:"icon"`
|
||||
Type uint16 `db:"type"`
|
||||
Hide bool `db:"hide"`
|
||||
ID uint32 `db:"id"`
|
||||
MinGR uint32 `db:"min_gr"`
|
||||
MinHR uint32 `db:"min_hr"`
|
||||
Name string `db:"name"`
|
||||
URLBanner string `db:"url_banner"`
|
||||
URLFeature string `db:"url_feature"`
|
||||
URLThumbnail string `db:"url_thumbnail"`
|
||||
Wide bool `db:"wide"`
|
||||
Recommended bool `db:"recommended"`
|
||||
GachaType uint8 `db:"gacha_type"`
|
||||
Hidden bool `db:"hidden"`
|
||||
}
|
||||
|
||||
type GachaEntry struct {
|
||||
EntryType uint8 `db:"entry_type"`
|
||||
ID uint32 `db:"id"`
|
||||
ItemType uint8 `db:"item_type"`
|
||||
ItemNumber uint16 `db:"item_number"`
|
||||
ItemQuantity uint16 `db:"item_quantity"`
|
||||
Weight float64 `db:"weight"`
|
||||
Rarity uint8 `db:"rarity"`
|
||||
Rolls uint8 `db:"rolls"`
|
||||
FrontierPoints uint16 `db:"frontier_points"`
|
||||
DailyLimit uint8 `db:"daily_limit"`
|
||||
}
|
||||
|
||||
type GachaItem struct {
|
||||
ItemType uint8 `db:"item_type"`
|
||||
ItemID uint16 `db:"item_id"`
|
||||
Quantity uint16 `db:"quantity"`
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
|
||||
@@ -55,7 +71,7 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
|
||||
switch pkt.ShopType {
|
||||
case 1: // Running gachas
|
||||
var count uint16
|
||||
shopEntries, err := s.server.db.Queryx("SELECT id, min_gr, min_hr, name, link1, link2, link3, icon, type, hide FROM gacha_shop")
|
||||
shopEntries, err := s.server.db.Queryx("SELECT id, min_gr, min_hr, name, url_banner, url_feature, url_thumbnail, wide, recommended, gacha_type, hidden FROM gacha_shop")
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
@@ -74,12 +90,18 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
|
||||
resp.WriteUint32(gacha.MinHR)
|
||||
resp.WriteUint32(0) // only 0 in known packet
|
||||
ps.Uint8(resp, gacha.Name, true)
|
||||
ps.Uint8(resp, gacha.Link1, false)
|
||||
ps.Uint8(resp, gacha.Link2, false)
|
||||
resp.WriteBool(gacha.Hide)
|
||||
ps.Uint8(resp, gacha.Link3, false)
|
||||
resp.WriteUint16(gacha.Icon)
|
||||
resp.WriteUint16(gacha.Type)
|
||||
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)
|
||||
@@ -87,47 +109,62 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
|
||||
resp.WriteUint16(count)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
case 2: // Actual gacha
|
||||
shopEntries, err := s.server.db.Query("SELECT entryType, itemhash, currType, currNumber, currQuant, percentage, rarityIcon, rollsCount, itemCount, dailyLimit, itemType, itemId, quantity FROM gacha_shop_items WHERE shophash=$1", pkt.ShopID)
|
||||
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 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 entryType, currType, rarityIcon, rollsCount, itemCount, dailyLimit uint8
|
||||
var currQuant, currNumber, percentage uint16
|
||||
var itemhash uint32
|
||||
var itemType, itemId, quantity pq.Int64Array
|
||||
var count uint16
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(pkt.ShopID)
|
||||
resp.WriteUint16(0) // total defs
|
||||
for shopEntries.Next() {
|
||||
err = shopEntries.Scan(&entryType, &itemhash, &currType, &currNumber, &currQuant, &percentage, &rarityIcon, &rollsCount, &itemCount, &dailyLimit, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity))
|
||||
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.WriteUint16(0)
|
||||
bf.WriteUint16(gachaEntry.ItemNumber)
|
||||
bf.WriteUint16(gachaEntry.ItemQuantity)
|
||||
if gachaType >= 4 { // If box
|
||||
bf.WriteUint16(1)
|
||||
} else {
|
||||
bf.WriteUint16(uint16(gachaEntry.Weight / divisor))
|
||||
}
|
||||
bf.WriteUint8(gachaEntry.Rarity)
|
||||
bf.WriteUint8(gachaEntry.Rolls)
|
||||
|
||||
var itemCount uint8
|
||||
temp := byteframe.NewByteFrame()
|
||||
items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id=$1`, gachaEntry.ID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
bf.WriteUint8(0)
|
||||
} else {
|
||||
for items.Next() {
|
||||
itemCount++
|
||||
items.StructScan(&gachaItem)
|
||||
temp.WriteUint16(uint16(gachaItem.ItemType))
|
||||
temp.WriteUint16(gachaItem.ItemID)
|
||||
temp.WriteUint16(gachaItem.Quantity)
|
||||
}
|
||||
bf.WriteUint8(itemCount)
|
||||
}
|
||||
resp.WriteUint8(entryType)
|
||||
resp.WriteUint32(itemhash)
|
||||
resp.WriteUint8(currType)
|
||||
resp.WriteUint16(0) // unk, always 0 in existing packets
|
||||
resp.WriteUint16(currNumber) // it's either item ID or quantity for gacha coins
|
||||
resp.WriteUint16(currQuant) // only for item ID
|
||||
resp.WriteUint16(percentage)
|
||||
resp.WriteUint8(rarityIcon)
|
||||
resp.WriteUint8(rollsCount)
|
||||
resp.WriteUint8(itemCount)
|
||||
resp.WriteUint16(0) // unk, always 0 in existing packets
|
||||
resp.WriteUint8(dailyLimit)
|
||||
resp.WriteUint8(0) // unk, always 0 in existing packets
|
||||
for i := 0; i < int(itemCount); i++ {
|
||||
resp.WriteUint16(uint16(itemType[i])) // unk, always 0 in existing packets
|
||||
resp.WriteUint16(uint16(itemId[i])) // unk, always 0 in existing packets
|
||||
resp.WriteUint16(uint16(quantity[i])) // unk, always 0 in existing packets
|
||||
}
|
||||
count++
|
||||
|
||||
bf.WriteUint16(gachaEntry.FrontierPoints)
|
||||
bf.WriteUint8(gachaEntry.DailyLimit)
|
||||
bf.WriteUint8(0)
|
||||
bf.WriteBytes(temp.Data())
|
||||
}
|
||||
resp.Seek(4, 0)
|
||||
resp.WriteUint16(count)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
bf.Seek(4, 0)
|
||||
bf.WriteUint16(entryCount)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
case 4: // N Points, 0-6
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
case 5: // GCP->Item, 0-6
|
||||
@@ -206,124 +243,363 @@ func handleMsgMhfAcquireExchangeShop(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfGetGachaPlayHistory(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGachaPlayHistory)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(0)
|
||||
bf.WriteUint8(1)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfGetGachaPoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGachaPoint)
|
||||
var fp, gp, gt uint32
|
||||
s.server.db.QueryRow("SELECT COALESCE(frontier_points, 0), COALESCE(gacha_prem, 0), COALESCE(gacha_trial,0) FROM characters WHERE id=$1", s.charID).Scan(&fp, &gp, >)
|
||||
s.server.db.QueryRow("SELECT COALESCE(frontier_points, 0), COALESCE(gacha_premium, 0), COALESCE(gacha_trial, 0) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)", s.charID).Scan(&fp, &gp, >)
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(gp) // Real Gacha Points?
|
||||
resp.WriteUint32(gt) // Trial Gacha Point?
|
||||
resp.WriteUint32(fp) // Frontier Points?
|
||||
resp.WriteUint32(gp)
|
||||
resp.WriteUint32(gt)
|
||||
resp.WriteUint32(fp)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
type gachaItem struct {
|
||||
itemhash uint32
|
||||
percentage uint16
|
||||
rarityIcon byte
|
||||
itemCount byte
|
||||
itemType pq.Int64Array
|
||||
itemId pq.Int64Array
|
||||
quantity pq.Int64Array
|
||||
func handleMsgMhfUseGachaPoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfUseGachaPoint)
|
||||
if pkt.TrialCoins > 0 {
|
||||
s.server.db.Exec(`UPDATE users u SET gacha_trial=gacha_trial-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, pkt.TrialCoins, s.charID)
|
||||
}
|
||||
if pkt.PremiumCoins > 0 {
|
||||
s.server.db.Exec(`UPDATE users u SET gacha_premium=gacha_premium-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, pkt.PremiumCoins, s.charID)
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func (i gachaItem) Weight() int {
|
||||
return int(i.percentage)
|
||||
func spendGachaCoin(s *Session, quantity uint16) {
|
||||
var gt uint16
|
||||
s.server.db.QueryRow(`SELECT COALESCE(gacha_trial, 0) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(>)
|
||||
if quantity <= gt {
|
||||
s.server.db.Exec(`UPDATE users u SET gacha_trial=gacha_trial-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, quantity, s.charID)
|
||||
} else {
|
||||
s.server.db.Exec(`UPDATE users u SET gacha_premium=gacha_premium-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, quantity, s.charID)
|
||||
}
|
||||
}
|
||||
|
||||
func transactGacha(s *Session, gachaID uint32, rollID uint8) (error, int) {
|
||||
var itemType uint8
|
||||
var itemNumber uint16
|
||||
var rolls int
|
||||
err := s.server.db.QueryRowx(`SELECT item_type, item_number, rolls FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2`, gachaID, rollID).Scan(&itemType, &itemNumber, &rolls)
|
||||
if err != nil {
|
||||
return err, 0
|
||||
}
|
||||
switch itemType {
|
||||
/*
|
||||
valid types that need manual savedata manipulation:
|
||||
- Ryoudan Points
|
||||
- Bond Points
|
||||
- Image Change Points
|
||||
valid types that work (no additional code needed):
|
||||
- Tore Points
|
||||
- Festa Points
|
||||
*/
|
||||
case 17:
|
||||
_ = addPointNetcafe(s, int(itemNumber)*-1)
|
||||
case 19:
|
||||
fallthrough
|
||||
case 20:
|
||||
spendGachaCoin(s, itemNumber)
|
||||
case 21:
|
||||
s.server.db.Exec("UPDATE users u SET frontier_points=frontier_points-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", itemNumber, s.charID)
|
||||
}
|
||||
return nil, rolls
|
||||
}
|
||||
|
||||
func getGuaranteedItems(s *Session, gachaID uint32, rollID uint8) []GachaItem {
|
||||
var rewards []GachaItem
|
||||
var reward GachaItem
|
||||
items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = (SELECT id FROM gacha_entries WHERE entry_type = $1 AND gacha_id = $2)`, rollID, gachaID)
|
||||
if err == nil {
|
||||
for items.Next() {
|
||||
items.StructScan(&reward)
|
||||
rewards = append(rewards, reward)
|
||||
}
|
||||
}
|
||||
return rewards
|
||||
}
|
||||
|
||||
func addGachaItem(s *Session, items []GachaItem) {
|
||||
var data []byte
|
||||
s.server.db.QueryRow(`SELECT gacha_items FROM characters WHERE id = $1`, s.charID).Scan(&data)
|
||||
if len(data) > 0 {
|
||||
numItems := int(data[0])
|
||||
data = data[1:]
|
||||
oldItem := byteframe.NewByteFrameFromBytes(data)
|
||||
for i := 0; i < numItems; i++ {
|
||||
items = append(items, GachaItem{
|
||||
ItemType: oldItem.ReadUint8(),
|
||||
ItemID: oldItem.ReadUint16(),
|
||||
Quantity: oldItem.ReadUint16(),
|
||||
})
|
||||
}
|
||||
}
|
||||
newItem := byteframe.NewByteFrame()
|
||||
newItem.WriteUint8(uint8(len(items)))
|
||||
for i := range items {
|
||||
newItem.WriteUint8(items[i].ItemType)
|
||||
newItem.WriteUint16(items[i].ItemID)
|
||||
newItem.WriteUint16(items[i].Quantity)
|
||||
}
|
||||
s.server.db.Exec(`UPDATE characters SET gacha_items = $1 WHERE id = $2`, newItem.Data(), s.charID)
|
||||
}
|
||||
|
||||
func getRandomEntries(entries []GachaEntry, rolls int, isBox bool) ([]GachaEntry, error) {
|
||||
var chosen []GachaEntry
|
||||
var totalWeight float64
|
||||
for i := range entries {
|
||||
totalWeight += entries[i].Weight
|
||||
}
|
||||
for {
|
||||
if !isBox {
|
||||
result := rand.Float64() * totalWeight
|
||||
for _, entry := range entries {
|
||||
result -= entry.Weight
|
||||
if result < 0 {
|
||||
chosen = append(chosen, entry)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result := rand.Intn(len(entries))
|
||||
chosen = append(chosen, entries[result])
|
||||
entries[result] = entries[len(entries)-1]
|
||||
entries = entries[:len(entries)-1]
|
||||
}
|
||||
if rolls == len(chosen) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return chosen, nil
|
||||
}
|
||||
|
||||
func handleMsgMhfReceiveGachaItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReceiveGachaItem)
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT COALESCE(gacha_items, $2) FROM characters WHERE id = $1", s.charID, []byte{0x00}).Scan(&data)
|
||||
if err != nil {
|
||||
data = []byte{0x00}
|
||||
}
|
||||
|
||||
// I think there are still some edge cases where rewards can be nulled via overflow
|
||||
if data[0] > 36 || len(data) > 181 {
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(36)
|
||||
resp.WriteBytes(data[1:181])
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
if !pkt.Freeze {
|
||||
if data[0] > 36 || len(data) > 181 {
|
||||
update := byteframe.NewByteFrame()
|
||||
update.WriteUint8(uint8(len(data[181:]) / 5))
|
||||
update.WriteBytes(data[181:])
|
||||
s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", update.Data(), s.charID)
|
||||
} else {
|
||||
s.server.db.Exec("UPDATE characters SET gacha_items = null WHERE id = $1", s.charID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPlayNormalGacha)
|
||||
// needs to query db for input gacha and return a result or number of results
|
||||
// uint8 number of results
|
||||
// uint8 item type
|
||||
// uint16 item id
|
||||
// uint16 quantity
|
||||
|
||||
var currType, rarityIcon, rollsCount, itemCount byte
|
||||
var currQuant, currNumber, percentage uint16
|
||||
var itemhash uint32
|
||||
var itemType, itemId, quantity pq.Int64Array
|
||||
var items []lottery.Weighter
|
||||
// get info for updating data and calculating costs
|
||||
err := s.server.db.QueryRow("SELECT currType, currNumber, currQuant, rollsCount FROM gacha_shop_items WHERE shophash=$1 AND entryType=$2", pkt.GachaHash, pkt.RollType).Scan(&currType, &currNumber, &currQuant, &rollsCount)
|
||||
bf := byteframe.NewByteFrame()
|
||||
var gachaEntries []GachaEntry
|
||||
var entry GachaEntry
|
||||
var rewards []GachaItem
|
||||
var reward GachaItem
|
||||
err, rolls := transactGacha(s, pkt.GachaID, pkt.RollType)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
||||
return
|
||||
}
|
||||
// get existing items in storage if any
|
||||
var data []byte
|
||||
_ = s.server.db.QueryRow("SELECT gacha_items FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if len(data) == 0 {
|
||||
data = []byte{0x00}
|
||||
}
|
||||
// get gacha items and iterate through them for gacha roll
|
||||
shopEntries, err := s.server.db.Query("SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity FROM gacha_shop_items WHERE shophash=$1 AND entryType=100", pkt.GachaHash)
|
||||
temp := byteframe.NewByteFrame()
|
||||
entries, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
||||
return
|
||||
}
|
||||
for shopEntries.Next() {
|
||||
err = shopEntries.Scan(&itemhash, &percentage, &rarityIcon, &itemCount, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity))
|
||||
for entries.Next() {
|
||||
entries.StructScan(&entry)
|
||||
gachaEntries = append(gachaEntries, entry)
|
||||
}
|
||||
rewardEntries, err := getRandomEntries(gachaEntries, rolls, false)
|
||||
for i := range rewardEntries {
|
||||
items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = $1`, rewardEntries[i].ID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
continue
|
||||
}
|
||||
items = append(items, &gachaItem{itemhash: itemhash, percentage: percentage, rarityIcon: rarityIcon, itemCount: itemCount, itemType: itemType, itemId: itemId, quantity: quantity})
|
||||
}
|
||||
// execute rolls, build response and update database
|
||||
results := byte(0)
|
||||
resp := byteframe.NewByteFrame()
|
||||
dbUpdate := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(0) // results go here later
|
||||
l := lottery.NewDefaultLottery()
|
||||
for x := 0; x < int(rollsCount); x++ {
|
||||
ind := l.Draw(items)
|
||||
results += items[ind].(*gachaItem).itemCount
|
||||
for y := 0; y < int(items[ind].(*gachaItem).itemCount); y++ {
|
||||
// items in storage don't get rarity
|
||||
dbUpdate.WriteUint8(uint8(items[ind].(*gachaItem).itemType[y]))
|
||||
dbUpdate.WriteUint16(uint16(items[ind].(*gachaItem).itemId[y]))
|
||||
dbUpdate.WriteUint16(uint16(items[ind].(*gachaItem).quantity[y]))
|
||||
data = append(data, dbUpdate.Data()...)
|
||||
dbUpdate.Seek(0, 0)
|
||||
// response needs all item info and the rarity
|
||||
resp.WriteBytes(dbUpdate.Data())
|
||||
resp.WriteUint8(items[ind].(*gachaItem).rarityIcon)
|
||||
for items.Next() {
|
||||
items.StructScan(&reward)
|
||||
rewards = append(rewards, reward)
|
||||
temp.WriteUint8(reward.ItemType)
|
||||
temp.WriteUint16(reward.ItemID)
|
||||
temp.WriteUint16(reward.Quantity)
|
||||
temp.WriteUint8(entry.Rarity)
|
||||
}
|
||||
}
|
||||
resp.Seek(0, 0)
|
||||
resp.WriteUint8(results)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
|
||||
// add claimables to DB
|
||||
data[0] = data[0] + results
|
||||
_, err = s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", data, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update minidata in db", zap.Error(err))
|
||||
}
|
||||
// deduct gacha coins if relevant, items are handled fine by the standard savedata packet immediately afterwards
|
||||
if currType == 19 {
|
||||
_, err = s.server.db.Exec("UPDATE characters SET gacha_trial = CASE WHEN (gacha_trial > $1) then gacha_trial - $1 else gacha_trial end, gacha_prem = CASE WHEN NOT (gacha_trial > $1) then gacha_prem - $1 else gacha_prem end WHERE id=$2", currNumber, s.charID)
|
||||
}
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err))
|
||||
}
|
||||
bf.WriteUint8(uint8(len(rewards)))
|
||||
bf.WriteBytes(temp.Data())
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
addGachaItem(s, rewards)
|
||||
}
|
||||
|
||||
func handleMsgMhfUseGachaPoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
// should write to database when that's set up
|
||||
pkt := p.(*mhfpacket.MsgMhfUseGachaPoint)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
func handleMsgMhfPlayStepupGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPlayStepupGacha)
|
||||
bf := byteframe.NewByteFrame()
|
||||
var gachaEntries []GachaEntry
|
||||
var entry GachaEntry
|
||||
var rewards []GachaItem
|
||||
var reward GachaItem
|
||||
err, rolls := transactGacha(s, pkt.GachaID, pkt.RollType)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
||||
return
|
||||
}
|
||||
s.server.db.Exec("UPDATE users u SET frontier_points=frontier_points+(SELECT frontier_points FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2) WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$3)", pkt.GachaID, pkt.RollType, s.charID)
|
||||
s.server.db.Exec(`DELETE FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID)
|
||||
s.server.db.Exec(`INSERT INTO gacha_stepup (gacha_id, step, character_id) VALUES ($1, $2, $3)`, pkt.GachaID, pkt.RollType+1, s.charID)
|
||||
temp := byteframe.NewByteFrame()
|
||||
guaranteedItems := getGuaranteedItems(s, pkt.GachaID, pkt.RollType)
|
||||
for _, item := range guaranteedItems {
|
||||
temp.WriteUint8(item.ItemType)
|
||||
temp.WriteUint16(item.ItemID)
|
||||
temp.WriteUint16(item.Quantity)
|
||||
temp.WriteUint8(0)
|
||||
}
|
||||
entries, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
||||
return
|
||||
}
|
||||
for entries.Next() {
|
||||
entries.StructScan(&entry)
|
||||
gachaEntries = append(gachaEntries, entry)
|
||||
}
|
||||
rewardEntries, err := getRandomEntries(gachaEntries, rolls, false)
|
||||
for i := range rewardEntries {
|
||||
items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = $1`, rewardEntries[i].ID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for items.Next() {
|
||||
items.StructScan(&reward)
|
||||
rewards = append(rewards, reward)
|
||||
temp.WriteUint8(reward.ItemType)
|
||||
temp.WriteUint16(reward.ItemID)
|
||||
temp.WriteUint16(reward.Quantity)
|
||||
temp.WriteUint8(entry.Rarity)
|
||||
}
|
||||
}
|
||||
bf.WriteUint8(uint8(len(rewards) + len(guaranteedItems)))
|
||||
bf.WriteUint8(uint8(len(rewards)))
|
||||
bf.WriteBytes(temp.Data())
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
addGachaItem(s, rewards)
|
||||
addGachaItem(s, guaranteedItems)
|
||||
}
|
||||
|
||||
func handleMsgMhfGetStepupStatus(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetStepupStatus)
|
||||
// TODO: Reset daily (noon)
|
||||
var step uint8
|
||||
s.server.db.QueryRow(`SELECT step FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID).Scan(&step)
|
||||
var stepCheck int
|
||||
s.server.db.QueryRow(`SELECT COUNT(1) FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2`, pkt.GachaID, step).Scan(&stepCheck)
|
||||
if stepCheck == 0 {
|
||||
s.server.db.Exec(`DELETE FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID)
|
||||
step = 0
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(step)
|
||||
bf.WriteUint32(uint32(Time_Current_Adjusted().Unix()))
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfGetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetBoxGachaInfo)
|
||||
entries, err := s.server.db.Queryx(`SELECT entry_id FROM gacha_box WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
||||
return
|
||||
}
|
||||
var entryIDs []uint32
|
||||
for entries.Next() {
|
||||
var entryID uint32
|
||||
entries.Scan(&entryID)
|
||||
entryIDs = append(entryIDs, entryID)
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(uint8(len(entryIDs)))
|
||||
for i := range entryIDs {
|
||||
bf.WriteUint32(entryIDs[i])
|
||||
bf.WriteBool(true)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfPlayBoxGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPlayBoxGacha)
|
||||
bf := byteframe.NewByteFrame()
|
||||
var gachaEntries []GachaEntry
|
||||
var entry GachaEntry
|
||||
var rewards []GachaItem
|
||||
var reward GachaItem
|
||||
err, rolls := transactGacha(s, pkt.GachaID, pkt.RollType)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
||||
return
|
||||
}
|
||||
temp := byteframe.NewByteFrame()
|
||||
entries, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
||||
return
|
||||
}
|
||||
for entries.Next() {
|
||||
entries.StructScan(&entry)
|
||||
gachaEntries = append(gachaEntries, entry)
|
||||
}
|
||||
rewardEntries, err := getRandomEntries(gachaEntries, rolls, true)
|
||||
for i := range rewardEntries {
|
||||
items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = $1`, rewardEntries[i].ID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
s.server.db.Exec(`INSERT INTO gacha_box (gacha_id, entry_id, character_id) VALUES ($1, $2, $3)`, pkt.GachaID, rewardEntries[i].ID, s.charID)
|
||||
for items.Next() {
|
||||
items.StructScan(&reward)
|
||||
rewards = append(rewards, reward)
|
||||
temp.WriteUint8(reward.ItemType)
|
||||
temp.WriteUint16(reward.ItemID)
|
||||
temp.WriteUint16(reward.Quantity)
|
||||
temp.WriteUint8(0)
|
||||
}
|
||||
}
|
||||
bf.WriteUint8(uint8(len(rewards)))
|
||||
bf.WriteBytes(temp.Data())
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
addGachaItem(s, rewards)
|
||||
}
|
||||
|
||||
func handleMsgMhfResetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfResetBoxGachaInfo)
|
||||
s.server.db.Exec("DELETE FROM gacha_box WHERE gacha_id = $1 AND character_id = $2", pkt.GachaID, s.charID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfExchangeFpoint2Item(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfExchangeFpoint2Item)
|
||||
var balance uint32
|
||||
var itemValue, quantity int
|
||||
_ = s.server.db.QueryRow("SELECT quantity, fpoints FROM fpoint_items WHERE id=$1", pkt.TradeID).Scan(&quantity, &itemValue)
|
||||
s.server.db.QueryRow("SELECT quantity, fpoints FROM fpoint_items WHERE id=$1", pkt.TradeID).Scan(&quantity, &itemValue)
|
||||
cost := (int(pkt.Quantity) * quantity) * itemValue
|
||||
s.server.db.QueryRow("UPDATE characters SET frontier_points=frontier_points::int - $1 WHERE id=$2 RETURNING frontier_points", cost, s.charID).Scan(&balance)
|
||||
s.server.db.QueryRow("UPDATE users u SET frontier_points=frontier_points::int - $1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2) RETURNING frontier_points", cost, s.charID).Scan(&balance)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(balance)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
@@ -335,7 +611,7 @@ func handleMsgMhfExchangeItem2Fpoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
var itemValue, quantity int
|
||||
s.server.db.QueryRow("SELECT quantity, fpoints FROM fpoint_items WHERE id=$1", pkt.TradeID).Scan(&quantity, &itemValue)
|
||||
cost := (int(pkt.Quantity) / quantity) * itemValue
|
||||
s.server.db.QueryRow("UPDATE characters SET frontier_points=COALESCE(frontier_points::int + $1, $1) WHERE id=$2 RETURNING frontier_points", cost, s.charID).Scan(&balance)
|
||||
s.server.db.QueryRow("UPDATE users u SET frontier_points=COALESCE(frontier_points::int + $1, $1) WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2) RETURNING frontier_points", cost, s.charID).Scan(&balance)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(balance)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
@@ -394,291 +670,6 @@ func handleMsgMhfGetFpointExchangeList(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfPlayStepupGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPlayStepupGacha)
|
||||
results := byte(0)
|
||||
stepResults := byte(0)
|
||||
resp := byteframe.NewByteFrame()
|
||||
rollFrame := byteframe.NewByteFrame()
|
||||
stepFrame := byteframe.NewByteFrame()
|
||||
stepData := []byte{}
|
||||
var currType, rarityIcon, rollsCount, itemCount byte
|
||||
var currQuant, currNumber, percentage uint16
|
||||
var itemhash uint32
|
||||
var itemType, itemId, quantity pq.Int64Array
|
||||
var items []lottery.Weighter
|
||||
// get info for updating data and calculating costs
|
||||
err := s.server.db.QueryRow("SELECT currType, currNumber, currQuant, rollsCount, itemCount, itemType, itemId, quantity FROM gacha_shop_items WHERE shophash=$1 AND entryType=$2", pkt.GachaHash, pkt.RollType).Scan(&currType, &currNumber, &currQuant, &rollsCount, &itemCount, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// get existing items in storage if any
|
||||
var data []byte
|
||||
_ = s.server.db.QueryRow("SELECT gacha_items FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if len(data) == 0 {
|
||||
data = []byte{0x00}
|
||||
}
|
||||
// roll definition includes items with step up gachas that are appended last
|
||||
for x := 0; x < int(itemCount); x++ {
|
||||
stepFrame.WriteUint8(uint8(itemType[x]))
|
||||
stepFrame.WriteUint16(uint16(itemId[x]))
|
||||
stepFrame.WriteUint16(uint16(quantity[x]))
|
||||
stepData = append(stepData, stepFrame.Data()...)
|
||||
stepFrame.WriteUint8(0) // rarity still defined
|
||||
stepResults++
|
||||
}
|
||||
// get gacha items and iterate through them for gacha roll
|
||||
shopEntries, err := s.server.db.Query("SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity FROM gacha_shop_items WHERE shophash=$1 AND entryType=100", pkt.GachaHash)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for shopEntries.Next() {
|
||||
err = shopEntries.Scan(&itemhash, &percentage, &rarityIcon, &itemCount, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
items = append(items, &gachaItem{itemhash: itemhash, percentage: percentage, rarityIcon: rarityIcon, itemCount: itemCount, itemType: itemType, itemId: itemId, quantity: quantity})
|
||||
}
|
||||
// execute rolls, build response and update database
|
||||
resp.WriteUint16(0) // results count goes here later
|
||||
l := lottery.NewDefaultLottery()
|
||||
for x := 0; x < int(rollsCount); x++ {
|
||||
ind := l.Draw(items)
|
||||
results += items[ind].(*gachaItem).itemCount
|
||||
for y := 0; y < int(items[ind].(*gachaItem).itemCount); y++ {
|
||||
// items in storage don't get rarity
|
||||
rollFrame.WriteUint8(uint8(items[ind].(*gachaItem).itemType[y]))
|
||||
rollFrame.WriteUint16(uint16(items[ind].(*gachaItem).itemId[y]))
|
||||
rollFrame.WriteUint16(uint16(items[ind].(*gachaItem).quantity[y]))
|
||||
data = append(data, rollFrame.Data()...)
|
||||
rollFrame.Seek(0, 0)
|
||||
// response needs all item info and the rarity
|
||||
resp.WriteBytes(rollFrame.Data())
|
||||
resp.WriteUint8(items[ind].(*gachaItem).rarityIcon)
|
||||
}
|
||||
}
|
||||
resp.WriteBytes(stepFrame.Data())
|
||||
resp.Seek(0, 0)
|
||||
resp.WriteUint8(results + stepResults)
|
||||
resp.WriteUint8(results)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
|
||||
// add claimables to DB
|
||||
data = append(data, stepData...)
|
||||
data[0] = data[0] + results + stepResults
|
||||
_, err = s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", data, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err))
|
||||
}
|
||||
// deduct gacha coins if relevant, items are handled fine by the standard savedata packet immediately afterwards
|
||||
// reduce real if trial don't cover cost
|
||||
if currType == 19 {
|
||||
_, err = s.server.db.Exec(`UPDATE characters
|
||||
SET gacha_trial = CASE WHEN (gacha_trial > $1) then gacha_trial - $1 else gacha_trial end,
|
||||
gacha_prem = CASE WHEN NOT (gacha_trial > $1) then gacha_prem - $1 else gacha_prem end
|
||||
WHERE id=$2`, currNumber, s.charID)
|
||||
}
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err))
|
||||
}
|
||||
// update step progression
|
||||
_, err = s.server.db.Exec("UPDATE stepup_state SET step_progression = $1 WHERE char_id = $2", pkt.RollType+1, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update step_progression in db", zap.Error(err))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func handleMsgMhfReceiveGachaItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReceiveGachaItem)
|
||||
// persistent for claimable items on cat
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT COALESCE(gacha_items, $2) FROM characters WHERE id = $1", s.charID, []byte{0x00}).Scan(&data)
|
||||
if err != nil {
|
||||
panic("Failed to get gacha_items")
|
||||
}
|
||||
// limit of 36 items are returned
|
||||
if data[0] > 36 {
|
||||
outData := make([]byte, 181)
|
||||
copy(outData, data[0:181])
|
||||
outData[0] = byte(36)
|
||||
saveData := append(data[:1], data[181:]...)
|
||||
saveData[0] = saveData[0] - 36
|
||||
doAckBufSucceed(s, pkt.AckHandle, outData)
|
||||
if pkt.Unk0 != 0x2401 {
|
||||
_, err := s.server.db.Exec("UPDATE characters SET gacha_items = $2 WHERE id = $1", s.charID, saveData)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
if pkt.Unk0 != 0x2401 {
|
||||
_, err := s.server.db.Exec("UPDATE characters SET gacha_items = null WHERE id = $1", s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfGetStepupStatus(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetStepupStatus)
|
||||
// get the reset time from db
|
||||
var step_progression int
|
||||
var step_time time.Time
|
||||
err := s.server.db.QueryRow(`SELECT COALESCE(step_progression, 0), COALESCE(step_time, $1) FROM stepup_state WHERE char_id = $2 AND shophash = $3`, time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), s.charID, pkt.GachaHash).Scan(&step_progression, &step_time)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to Select coalesce in db", zap.Error(err))
|
||||
}
|
||||
|
||||
// calculate next midday
|
||||
var t = time.Now().In(time.FixedZone("UTC+9", 9*60*60))
|
||||
year, month, day := t.Date()
|
||||
midday := time.Date(year, month, day, 12, 0, 0, 0, t.Location())
|
||||
if t.After(midday) {
|
||||
midday = midday.Add(24 * time.Hour)
|
||||
}
|
||||
// after midday or not set
|
||||
if t.After(step_time) {
|
||||
step_progression = 0
|
||||
}
|
||||
_, err = s.server.db.Exec(`INSERT INTO stepup_state (shophash, step_progression, step_time, char_id)
|
||||
VALUES ($1,$2,$3,$4) ON CONFLICT (shophash, char_id)
|
||||
DO UPDATE SET step_progression=$2, step_time=$3
|
||||
WHERE EXCLUDED.char_id=$4 AND EXCLUDED.shophash=$1`, pkt.GachaHash, step_progression, midday, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update platedata savedata in db", zap.Error(err))
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(uint8(step_progression))
|
||||
resp.WriteUint32(uint32(time.Now().In(time.FixedZone("UTC+9", 9*60*60)).Unix()))
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfPlayFreeGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
// not sure this is used anywhere, free gachas use the MSG_MHF_PLAY_NORMAL_GACHA method in captures
|
||||
}
|
||||
|
||||
func handleMsgMhfGetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetBoxGachaInfo)
|
||||
count := 0
|
||||
var used_itemhash pq.Int64Array
|
||||
// pull array of used values
|
||||
// single sized respone with 0x00 is a valid with no items present
|
||||
_ = s.server.db.QueryRow("SELECT used_itemhash FROM lucky_box_state WHERE shophash=$1 AND char_id=$2", pkt.GachaHash, s.charID).Scan((*pq.Int64Array)(&used_itemhash))
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(0)
|
||||
for ind := range used_itemhash {
|
||||
resp.WriteUint32(uint32(used_itemhash[ind]))
|
||||
resp.WriteUint8(1)
|
||||
count++
|
||||
}
|
||||
resp.Seek(0, 0)
|
||||
resp.WriteUint8(uint8(count))
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfPlayBoxGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPlayBoxGacha)
|
||||
// needs to query db for input gacha and return a result or number of results
|
||||
// uint8 number of results
|
||||
// uint8 item type
|
||||
// uint16 item id
|
||||
// uint16 quantity
|
||||
|
||||
var currType, rarityIcon, rollsCount, itemCount byte
|
||||
var currQuant, currNumber, percentage uint16
|
||||
var itemhash uint32
|
||||
var itemType, itemId, quantity, usedItemHash pq.Int64Array
|
||||
var items []lottery.Weighter
|
||||
// get info for updating data and calculating costs
|
||||
err := s.server.db.QueryRow("SELECT currType, currNumber, currQuant, rollsCount FROM gacha_shop_items WHERE shophash=$1 AND entryType=$2", pkt.GachaHash, pkt.RollType).Scan(&currType, &currNumber, &currQuant, &rollsCount)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// get existing items in storage if any
|
||||
var data []byte
|
||||
_ = s.server.db.QueryRow("SELECT gacha_items FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if len(data) == 0 {
|
||||
data = []byte{0x00}
|
||||
}
|
||||
// get gacha items and iterate through them for gacha roll
|
||||
shopEntries, err := s.server.db.Query(`SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity
|
||||
FROM gacha_shop_items
|
||||
WHERE shophash=$1 AND entryType=100
|
||||
EXCEPT ALL SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity
|
||||
FROM gacha_shop_items gsi JOIN lucky_box_state lbs ON gsi.itemhash = ANY(lbs.used_itemhash)
|
||||
WHERE lbs.char_id=$2`, pkt.GachaHash, s.charID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for shopEntries.Next() {
|
||||
err = shopEntries.Scan(&itemhash, &percentage, &rarityIcon, &itemCount, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
items = append(items, &gachaItem{itemhash: itemhash, percentage: percentage, rarityIcon: rarityIcon, itemCount: itemCount, itemType: itemType, itemId: itemId, quantity: quantity})
|
||||
}
|
||||
// execute rolls, build response and update database
|
||||
results := byte(0)
|
||||
resp := byteframe.NewByteFrame()
|
||||
dbUpdate := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(0) // results go here later
|
||||
l := lottery.NewDefaultLottery()
|
||||
for x := 0; x < int(rollsCount); x++ {
|
||||
ind := l.Draw(items)
|
||||
results += items[ind].(*gachaItem).itemCount
|
||||
for y := 0; y < int(items[ind].(*gachaItem).itemCount); y++ {
|
||||
// items in storage don't get rarity
|
||||
dbUpdate.WriteUint8(uint8(items[ind].(*gachaItem).itemType[y]))
|
||||
dbUpdate.WriteUint16(uint16(items[ind].(*gachaItem).itemId[y]))
|
||||
dbUpdate.WriteUint16(uint16(items[ind].(*gachaItem).quantity[y]))
|
||||
data = append(data, dbUpdate.Data()...)
|
||||
dbUpdate.Seek(0, 0)
|
||||
// response needs all item info and the rarity
|
||||
resp.WriteBytes(dbUpdate.Data())
|
||||
resp.WriteUint8(items[ind].(*gachaItem).rarityIcon)
|
||||
|
||||
usedItemHash = append(usedItemHash, int64(items[ind].(*gachaItem).itemhash))
|
||||
}
|
||||
// remove rolled
|
||||
items = append(items[:ind], items[ind+1:]...)
|
||||
}
|
||||
resp.Seek(0, 0)
|
||||
resp.WriteUint8(results)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
|
||||
// add claimables to DB
|
||||
data[0] = data[0] + results
|
||||
_, err = s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", data, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err))
|
||||
}
|
||||
// update lucky_box_state
|
||||
_, err = s.server.db.Exec(`INSERT INTO lucky_box_state (char_id, shophash, used_itemhash)
|
||||
VALUES ($1,$2,$3) ON CONFLICT (char_id, shophash)
|
||||
DO UPDATE SET used_itemhash = COALESCE(lucky_box_state.used_itemhash::int[] || $3::int[], $3::int[])
|
||||
WHERE EXCLUDED.char_id=$1 AND EXCLUDED.shophash=$2`, s.charID, pkt.GachaHash, usedItemHash)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update lucky box state in db", zap.Error(err))
|
||||
}
|
||||
// deduct gacha coins if relevant, items are handled fine by the standard savedata packet immediately afterwards
|
||||
if currType == 19 {
|
||||
_, err = s.server.db.Exec(`UPDATE characters
|
||||
SET gacha_trial = CASE WHEN (gacha_trial > $1) then gacha_trial - $1 else gacha_trial end, gacha_prem = CASE WHEN NOT (gacha_trial > $1) then gacha_prem - $1 else gacha_prem end
|
||||
WHERE id=$2`, currNumber, s.charID)
|
||||
}
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update gacha_trial in db", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfResetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfResetBoxGachaInfo)
|
||||
_, err := s.server.db.Exec("DELETE FROM lucky_box_state WHERE shophash=$1 AND char_id=$2", pkt.GachaHash, s.charID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
@@ -158,7 +158,9 @@ func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
if s.stageID == "" {
|
||||
s.stageMoveStack.Set(pkt.StageID)
|
||||
} else {
|
||||
s.stage.Lock()
|
||||
s.stage.reservedClientSlots[s.charID] = false
|
||||
s.stage.Unlock()
|
||||
s.stageMoveStack.Push(s.stageID)
|
||||
s.stageMoveStack.Lock()
|
||||
}
|
||||
|
||||
@@ -1,8 +1,59 @@
|
||||
package channelserver
|
||||
|
||||
import "erupe-ce/network/mhfpacket"
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"time"
|
||||
)
|
||||
|
||||
func handleMsgMhfInfoTournament(s *Session, p mhfpacket.MHFPacket) {}
|
||||
func handleMsgMhfInfoTournament(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfInfoTournament)
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
switch pkt.Unk0 {
|
||||
case 0:
|
||||
bf.WriteUint32(uint32(Time_Current_Adjusted().Unix()))
|
||||
bf.WriteUint32(0) // Tied to schedule ID?
|
||||
case 1:
|
||||
|
||||
bf.WriteBytes(make([]byte, 21))
|
||||
ps.Uint8(bf, "", false)
|
||||
break
|
||||
|
||||
bf.WriteUint32(0xACEDCAFE)
|
||||
|
||||
bf.WriteUint32(5) // Active schedule?
|
||||
|
||||
bf.WriteUint32(1) // Schedule ID?
|
||||
|
||||
bf.WriteUint32(32) // Max players
|
||||
bf.WriteUint32(0) // Registered players
|
||||
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(2) // Color code for schedule item
|
||||
bf.WriteUint32(0)
|
||||
|
||||
bf.WriteUint32(uint32(time.Now().Add(time.Hour * -10).Unix()))
|
||||
bf.WriteUint32(uint32(time.Now().Add(time.Hour * 10).Unix()))
|
||||
bf.WriteUint32(uint32(time.Now().Add(time.Hour * 10).Unix()))
|
||||
bf.WriteUint32(uint32(time.Now().Add(time.Hour * 10).Unix()))
|
||||
bf.WriteUint32(uint32(time.Now().Add(time.Hour * 10).Unix()))
|
||||
bf.WriteUint32(uint32(time.Now().Add(time.Hour * 10).Unix()))
|
||||
|
||||
bf.WriteBool(true) // Unk
|
||||
bf.WriteBool(false) // Cafe-only
|
||||
|
||||
bf.WriteUint32(0) // Min HR
|
||||
bf.WriteUint32(0) // Max HR
|
||||
|
||||
ps.Uint8(bf, "Test", false)
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfEntryTournament(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
|
||||
@@ -58,6 +58,42 @@ func handleMsgMhfPostTowerInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfGetTenrouirai(s *Session, p mhfpacket.MHFPacket) {
|
||||
// if the game gets bad responses for this it breaks the ability to save
|
||||
pkt := p.(*mhfpacket.MsgMhfGetTenrouirai)
|
||||
var data []byte
|
||||
var err error
|
||||
if pkt.Unk0 == 1 {
|
||||
data, err = hex.DecodeString("0A218EAD000000000000000000000001010000000000060010")
|
||||
} else if pkt.Unk2 == 4 {
|
||||
data, err = hex.DecodeString("0A218EAD0000000000000000000000210101005000000202010102020104001000000202010102020106003200000202010002020104000C003202020101020201030032000002020101020202059C4000000202010002020105C35000320202010102020201003C00000202010102020203003200000201010001020203002800320201010101020204000C00000201010101020206002800000201010001020101003C00320201020101020105C35000000301020101020106003200000301020001020104001000320301020101020105C350000003010201010202030028000003010200010201030032003203010201010202059C4000000301020101010206002800000301020001010201003C00320301020101010206003200000301020101010204000C000003010200010101010050003203010201010101059C40000003010201010101030032000003010200010101040010003203010001010101060032000003010001010102030028000003010001010101010050003203010000010102059C4000000301000001010206002800000301000001010010")
|
||||
} else {
|
||||
data = []byte{0x00, 0x00, 0x00, 0x00}
|
||||
s.logger.Info("GET_TENROUIRAI request for unknown type")
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfPostTenrouirai(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostTenrouirai)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfGetBreakSeibatuLevelReward(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfGetWeeklySeibatuRankingReward(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetWeeklySeibatuRankingReward)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfPresentBox(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPresentBox)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfGetGemInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGemInfo)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
|
||||
@@ -92,8 +92,7 @@ type RavienteRegister struct {
|
||||
}
|
||||
|
||||
type RavienteState struct {
|
||||
damageMultiplier uint32
|
||||
stateData []uint32
|
||||
stateData []uint32
|
||||
}
|
||||
|
||||
type RavienteSupport struct {
|
||||
@@ -111,9 +110,7 @@ func NewRaviente() *Raviente {
|
||||
maxPlayers: 0,
|
||||
carveQuest: 0,
|
||||
}
|
||||
ravienteState := &RavienteState{
|
||||
damageMultiplier: 1,
|
||||
}
|
||||
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}
|
||||
@@ -127,6 +124,23 @@ func NewRaviente() *Raviente {
|
||||
return raviente
|
||||
}
|
||||
|
||||
func (r *Raviente) GetRaviMultiplier(s *Server) uint32 {
|
||||
raviSema := getRaviSemaphore(s)
|
||||
if raviSema != nil {
|
||||
var minPlayers uint32
|
||||
if r.register.maxPlayers > 8 {
|
||||
minPlayers = 24
|
||||
} else {
|
||||
minPlayers = 4
|
||||
}
|
||||
if uint32(len(raviSema.clients)) > minPlayers {
|
||||
return 1
|
||||
}
|
||||
return minPlayers / uint32(len(raviSema.clients))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// NewServer creates a new Server type.
|
||||
func NewServer(config *Config) *Server {
|
||||
s := &Server{
|
||||
|
||||
@@ -6,6 +6,32 @@ func getLangStrings(s *Server) map[string]string {
|
||||
case "jp":
|
||||
strings["language"] = "日本語"
|
||||
strings["cafeReset"] = "%d/%dにリセット"
|
||||
|
||||
strings["commandDisabled"] = "%sのコマンドは無効です"
|
||||
strings["commandReload"] = "リロードします"
|
||||
strings["commandKqfGet"] = "現在のキークエストフラグ:%x"
|
||||
strings["commandKqfSetError"] = "キークエコマンドエラー 例:%s set xxxxxxxxxxxxxxxx"
|
||||
strings["commandKqfSetSuccess"] = "キークエストのフラグが更新されました。ワールド/ランドを移動してください"
|
||||
strings["commandRightsError"] = "コース更新コマンドエラー 例:%s x"
|
||||
strings["commandRightsSuccess"] = "コース情報を更新しました:%d"
|
||||
strings["commandCourseError"] = "コース確認コマンドエラー 例:%s <name>"
|
||||
strings["commandCourseDisabled"] = "%sコースは無効です"
|
||||
strings["commandCourseEnabled"] = "%sコースは有効です"
|
||||
strings["commandCourseLocked"] = "%sコースはロックされています"
|
||||
strings["commandTeleportError"] = "テレポートコマンドエラー 構文:%s x y"
|
||||
strings["commandTeleportSuccess"] = "%d %dにテレポート"
|
||||
|
||||
strings["commandRaviNoCommand"] = "ラヴィコマンドが指定されていません"
|
||||
strings["commandRaviStartSuccess"] = "大討伐を開始します"
|
||||
strings["commandRaviStartError"] = "大討伐は既に開催されています"
|
||||
strings["commandRaviMultiplier"] = "ラヴィダメージ倍率:x%d"
|
||||
strings["commandRaviResSuccess"] = "復活支援を実行します"
|
||||
strings["commandRaviResError"] = "復活支援は実行されませんでした"
|
||||
strings["commandRaviSedSuccess"] = "鎮静支援を実行します"
|
||||
strings["commandRaviRequest"] = "鎮静支援を要請します"
|
||||
strings["commandRaviError"] = "ラヴィコマンドが認識されません"
|
||||
strings["commandRaviNoPlayers"] = "誰も大討伐に参加していません"
|
||||
|
||||
strings["ravienteBerserk"] = "<大討伐:猛狂期>が開催されました!"
|
||||
strings["ravienteExtreme"] = "<大討伐:猛狂期【極】>が開催されました!"
|
||||
strings["ravienteExtremeLimited"] = "<大討伐:猛狂期【極】(制限付)>が開催されました!"
|
||||
@@ -28,6 +54,32 @@ func getLangStrings(s *Server) map[string]string {
|
||||
default:
|
||||
strings["language"] = "English"
|
||||
strings["cafeReset"] = "Resets on %d/%d"
|
||||
|
||||
strings["commandDisabled"] = "%s command is disabled"
|
||||
strings["commandReload"] = "Reloading players..."
|
||||
strings["commandKqfGet"] = "KQF: %x"
|
||||
strings["commandKqfSetError"] = "Error in command. Format: %s set xxxxxxxxxxxxxxxx"
|
||||
strings["commandKqfSetSuccess"] = "KQF set, please switch Land/World"
|
||||
strings["commandRightsError"] = "Error in command. Format: %s x"
|
||||
strings["commandRightsSuccess"] = "Set rights integer: %d"
|
||||
strings["commandCourseError"] = "Error in command. Format: %s <name>"
|
||||
strings["commandCourseDisabled"] = "%s Course disabled"
|
||||
strings["commandCourseEnabled"] = "%s Course enabled"
|
||||
strings["commandCourseLocked"] = "%s Course is locked"
|
||||
strings["commandTeleportError"] = "Error in command. Format: %s x y"
|
||||
strings["commandTeleportSuccess"] = "Teleporting to %d %d"
|
||||
|
||||
strings["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 %dx"
|
||||
strings["commandRaviResSuccess"] = "Sending resurrection support!"
|
||||
strings["commandRaviResError"] = "Resurrection support has not been requested!"
|
||||
strings["commandRaviSedSuccess"] = "Sending sedation support if requested!"
|
||||
strings["commandRaviRequest"] = "Requesting sedation support!"
|
||||
strings["commandRaviError"] = "Raviente command not recognised!"
|
||||
strings["commandRaviNoPlayers"] = "No one has joined the Great Slaying!"
|
||||
|
||||
strings["ravienteBerserk"] = "<Great Slaying: Berserk> is being held!"
|
||||
strings["ravienteExtreme"] = "<Great Slaying: Extreme> is being held!"
|
||||
strings["ravienteExtremeLimited"] = "<Great Slaying: Extreme (Limited)> is being held!"
|
||||
|
||||
Reference in New Issue
Block a user