mirror of
https://github.com/Mezeporta/Erupe.git
synced 2025-12-13 15:34:38 +01:00
repository cleanup
This commit is contained in:
738
server/channelserver/handlers_shop_gacha.go
Normal file
738
server/channelserver/handlers_shop_gacha.go
Normal file
@@ -0,0 +1,738 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
//"erupe-ce/common/stringsupport"
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"github.com/lib/pq"
|
||||
"github.com/sachaos/lottery"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func contains(s []string, str string) bool {
|
||||
for _, v := range s {
|
||||
if v == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateShop)
|
||||
// SHOP TYPES:
|
||||
// 01 = Running Gachas, 02 = actual gacha, 04 = N Points, 05 = GCP, 07 = Item to GCP, 08 = Diva Defense, 10 = Hunter's Road
|
||||
|
||||
// GACHA FORMAT:
|
||||
// int32: gacha id
|
||||
|
||||
// STORE FORMAT:
|
||||
// Int16: total item count
|
||||
// Int16: total item count
|
||||
|
||||
// ITEM FORMAT:
|
||||
// int32: Unique item hash for tracking purchases
|
||||
// int16: padding?
|
||||
// int16: Item ID
|
||||
// int16: padding?
|
||||
// int16: GCP returns
|
||||
// int16: Number traded at once
|
||||
// int16: HR or SR Requirement
|
||||
// int16: Whichever of the above it isn't
|
||||
// int16: GR Requirement
|
||||
// int16: Store level requirement
|
||||
// int16: Maximum quantity purchasable
|
||||
// int16: Unk
|
||||
// int16: Road floors cleared requirement
|
||||
// int16: Road White Fatalis weekly kills
|
||||
if pkt.ShopType == 2 {
|
||||
shopEntries, err := s.server.db.Query("SELECT entryType, itemhash, currType, currNumber, currQuant, percentage, rarityIcon, rollsCount, itemCount, dailyLimit, itemType, itemId, quantity FROM gacha_shop_items WHERE shophash=$1", pkt.ShopID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var entryType, currType, rarityIcon, rollsCount, itemCount, dailyLimit byte
|
||||
var currQuant, currNumber, percentage uint16
|
||||
var itemhash uint32
|
||||
var itemType, itemId, quantity pq.Int64Array
|
||||
var entryCount int
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(pkt.ShopID)
|
||||
resp.WriteUint16(0) // total defs
|
||||
for shopEntries.Next() {
|
||||
err = shopEntries.Scan(&entryType, &itemhash, &currType, &currNumber, &currQuant, &percentage, &rarityIcon, &rollsCount, &itemCount, &dailyLimit, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
resp.WriteUint8(entryType)
|
||||
resp.WriteUint32(itemhash)
|
||||
resp.WriteUint8(currType)
|
||||
resp.WriteUint16(0) // unk, always 0 in existing packets
|
||||
resp.WriteUint16(currNumber) // it's either item ID or quantity for gacha coins
|
||||
resp.WriteUint16(currQuant) // only for item ID
|
||||
resp.WriteUint16(percentage)
|
||||
resp.WriteUint8(rarityIcon)
|
||||
resp.WriteUint8(rollsCount)
|
||||
resp.WriteUint8(itemCount)
|
||||
resp.WriteUint16(0) // unk, always 0 in existing packets
|
||||
resp.WriteUint8(dailyLimit)
|
||||
resp.WriteUint8(0) // unk, always 0 in existing packets
|
||||
for i := 0; i < int(itemCount); i++ {
|
||||
resp.WriteUint16(uint16(itemType[i])) // unk, always 0 in existing packets
|
||||
resp.WriteUint16(uint16(itemId[i])) // unk, always 0 in existing packets
|
||||
resp.WriteUint16(uint16(quantity[i])) // unk, always 0 in existing packets
|
||||
}
|
||||
entryCount++
|
||||
}
|
||||
if entryCount == 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
return
|
||||
}
|
||||
resp.Seek(4, 0)
|
||||
resp.WriteUint16(uint16(entryCount))
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
} else if pkt.ShopType == 1 {
|
||||
gachaCount := 0
|
||||
shopEntries, err := s.server.db.Query("SELECT hash, reqGR, reqHR, gachaName, gachaLink0, gachaLink1, COALESCE(gachaLink2, ''), extraIcon, gachaType, hideFlag FROM gacha_shop")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(0)
|
||||
var gachaName, gachaLink0, gachaLink1, gachaLink2 string
|
||||
var hash, reqGR, reqHR, extraIcon, gachaType int
|
||||
var hideFlag bool
|
||||
for shopEntries.Next() {
|
||||
err = shopEntries.Scan(&hash, &reqGR, &reqHR, &gachaName, &gachaLink0, &gachaLink1, &gachaLink2, &extraIcon, &gachaType, &hideFlag)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
resp.WriteUint32(uint32(hash))
|
||||
resp.WriteUint32(0) // only 0 in known packets
|
||||
resp.WriteUint32(0) // all of these seem to trigger the 'rank restriction'
|
||||
resp.WriteUint32(0) // message so they are presumably placeholders for a
|
||||
resp.WriteUint32(0) // Z Rank or similar that never turned up?
|
||||
resp.WriteUint32(uint32(reqGR))
|
||||
resp.WriteUint32(uint32(reqHR))
|
||||
resp.WriteUint32(0) // only 0 in known packet
|
||||
stringBytes := append([]byte(gachaName), 0x00)
|
||||
resp.WriteUint8(byte(len(stringBytes)))
|
||||
resp.WriteBytes(stringBytes)
|
||||
stringBytes = append([]byte(gachaLink0), 0x00)
|
||||
resp.WriteUint8(byte(len(stringBytes)))
|
||||
resp.WriteBytes(stringBytes)
|
||||
stringBytes = append([]byte(gachaLink1), 0x00)
|
||||
resp.WriteUint8(byte(len(stringBytes)))
|
||||
resp.WriteBytes(stringBytes)
|
||||
stringBytes = append([]byte(gachaLink2), 0x00)
|
||||
resp.WriteBool(hideFlag)
|
||||
resp.WriteUint8(uint8(len(stringBytes)))
|
||||
resp.WriteBytes(stringBytes)
|
||||
resp.WriteUint16(uint16(extraIcon))
|
||||
resp.WriteUint16(uint16(gachaType))
|
||||
gachaCount++
|
||||
}
|
||||
resp.Seek(0, 0)
|
||||
resp.WriteUint16(uint16(gachaCount))
|
||||
resp.WriteUint16(uint16(gachaCount))
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
|
||||
} else if pkt.ShopType == 7 {
|
||||
// GCP conversion store
|
||||
if pkt.ShopID == 0 {
|
||||
// Items to GCP exchange. Gou Tickets, Shiten Tickets, GP Tickets
|
||||
data, _ := hex.DecodeString("000300033a9186fb000033860000000a000100000000000000000000000000000000097fdb1c0000067e0000000a0001000000000000000000000000000000001374db29000027c300000064000100000000000000000000000000000000")
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
} else if pkt.ShopType == 8 {
|
||||
// Dive Defense sections
|
||||
// 00 = normal level limited exchange store, 05 = GCP skill store, 07 = limited quantity exchange
|
||||
if pkt.ShopID == 5 {
|
||||
// diva defense skill level limited store
|
||||
data, _ := hex.DecodeString("001f001f2c9365c1000000010000001e000a0000000000000000000a0000000000001979f1c2000000020000003c000a0000000000000000000a0000000000003e5197df000000030000003c000a0000000000000000000a000000000000219337c0000000040000001e000a0000000000000000000a00000000000009b24c9d000000140000001e000a0000000000000000000a0000000000001f1d496e000000150000001e000a0000000000000000000a0000000000003b918fcb000000160000003c000a0000000000000000000a0000000000000b7fd81c000000170000003c000a0000000000000000000a0000000000001374f239000000180000003c000a0000000000000000000a00000000000026950cba0000001c0000003c000a0000000000000000000a0000000000003797eae70000001d0000003c000a012b000000000000000a00000000000015758ad8000000050000003c00000000000000000000000a0000000000003c7035050000000600000050000a0000000000000001000a00000000000024f3b5560000000700000050000a0000000000000001000a00000000000000b600330000000800000050000a0000000000000001000a0000000000002efdce840000001900000050000a0000000000000001000a0000000000002d9365f10000001a00000050000a0000000000000001000a0000000000001979f3420000001f00000050000a012b000000000001000a0000000000003f5397cf0000002000000050000a012b000000000001000a000000000000319337c00000002100000050000a012b000000000001000a00000000000008b04cbd0000000900000064000a0000000000000002000a0000000000000b1d4b6e0000000a00000064000a0000000000000002000a0000000000003b918feb0000000b00000064000a0000000000000002000a0000000000001b7fd81c0000000c00000064000a0000000000000002000a0000000000001276f2290000000d00000064000a0000000000000002000a00000000000022950cba0000000e000000c8000a0000000000000002000a0000000000003697ead70000000f000001f4000a0000000000000003000a00000000000005758a5800000010000003e8000a0000000000000003000a0000000000003c7035250000001b000001f4000a0000000000010003000a00000000000034f3b5d60000001e00000064000a012b000000000003000a00000000000000b600030000002200000064000a0000000000010003000a000000000000")
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
} else {
|
||||
_, week := time.Now().ISOWeek()
|
||||
season := fmt.Sprintf("%d", week%4)
|
||||
shopEntries, err := s.server.db.Query("SELECT itemhash,itemID,Points,TradeQuantity,rankReqLow,rankReqHigh,rankReqG,storeLevelReq,maximumQuantity,boughtQuantity,roadFloorsRequired,weeklyFatalisKills, COALESCE(enable_weeks, '') FROM normal_shop_items WHERE shoptype=$1 AND shopid=$2", pkt.ShopType, pkt.ShopID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var ItemHash, entryCount int
|
||||
var itemID, Points, TradeQuantity, rankReqLow, rankReqHigh, rankReqG, storeLevelReq, maximumQuantity, boughtQuantity, roadFloorsRequired, weeklyFatalisKills, charQuantity uint16
|
||||
var itemWeeks string
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(0) // total defs
|
||||
for shopEntries.Next() {
|
||||
err = shopEntries.Scan(&ItemHash, &itemID, &Points, &TradeQuantity, &rankReqLow, &rankReqHigh, &rankReqG, &storeLevelReq, &maximumQuantity, &boughtQuantity, &roadFloorsRequired, &weeklyFatalisKills, &itemWeeks)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(itemWeeks) > 0 && !contains(strings.Split(itemWeeks, ","), season) {
|
||||
continue
|
||||
}
|
||||
|
||||
resp.WriteUint32(uint32(ItemHash))
|
||||
resp.WriteUint16(0) // unk, always 0 in existing packets
|
||||
resp.WriteUint16(itemID)
|
||||
resp.WriteUint16(0) // unk, always 0 in existing packets
|
||||
resp.WriteUint16(Points) // it's either item ID or quantity for gacha coins
|
||||
resp.WriteUint16(TradeQuantity) // only for item ID
|
||||
resp.WriteUint16(rankReqLow)
|
||||
resp.WriteUint16(rankReqHigh)
|
||||
resp.WriteUint16(rankReqG)
|
||||
resp.WriteUint16(storeLevelReq)
|
||||
resp.WriteUint16(maximumQuantity)
|
||||
if maximumQuantity > 0 {
|
||||
var itemWeek int
|
||||
err = s.server.db.QueryRow("SELECT COALESCE(usedquantity,0), COALESCE(week,-1) FROM shop_item_state WHERE itemhash=$1 AND char_id=$2", ItemHash, s.charID).Scan(&charQuantity, &itemWeek)
|
||||
if err != nil {
|
||||
resp.WriteUint16(0)
|
||||
} else if pkt.ShopID == 7 && itemWeek >= 0 && itemWeek != week {
|
||||
clearShopItemState(s, s.charID, uint32(ItemHash))
|
||||
resp.WriteUint16(0)
|
||||
} else {
|
||||
resp.WriteUint16(charQuantity)
|
||||
}
|
||||
} else {
|
||||
resp.WriteUint16(boughtQuantity)
|
||||
}
|
||||
resp.WriteUint16(roadFloorsRequired)
|
||||
resp.WriteUint16(weeklyFatalisKills)
|
||||
entryCount++
|
||||
}
|
||||
if entryCount == 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
return
|
||||
}
|
||||
resp.Seek(0, 0)
|
||||
resp.WriteUint16(uint16(entryCount))
|
||||
resp.WriteUint16(uint16(entryCount))
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireExchangeShop(s *Session, p mhfpacket.MHFPacket) {
|
||||
_, week := time.Now().ISOWeek()
|
||||
// writing out to an editable shop enumeration
|
||||
pkt := p.(*mhfpacket.MsgMhfAcquireExchangeShop)
|
||||
if pkt.DataSize == 10 {
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
|
||||
_ = bf.ReadUint16() // unk, always 1 in examples
|
||||
itemHash := bf.ReadUint32()
|
||||
buyCount := bf.ReadUint32()
|
||||
_, err := s.server.db.Exec(`INSERT INTO shop_item_state (char_id, itemhash, usedquantity, week)
|
||||
VALUES ($1,$2,$3,$4) ON CONFLICT (char_id, itemhash)
|
||||
DO UPDATE SET usedquantity = shop_item_state.usedquantity + $3
|
||||
WHERE EXCLUDED.char_id=$1 AND EXCLUDED.itemhash=$2`, s.charID, itemHash, buyCount, week)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update shop_item_state in db", zap.Error(err))
|
||||
}
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func clearShopItemState(s *Session, charId uint32, itemHash uint32) {
|
||||
_, err := s.server.db.Exec(`DELETE FROM shop_item_state WHERE char_id=$1 AND itemhash=$2`, charId, itemHash)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to delete shop_item_state in db", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfGetGachaPlayHistory(s *Session, p mhfpacket.MHFPacket) {
|
||||
// returns number of times the gacha was played, will need persistent db stuff
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGachaPlayHistory)
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x0A})
|
||||
}
|
||||
|
||||
func handleMsgMhfGetGachaPoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGachaPoint)
|
||||
var fp, gp, gt uint32
|
||||
_ = s.server.db.QueryRow("SELECT COALESCE(frontier_points, 0), COALESCE(gacha_prem, 0), COALESCE(gacha_trial,0) FROM characters WHERE id=$1", s.charID).Scan(&fp, &gp, >)
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(gp) // Real Gacha Points?
|
||||
resp.WriteUint32(gt) // Trial Gacha Point?
|
||||
resp.WriteUint32(fp) // Frontier Points?
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
type gachaItem struct {
|
||||
itemhash uint32
|
||||
percentage uint16
|
||||
rarityIcon byte
|
||||
itemCount byte
|
||||
itemType pq.Int64Array
|
||||
itemId pq.Int64Array
|
||||
quantity pq.Int64Array
|
||||
}
|
||||
|
||||
func (i gachaItem) Weight() int {
|
||||
return int(i.percentage)
|
||||
}
|
||||
|
||||
func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPlayNormalGacha)
|
||||
// needs to query db for input gacha and return a result or number of results
|
||||
// uint8 number of results
|
||||
// uint8 item type
|
||||
// uint16 item id
|
||||
// uint16 quantity
|
||||
|
||||
var currType, rarityIcon, rollsCount, itemCount byte
|
||||
var currQuant, currNumber, percentage uint16
|
||||
var itemhash uint32
|
||||
var itemType, itemId, quantity pq.Int64Array
|
||||
var items []lottery.Weighter
|
||||
// get info for updating data and calculating costs
|
||||
err := s.server.db.QueryRow("SELECT currType, currNumber, currQuant, rollsCount FROM gacha_shop_items WHERE shophash=$1 AND entryType=$2", pkt.GachaHash, pkt.RollType).Scan(&currType, &currNumber, &currQuant, &rollsCount)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// get existing items in storage if any
|
||||
var data []byte
|
||||
_ = s.server.db.QueryRow("SELECT gacha_items FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if len(data) == 0 {
|
||||
data = []byte{0x00}
|
||||
}
|
||||
// get gacha items and iterate through them for gacha roll
|
||||
shopEntries, err := s.server.db.Query("SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity FROM gacha_shop_items WHERE shophash=$1 AND entryType=100", pkt.GachaHash)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for shopEntries.Next() {
|
||||
err = shopEntries.Scan(&itemhash, &percentage, &rarityIcon, &itemCount, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
items = append(items, &gachaItem{itemhash: itemhash, percentage: percentage, rarityIcon: rarityIcon, itemCount: itemCount, itemType: itemType, itemId: itemId, quantity: quantity})
|
||||
}
|
||||
// execute rolls, build response and update database
|
||||
results := byte(0)
|
||||
resp := byteframe.NewByteFrame()
|
||||
dbUpdate := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(0) // results go here later
|
||||
l := lottery.NewDefaultLottery()
|
||||
for x := 0; x < int(rollsCount); x++ {
|
||||
ind := l.Draw(items)
|
||||
results += items[ind].(*gachaItem).itemCount
|
||||
for y := 0; y < int(items[ind].(*gachaItem).itemCount); y++ {
|
||||
// items in storage don't get rarity
|
||||
dbUpdate.WriteUint8(uint8(items[ind].(*gachaItem).itemType[y]))
|
||||
dbUpdate.WriteUint16(uint16(items[ind].(*gachaItem).itemId[y]))
|
||||
dbUpdate.WriteUint16(uint16(items[ind].(*gachaItem).quantity[y]))
|
||||
data = append(data, dbUpdate.Data()...)
|
||||
dbUpdate.Seek(0, 0)
|
||||
// response needs all item info and the rarity
|
||||
resp.WriteBytes(dbUpdate.Data())
|
||||
resp.WriteUint8(uint8(items[ind].(*gachaItem).rarityIcon))
|
||||
}
|
||||
}
|
||||
resp.Seek(0, 0)
|
||||
resp.WriteUint8(uint8(results))
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
|
||||
// add claimables to DB
|
||||
data[0] = data[0] + results
|
||||
_, err = s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", data, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update minidata in db", zap.Error(err))
|
||||
}
|
||||
// deduct gacha coins if relevant, items are handled fine by the standard savedata packet immediately afterwards
|
||||
if currType == 19 {
|
||||
_, err = s.server.db.Exec("UPDATE characters SET gacha_trial = CASE WHEN (gacha_trial > $1) then gacha_trial - $1 else gacha_trial end, gacha_prem = CASE WHEN NOT (gacha_trial > $1) then gacha_prem - $1 else gacha_prem end WHERE id=$2", currNumber, s.charID)
|
||||
}
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfUseGachaPoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
// should write to database when that's set up
|
||||
pkt := p.(*mhfpacket.MsgMhfUseGachaPoint)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfExchangeFpoint2Item(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfExchangeFpoint2Item)
|
||||
|
||||
var itemValue, quant int
|
||||
_ = s.server.db.QueryRow("SELECT quant, itemValue FROM fpoint_items WHERE hash=$1", pkt.ItemHash).Scan(&quant, &itemValue)
|
||||
itemCost := (int(pkt.Quantity) * quant) * itemValue
|
||||
|
||||
// also update frontierpoints entry in database
|
||||
_, err := s.server.db.Exec("UPDATE characters SET frontier_points=frontier_points::int - $1 WHERE id=$2", itemCost, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update minidata in db", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfExchangeItem2Fpoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfExchangeItem2Fpoint)
|
||||
|
||||
var itemValue, quant int
|
||||
_ = s.server.db.QueryRow("SELECT quant, itemValue FROM fpoint_items WHERE hash=$1", pkt.ItemHash).Scan(&quant, &itemValue)
|
||||
itemCost := (int(pkt.Quantity) / quant) * itemValue
|
||||
// also update frontierpoints entry in database
|
||||
_, err := s.server.db.Exec("UPDATE characters SET frontier_points=COALESCE(frontier_points::int + $1, $1) WHERE id=$2", itemCost, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update minidata in db", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfGetFpointExchangeList(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetFpointExchangeList)
|
||||
//absurd, probably lists every single item to trade to FP?
|
||||
|
||||
var buyables int
|
||||
var sellables int
|
||||
|
||||
buyRows, err := s.server.db.Query("SELECT hash,itemType,itemID,quant,itemValue FROM fpoint_items WHERE tradeType=0")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(0)
|
||||
var hash, itemType, itemID, quant, itemValue int
|
||||
for buyRows.Next() {
|
||||
err = buyRows.Scan(&hash, &itemType, &itemID, &quant, &itemValue)
|
||||
if err != nil {
|
||||
panic("Error in fpoint_items")
|
||||
}
|
||||
resp.WriteUint32(uint32(hash))
|
||||
resp.WriteUint32(0) // this and following only 0 in known packets
|
||||
resp.WriteUint16(0)
|
||||
resp.WriteUint8(byte(itemType))
|
||||
resp.WriteUint16(uint16(itemID))
|
||||
resp.WriteUint16(uint16(quant))
|
||||
resp.WriteUint16(uint16(itemValue))
|
||||
buyables++
|
||||
}
|
||||
|
||||
sellRows, err := s.server.db.Query("SELECT hash,itemType,itemID,quant,itemValue FROM fpoint_items WHERE tradeType=1")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for sellRows.Next() {
|
||||
err = sellRows.Scan(&hash, &itemType, &itemID, &quant, &itemValue)
|
||||
if err != nil {
|
||||
panic("Error in fpoint_items")
|
||||
}
|
||||
resp.WriteUint32(uint32(hash))
|
||||
resp.WriteUint32(0) // this and following only 0 in known packets
|
||||
resp.WriteUint16(0)
|
||||
resp.WriteUint8(byte(itemType))
|
||||
resp.WriteUint16(uint16(itemID))
|
||||
resp.WriteUint16(uint16(quant))
|
||||
resp.WriteUint16(uint16(itemValue))
|
||||
sellables++
|
||||
}
|
||||
resp.Seek(0, 0)
|
||||
resp.WriteUint16(uint16(sellables))
|
||||
resp.WriteUint16(uint16(buyables))
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfPlayStepupGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPlayStepupGacha)
|
||||
results := byte(0)
|
||||
stepResults := byte(0)
|
||||
resp := byteframe.NewByteFrame()
|
||||
rollFrame := byteframe.NewByteFrame()
|
||||
stepFrame := byteframe.NewByteFrame()
|
||||
stepData := []byte{}
|
||||
var currType, rarityIcon, rollsCount, itemCount byte
|
||||
var currQuant, currNumber, percentage uint16
|
||||
var itemhash uint32
|
||||
var itemType, itemId, quantity pq.Int64Array
|
||||
var items []lottery.Weighter
|
||||
// get info for updating data and calculating costs
|
||||
err := s.server.db.QueryRow("SELECT currType, currNumber, currQuant, rollsCount, itemCount, itemType, itemId, quantity FROM gacha_shop_items WHERE shophash=$1 AND entryType=$2", pkt.GachaHash, pkt.RollType).Scan(&currType, &currNumber, &currQuant, &rollsCount, &itemCount, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// get existing items in storage if any
|
||||
var data []byte
|
||||
_ = s.server.db.QueryRow("SELECT gacha_items FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if len(data) == 0 {
|
||||
data = []byte{0x00}
|
||||
}
|
||||
// roll definition includes items with step up gachas that are appended last
|
||||
for x := 0; x < int(itemCount); x++ {
|
||||
stepFrame.WriteUint8(uint8(itemType[x]))
|
||||
stepFrame.WriteUint16(uint16(itemId[x]))
|
||||
stepFrame.WriteUint16(uint16(quantity[x]))
|
||||
stepData = append(stepData, stepFrame.Data()...)
|
||||
stepFrame.WriteUint8(0) // rarity still defined
|
||||
stepResults++
|
||||
}
|
||||
// get gacha items and iterate through them for gacha roll
|
||||
shopEntries, err := s.server.db.Query("SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity FROM gacha_shop_items WHERE shophash=$1 AND entryType=100", pkt.GachaHash)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for shopEntries.Next() {
|
||||
err = shopEntries.Scan(&itemhash, &percentage, &rarityIcon, &itemCount, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
items = append(items, &gachaItem{itemhash: itemhash, percentage: percentage, rarityIcon: rarityIcon, itemCount: itemCount, itemType: itemType, itemId: itemId, quantity: quantity})
|
||||
}
|
||||
// execute rolls, build response and update database
|
||||
resp.WriteUint16(0) // results count goes here later
|
||||
l := lottery.NewDefaultLottery()
|
||||
for x := 0; x < int(rollsCount); x++ {
|
||||
ind := l.Draw(items)
|
||||
results += items[ind].(*gachaItem).itemCount
|
||||
for y := 0; y < int(items[ind].(*gachaItem).itemCount); y++ {
|
||||
// items in storage don't get rarity
|
||||
rollFrame.WriteUint8(uint8(items[ind].(*gachaItem).itemType[y]))
|
||||
rollFrame.WriteUint16(uint16(items[ind].(*gachaItem).itemId[y]))
|
||||
rollFrame.WriteUint16(uint16(items[ind].(*gachaItem).quantity[y]))
|
||||
data = append(data, rollFrame.Data()...)
|
||||
rollFrame.Seek(0, 0)
|
||||
// response needs all item info and the rarity
|
||||
resp.WriteBytes(rollFrame.Data())
|
||||
resp.WriteUint8(uint8(items[ind].(*gachaItem).rarityIcon))
|
||||
}
|
||||
}
|
||||
resp.WriteBytes(stepFrame.Data())
|
||||
resp.Seek(0, 0)
|
||||
resp.WriteUint8(uint8(results + stepResults))
|
||||
resp.WriteUint8(uint8(results))
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
|
||||
// add claimables to DB
|
||||
data = append(data, stepData...)
|
||||
data[0] = data[0] + results + stepResults
|
||||
_, err = s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", data, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err))
|
||||
}
|
||||
// deduct gacha coins if relevant, items are handled fine by the standard savedata packet immediately afterwards
|
||||
// reduce real if trial don't cover cost
|
||||
if currType == 19 {
|
||||
_, err = s.server.db.Exec(`UPDATE characters
|
||||
SET gacha_trial = CASE WHEN (gacha_trial > $1) then gacha_trial - $1 else gacha_trial end,
|
||||
gacha_prem = CASE WHEN NOT (gacha_trial > $1) then gacha_prem - $1 else gacha_prem end
|
||||
WHERE id=$2`, currNumber, s.charID)
|
||||
|
||||
}
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err))
|
||||
}
|
||||
// update step progression
|
||||
_, err = s.server.db.Exec("UPDATE stepup_state SET step_progression = $1 WHERE char_id = $2", pkt.RollType+1, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update step_progression in db", zap.Error(err))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func handleMsgMhfReceiveGachaItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReceiveGachaItem)
|
||||
// persistent for claimable items on cat
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT COALESCE(gacha_items, $2) FROM characters WHERE id = $1", s.charID, []byte{0x00}).Scan(&data)
|
||||
if err != nil {
|
||||
panic("Failed to get gacha_items")
|
||||
}
|
||||
// limit of 36 items are returned
|
||||
if data[0] > 36 {
|
||||
outData := make([]byte, 181)
|
||||
copy(outData, data[0:181])
|
||||
outData[0] = byte(36)
|
||||
saveData := append(data[:1], data[181:]...)
|
||||
saveData[0] = saveData[0] - 36
|
||||
doAckBufSucceed(s, pkt.AckHandle, outData)
|
||||
if pkt.Unk0 != 0x2401 {
|
||||
_, err := s.server.db.Exec("UPDATE characters SET gacha_items = $2 WHERE id = $1", s.charID, saveData)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
if pkt.Unk0 != 0x2401 {
|
||||
_, err := s.server.db.Exec("UPDATE characters SET gacha_items = null WHERE id = $1", s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfGetStepupStatus(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetStepupStatus)
|
||||
// get the reset time from db
|
||||
var step_progression int
|
||||
var step_time time.Time
|
||||
err := s.server.db.QueryRow(`SELECT COALESCE(step_progression, 0), COALESCE(step_time, $1) FROM stepup_state WHERE char_id = $2 AND shophash = $3`, time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), s.charID, pkt.GachaHash).Scan(&step_progression, &step_time)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to Select coalesce in db", zap.Error(err))
|
||||
}
|
||||
|
||||
// calculate next midday
|
||||
var t = time.Now().In(time.FixedZone("UTC+9", 9*60*60))
|
||||
year, month, day := t.Date()
|
||||
midday := time.Date(year, month, day, 12, 0, 0, 0, t.Location())
|
||||
if t.After(midday) {
|
||||
midday = midday.Add(24 * time.Hour)
|
||||
}
|
||||
// after midday or not set
|
||||
if t.After(step_time) {
|
||||
step_progression = 0
|
||||
}
|
||||
_, err = s.server.db.Exec(`INSERT INTO stepup_state (shophash, step_progression, step_time, char_id)
|
||||
VALUES ($1,$2,$3,$4) ON CONFLICT (shophash, char_id)
|
||||
DO UPDATE SET step_progression=$2, step_time=$3
|
||||
WHERE EXCLUDED.char_id=$4 AND EXCLUDED.shophash=$1`, pkt.GachaHash, step_progression, midday, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update platedata savedata in db", zap.Error(err))
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(uint8(step_progression))
|
||||
resp.WriteUint32(uint32(time.Now().In(time.FixedZone("UTC+9", 9*60*60)).Unix()))
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfPlayFreeGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
// not sure this is used anywhere, free gachas use the MSG_MHF_PLAY_NORMAL_GACHA method in captures
|
||||
}
|
||||
|
||||
func handleMsgMhfGetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetBoxGachaInfo)
|
||||
count := 0
|
||||
var used_itemhash pq.Int64Array
|
||||
// pull array of used values
|
||||
// single sized respone with 0x00 is a valid with no items present
|
||||
_ = s.server.db.QueryRow("SELECT used_itemhash FROM lucky_box_state WHERE shophash=$1 AND char_id=$2", pkt.GachaHash, s.charID).Scan((*pq.Int64Array)(&used_itemhash))
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(0)
|
||||
for ind := range used_itemhash {
|
||||
resp.WriteUint32(uint32(used_itemhash[ind]))
|
||||
resp.WriteUint8(1)
|
||||
count++
|
||||
}
|
||||
resp.Seek(0, 0)
|
||||
resp.WriteUint8(uint8(count))
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfPlayBoxGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPlayBoxGacha)
|
||||
// needs to query db for input gacha and return a result or number of results
|
||||
// uint8 number of results
|
||||
// uint8 item type
|
||||
// uint16 item id
|
||||
// uint16 quantity
|
||||
|
||||
var currType, rarityIcon, rollsCount, itemCount byte
|
||||
var currQuant, currNumber, percentage uint16
|
||||
var itemhash uint32
|
||||
var itemType, itemId, quantity, usedItemHash pq.Int64Array
|
||||
var items []lottery.Weighter
|
||||
// get info for updating data and calculating costs
|
||||
err := s.server.db.QueryRow("SELECT currType, currNumber, currQuant, rollsCount FROM gacha_shop_items WHERE shophash=$1 AND entryType=$2", pkt.GachaHash, pkt.RollType).Scan(&currType, &currNumber, &currQuant, &rollsCount)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// get existing items in storage if any
|
||||
var data []byte
|
||||
_ = s.server.db.QueryRow("SELECT gacha_items FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if len(data) == 0 {
|
||||
data = []byte{0x00}
|
||||
}
|
||||
// get gacha items and iterate through them for gacha roll
|
||||
shopEntries, err := s.server.db.Query(`SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity
|
||||
FROM gacha_shop_items
|
||||
WHERE shophash=$1 AND entryType=100
|
||||
EXCEPT ALL SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity
|
||||
FROM gacha_shop_items gsi JOIN lucky_box_state lbs ON gsi.itemhash = ANY(lbs.used_itemhash)
|
||||
WHERE lbs.char_id=$2`, pkt.GachaHash, s.charID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for shopEntries.Next() {
|
||||
err = shopEntries.Scan(&itemhash, &percentage, &rarityIcon, &itemCount, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
items = append(items, &gachaItem{itemhash: itemhash, percentage: percentage, rarityIcon: rarityIcon, itemCount: itemCount, itemType: itemType, itemId: itemId, quantity: quantity})
|
||||
}
|
||||
// execute rolls, build response and update database
|
||||
results := byte(0)
|
||||
resp := byteframe.NewByteFrame()
|
||||
dbUpdate := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(0) // results go here later
|
||||
l := lottery.NewDefaultLottery()
|
||||
for x := 0; x < int(rollsCount); x++ {
|
||||
ind := l.Draw(items)
|
||||
results += items[ind].(*gachaItem).itemCount
|
||||
for y := 0; y < int(items[ind].(*gachaItem).itemCount); y++ {
|
||||
// items in storage don't get rarity
|
||||
dbUpdate.WriteUint8(uint8(items[ind].(*gachaItem).itemType[y]))
|
||||
dbUpdate.WriteUint16(uint16(items[ind].(*gachaItem).itemId[y]))
|
||||
dbUpdate.WriteUint16(uint16(items[ind].(*gachaItem).quantity[y]))
|
||||
data = append(data, dbUpdate.Data()...)
|
||||
dbUpdate.Seek(0, 0)
|
||||
// response needs all item info and the rarity
|
||||
resp.WriteBytes(dbUpdate.Data())
|
||||
resp.WriteUint8(uint8(items[ind].(*gachaItem).rarityIcon))
|
||||
|
||||
usedItemHash = append(usedItemHash, int64(items[ind].(*gachaItem).itemhash))
|
||||
}
|
||||
// remove rolled
|
||||
items = append(items[:ind], items[ind+1:]...)
|
||||
}
|
||||
resp.Seek(0, 0)
|
||||
resp.WriteUint8(uint8(results))
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
|
||||
// add claimables to DB
|
||||
data[0] = data[0] + results
|
||||
_, err = s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", data, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err))
|
||||
}
|
||||
// update lucky_box_state
|
||||
_, err = s.server.db.Exec(` INSERT INTO lucky_box_state (char_id, shophash, used_itemhash)
|
||||
VALUES ($1,$2,$3) ON CONFLICT (char_id, shophash)
|
||||
DO UPDATE SET used_itemhash = COALESCE(lucky_box_state.used_itemhash::int[] || $3::int[], $3::int[])
|
||||
WHERE EXCLUDED.char_id=$1 AND EXCLUDED.shophash=$2`, s.charID, pkt.GachaHash, usedItemHash)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update lucky box state in db", zap.Error(err))
|
||||
}
|
||||
// deduct gacha coins if relevant, items are handled fine by the standard savedata packet immediately afterwards
|
||||
if currType == 19 {
|
||||
_, err = s.server.db.Exec(` UPDATE characters
|
||||
SET gacha_trial = CASE WHEN (gacha_trial > $1) then gacha_trial - $1 else gacha_trial end, gacha_prem = CASE WHEN NOT (gacha_trial > $1) then gacha_prem - $1 else gacha_prem end
|
||||
WHERE id=$2`, currNumber, s.charID)
|
||||
}
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update gacha_trial in db", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfResetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfResetBoxGachaInfo)
|
||||
_, err := s.server.db.Exec("DELETE FROM lucky_box_state WHERE shophash=$1 AND char_id=$2", pkt.GachaHash, s.charID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
Reference in New Issue
Block a user