mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
refactor(channelserver): split handlers_shop_gacha.go into shop and gacha
Separate the two distinct systems into focused files: - handlers_shop.go: item shops, exchange shops, frontier point trading - handlers_gacha.go: normal/stepup/box/free gacha, coin management
This commit is contained in:
@@ -2,29 +2,12 @@ package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
_config "erupe-ce/config"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"math/rand"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type ShopItem struct {
|
||||
ID uint32 `db:"id"`
|
||||
ItemID uint32 `db:"item_id"`
|
||||
Cost uint32 `db:"cost"`
|
||||
Quantity uint16 `db:"quantity"`
|
||||
MinHR uint16 `db:"min_hr"`
|
||||
MinSR uint16 `db:"min_sr"`
|
||||
MinGR uint16 `db:"min_gr"`
|
||||
StoreLevel uint8 `db:"store_level"`
|
||||
MaxQuantity uint16 `db:"max_quantity"`
|
||||
UsedQuantity uint16 `db:"used_quantity"`
|
||||
RoadFloors uint16 `db:"road_floors"`
|
||||
RoadFatalis uint16 `db:"road_fatalis"`
|
||||
}
|
||||
|
||||
type Gacha struct {
|
||||
ID uint32 `db:"id"`
|
||||
MinGR uint32 `db:"min_gr"`
|
||||
@@ -59,230 +42,6 @@ type GachaItem struct {
|
||||
Quantity uint16 `db:"quantity"`
|
||||
}
|
||||
|
||||
func writeShopItems(bf *byteframe.ByteFrame, items []ShopItem) {
|
||||
bf.WriteUint16(uint16(len(items)))
|
||||
bf.WriteUint16(uint16(len(items)))
|
||||
for _, item := range items {
|
||||
if _config.ErupeConfig.RealClientMode >= _config.Z2 {
|
||||
bf.WriteUint32(item.ID)
|
||||
}
|
||||
bf.WriteUint32(item.ItemID)
|
||||
bf.WriteUint32(item.Cost)
|
||||
bf.WriteUint16(item.Quantity)
|
||||
bf.WriteUint16(item.MinHR)
|
||||
bf.WriteUint16(item.MinSR)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.Z2 {
|
||||
bf.WriteUint16(item.MinGR)
|
||||
}
|
||||
bf.WriteUint8(0) // Unk
|
||||
bf.WriteUint8(item.StoreLevel)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.Z2 {
|
||||
bf.WriteUint16(item.MaxQuantity)
|
||||
bf.WriteUint16(item.UsedQuantity)
|
||||
}
|
||||
if _config.ErupeConfig.RealClientMode == _config.Z1 {
|
||||
bf.WriteUint8(uint8(item.RoadFloors))
|
||||
bf.WriteUint8(uint8(item.RoadFatalis))
|
||||
} else if _config.ErupeConfig.RealClientMode >= _config.Z2 {
|
||||
bf.WriteUint16(item.RoadFloors)
|
||||
bf.WriteUint16(item.RoadFatalis)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getShopItems(s *Session, shopType uint8, shopID uint32) []ShopItem {
|
||||
var items []ShopItem
|
||||
var temp ShopItem
|
||||
rows, err := s.server.db.Queryx(`SELECT id, item_id, cost, quantity, min_hr, min_sr, min_gr, store_level, max_quantity,
|
||||
COALESCE((SELECT bought FROM shop_items_bought WHERE shop_item_id=si.id AND character_id=$3), 0) as used_quantity,
|
||||
road_floors, road_fatalis FROM shop_items si WHERE shop_type=$1 AND shop_id=$2
|
||||
`, shopType, shopID, s.charID)
|
||||
if err == nil {
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&temp)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
items = append(items, temp)
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateShop)
|
||||
// Generic Shop IDs
|
||||
// 0: basic item
|
||||
// 1: gatherables
|
||||
// 2: hr1-4 materials
|
||||
// 3: hr5-7 materials
|
||||
// 4: decos
|
||||
// 5: other item
|
||||
// 6: g mats
|
||||
// 7: limited item
|
||||
// 8: special item
|
||||
switch pkt.ShopType {
|
||||
case 1: // Running gachas
|
||||
// Fundamentally, gacha works completely differently, just hide it for now.
|
||||
if _config.ErupeConfig.RealClientMode <= _config.G7 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
|
||||
rows, err := s.server.db.Queryx("SELECT id, min_gr, min_hr, name, url_banner, url_feature, url_thumbnail, wide, recommended, gacha_type, hidden FROM gacha_shop")
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
var gacha Gacha
|
||||
var gachas []Gacha
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&gacha)
|
||||
if err == nil {
|
||||
gachas = append(gachas, gacha)
|
||||
}
|
||||
}
|
||||
bf.WriteUint16(uint16(len(gachas)))
|
||||
bf.WriteUint16(uint16(len(gachas)))
|
||||
for _, g := range gachas {
|
||||
bf.WriteUint32(g.ID)
|
||||
bf.WriteUint32(0) // Unknown rank restrictions
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(g.MinGR)
|
||||
bf.WriteUint32(g.MinHR)
|
||||
bf.WriteUint32(0) // only 0 in known packet
|
||||
ps.Uint8(bf, g.Name, true)
|
||||
ps.Uint8(bf, g.URLBanner, false)
|
||||
ps.Uint8(bf, g.URLFeature, false)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G10 {
|
||||
bf.WriteBool(g.Wide)
|
||||
ps.Uint8(bf, g.URLThumbnail, false)
|
||||
}
|
||||
if g.Recommended {
|
||||
bf.WriteUint16(2)
|
||||
} else {
|
||||
bf.WriteUint16(0)
|
||||
}
|
||||
bf.WriteUint8(g.GachaType)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G10 {
|
||||
bf.WriteBool(g.Hidden)
|
||||
}
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
case 2: // Actual gacha
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(pkt.ShopID)
|
||||
var gachaType int
|
||||
_ = s.server.db.QueryRow(`SELECT gacha_type FROM gacha_shop WHERE id = $1`, pkt.ShopID).Scan(&gachaType)
|
||||
rows, err := s.server.db.Queryx(`SELECT entry_type, id, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points, COALESCE(name, '') AS name FROM gacha_entries WHERE gacha_id = $1 ORDER BY weight DESC`, pkt.ShopID)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
var divisor float64
|
||||
_ = s.server.db.QueryRow(`SELECT COALESCE(SUM(weight) / 100000.0, 0) AS chance FROM gacha_entries WHERE gacha_id = $1`, pkt.ShopID).Scan(&divisor)
|
||||
|
||||
var entry GachaEntry
|
||||
var entries []GachaEntry
|
||||
var item GachaItem
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&entry)
|
||||
if err == nil {
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
}
|
||||
bf.WriteUint16(uint16(len(entries)))
|
||||
for _, ge := range entries {
|
||||
var items []GachaItem
|
||||
bf.WriteUint8(ge.EntryType)
|
||||
bf.WriteUint32(ge.ID)
|
||||
bf.WriteUint8(ge.ItemType)
|
||||
bf.WriteUint32(ge.ItemNumber)
|
||||
bf.WriteUint16(ge.ItemQuantity)
|
||||
if gachaType >= 4 { // If box
|
||||
bf.WriteUint16(1)
|
||||
} else {
|
||||
bf.WriteUint16(uint16(ge.Weight / divisor))
|
||||
}
|
||||
bf.WriteUint8(ge.Rarity)
|
||||
bf.WriteUint8(ge.Rolls)
|
||||
|
||||
rows, err = s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id=$1`, ge.ID)
|
||||
if err != nil {
|
||||
bf.WriteUint8(0)
|
||||
} else {
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&item)
|
||||
if err == nil {
|
||||
items = append(items, item)
|
||||
}
|
||||
}
|
||||
bf.WriteUint8(uint8(len(items)))
|
||||
}
|
||||
|
||||
bf.WriteUint16(ge.FrontierPoints)
|
||||
bf.WriteUint8(ge.DailyLimit)
|
||||
if ge.EntryType < 10 {
|
||||
ps.Uint8(bf, ge.Name, true)
|
||||
} else {
|
||||
bf.WriteUint8(0)
|
||||
}
|
||||
for _, gi := range items {
|
||||
bf.WriteUint16(uint16(gi.ItemType))
|
||||
bf.WriteUint16(gi.ItemID)
|
||||
bf.WriteUint16(gi.Quantity)
|
||||
}
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
case 3: // Hunting Festival Exchange
|
||||
fallthrough
|
||||
case 4: // N Points, 0-6
|
||||
fallthrough
|
||||
case 5: // GCP->Item, 0-6
|
||||
fallthrough
|
||||
case 6: // Gacha coin->Item
|
||||
fallthrough
|
||||
case 7: // Item->GCP
|
||||
fallthrough
|
||||
case 8: // Diva
|
||||
fallthrough
|
||||
case 9: // Diva song shop
|
||||
fallthrough
|
||||
case 10: // Item shop, 0-8
|
||||
bf := byteframe.NewByteFrame()
|
||||
items := getShopItems(s, pkt.ShopType, pkt.ShopID)
|
||||
if len(items) > int(pkt.Limit) {
|
||||
items = items[:pkt.Limit]
|
||||
}
|
||||
writeShopItems(bf, items)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireExchangeShop(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAcquireExchangeShop)
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
|
||||
exchanges := int(bf.ReadUint16())
|
||||
for i := 0; i < exchanges; i++ {
|
||||
itemHash := bf.ReadUint32()
|
||||
if itemHash == 0 {
|
||||
continue
|
||||
}
|
||||
buyCount := bf.ReadUint32()
|
||||
if _, err := s.server.db.Exec(`INSERT INTO shop_items_bought (character_id, shop_item_id, bought)
|
||||
VALUES ($1,$2,$3) ON CONFLICT (character_id, shop_item_id)
|
||||
DO UPDATE SET bought = bought + $3
|
||||
WHERE EXCLUDED.character_id=$1 AND EXCLUDED.shop_item_id=$2
|
||||
`, s.charID, itemHash, buyCount); err != nil {
|
||||
s.logger.Error("Failed to update shop item purchase count", zap.Error(err))
|
||||
}
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfGetGachaPlayHistory(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGachaPlayHistory)
|
||||
bf := byteframe.NewByteFrame()
|
||||
@@ -684,80 +443,6 @@ func handleMsgMhfResetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfExchangeFpoint2Item(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfExchangeFpoint2Item)
|
||||
var balance uint32
|
||||
var itemValue, quantity int
|
||||
_ = s.server.db.QueryRow("SELECT quantity, fpoints FROM fpoint_items WHERE id=$1", pkt.TradeID).Scan(&quantity, &itemValue)
|
||||
cost := (int(pkt.Quantity) * quantity) * itemValue
|
||||
_ = s.server.db.QueryRow("UPDATE users u SET frontier_points=frontier_points::int - $1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2) RETURNING frontier_points", cost, s.charID).Scan(&balance)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(balance)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfExchangeItem2Fpoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfExchangeItem2Fpoint)
|
||||
var balance uint32
|
||||
var itemValue, quantity int
|
||||
_ = s.server.db.QueryRow("SELECT quantity, fpoints FROM fpoint_items WHERE id=$1", pkt.TradeID).Scan(&quantity, &itemValue)
|
||||
cost := (int(pkt.Quantity) / quantity) * itemValue
|
||||
_ = s.server.db.QueryRow("UPDATE users u SET frontier_points=COALESCE(frontier_points::int + $1, $1) WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2) RETURNING frontier_points", cost, s.charID).Scan(&balance)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(balance)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
type FPointExchange struct {
|
||||
ID uint32 `db:"id"`
|
||||
ItemType uint8 `db:"item_type"`
|
||||
ItemID uint16 `db:"item_id"`
|
||||
Quantity uint16 `db:"quantity"`
|
||||
FPoints uint16 `db:"fpoints"`
|
||||
Buyable bool `db:"buyable"`
|
||||
}
|
||||
|
||||
func handleMsgMhfGetFpointExchangeList(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetFpointExchangeList)
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
var exchange FPointExchange
|
||||
var exchanges []FPointExchange
|
||||
var buyables uint16
|
||||
rows, err := s.server.db.Queryx(`SELECT id, item_type, item_id, quantity, fpoints, buyable FROM fpoint_items ORDER BY buyable DESC`)
|
||||
if err == nil {
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&exchange)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if exchange.Buyable {
|
||||
buyables++
|
||||
}
|
||||
exchanges = append(exchanges, exchange)
|
||||
}
|
||||
}
|
||||
if _config.ErupeConfig.RealClientMode <= _config.Z2 {
|
||||
bf.WriteUint8(uint8(len(exchanges)))
|
||||
bf.WriteUint8(uint8(buyables))
|
||||
} else {
|
||||
bf.WriteUint16(uint16(len(exchanges)))
|
||||
bf.WriteUint16(buyables)
|
||||
}
|
||||
for _, e := range exchanges {
|
||||
bf.WriteUint32(e.ID)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint8(e.ItemType)
|
||||
bf.WriteUint16(e.ItemID)
|
||||
bf.WriteUint16(e.Quantity)
|
||||
bf.WriteUint16(e.FPoints)
|
||||
}
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfPlayFreeGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPlayFreeGacha)
|
||||
bf := byteframe.NewByteFrame()
|
||||
323
server/channelserver/handlers_shop.go
Normal file
323
server/channelserver/handlers_shop.go
Normal file
@@ -0,0 +1,323 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
_config "erupe-ce/config"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type ShopItem struct {
|
||||
ID uint32 `db:"id"`
|
||||
ItemID uint32 `db:"item_id"`
|
||||
Cost uint32 `db:"cost"`
|
||||
Quantity uint16 `db:"quantity"`
|
||||
MinHR uint16 `db:"min_hr"`
|
||||
MinSR uint16 `db:"min_sr"`
|
||||
MinGR uint16 `db:"min_gr"`
|
||||
StoreLevel uint8 `db:"store_level"`
|
||||
MaxQuantity uint16 `db:"max_quantity"`
|
||||
UsedQuantity uint16 `db:"used_quantity"`
|
||||
RoadFloors uint16 `db:"road_floors"`
|
||||
RoadFatalis uint16 `db:"road_fatalis"`
|
||||
}
|
||||
|
||||
func writeShopItems(bf *byteframe.ByteFrame, items []ShopItem) {
|
||||
bf.WriteUint16(uint16(len(items)))
|
||||
bf.WriteUint16(uint16(len(items)))
|
||||
for _, item := range items {
|
||||
if _config.ErupeConfig.RealClientMode >= _config.Z2 {
|
||||
bf.WriteUint32(item.ID)
|
||||
}
|
||||
bf.WriteUint32(item.ItemID)
|
||||
bf.WriteUint32(item.Cost)
|
||||
bf.WriteUint16(item.Quantity)
|
||||
bf.WriteUint16(item.MinHR)
|
||||
bf.WriteUint16(item.MinSR)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.Z2 {
|
||||
bf.WriteUint16(item.MinGR)
|
||||
}
|
||||
bf.WriteUint8(0) // Unk
|
||||
bf.WriteUint8(item.StoreLevel)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.Z2 {
|
||||
bf.WriteUint16(item.MaxQuantity)
|
||||
bf.WriteUint16(item.UsedQuantity)
|
||||
}
|
||||
if _config.ErupeConfig.RealClientMode == _config.Z1 {
|
||||
bf.WriteUint8(uint8(item.RoadFloors))
|
||||
bf.WriteUint8(uint8(item.RoadFatalis))
|
||||
} else if _config.ErupeConfig.RealClientMode >= _config.Z2 {
|
||||
bf.WriteUint16(item.RoadFloors)
|
||||
bf.WriteUint16(item.RoadFatalis)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getShopItems(s *Session, shopType uint8, shopID uint32) []ShopItem {
|
||||
var items []ShopItem
|
||||
var temp ShopItem
|
||||
rows, err := s.server.db.Queryx(`SELECT id, item_id, cost, quantity, min_hr, min_sr, min_gr, store_level, max_quantity,
|
||||
COALESCE((SELECT bought FROM shop_items_bought WHERE shop_item_id=si.id AND character_id=$3), 0) as used_quantity,
|
||||
road_floors, road_fatalis FROM shop_items si WHERE shop_type=$1 AND shop_id=$2
|
||||
`, shopType, shopID, s.charID)
|
||||
if err == nil {
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&temp)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
items = append(items, temp)
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateShop)
|
||||
// Generic Shop IDs
|
||||
// 0: basic item
|
||||
// 1: gatherables
|
||||
// 2: hr1-4 materials
|
||||
// 3: hr5-7 materials
|
||||
// 4: decos
|
||||
// 5: other item
|
||||
// 6: g mats
|
||||
// 7: limited item
|
||||
// 8: special item
|
||||
switch pkt.ShopType {
|
||||
case 1: // Running gachas
|
||||
// Fundamentally, gacha works completely differently, just hide it for now.
|
||||
if _config.ErupeConfig.RealClientMode <= _config.G7 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
|
||||
rows, err := s.server.db.Queryx("SELECT id, min_gr, min_hr, name, url_banner, url_feature, url_thumbnail, wide, recommended, gacha_type, hidden FROM gacha_shop")
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
var gacha Gacha
|
||||
var gachas []Gacha
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&gacha)
|
||||
if err == nil {
|
||||
gachas = append(gachas, gacha)
|
||||
}
|
||||
}
|
||||
bf.WriteUint16(uint16(len(gachas)))
|
||||
bf.WriteUint16(uint16(len(gachas)))
|
||||
for _, g := range gachas {
|
||||
bf.WriteUint32(g.ID)
|
||||
bf.WriteUint32(0) // Unknown rank restrictions
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(g.MinGR)
|
||||
bf.WriteUint32(g.MinHR)
|
||||
bf.WriteUint32(0) // only 0 in known packet
|
||||
ps.Uint8(bf, g.Name, true)
|
||||
ps.Uint8(bf, g.URLBanner, false)
|
||||
ps.Uint8(bf, g.URLFeature, false)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G10 {
|
||||
bf.WriteBool(g.Wide)
|
||||
ps.Uint8(bf, g.URLThumbnail, false)
|
||||
}
|
||||
if g.Recommended {
|
||||
bf.WriteUint16(2)
|
||||
} else {
|
||||
bf.WriteUint16(0)
|
||||
}
|
||||
bf.WriteUint8(g.GachaType)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G10 {
|
||||
bf.WriteBool(g.Hidden)
|
||||
}
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
case 2: // Actual gacha
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(pkt.ShopID)
|
||||
var gachaType int
|
||||
_ = s.server.db.QueryRow(`SELECT gacha_type FROM gacha_shop WHERE id = $1`, pkt.ShopID).Scan(&gachaType)
|
||||
rows, err := s.server.db.Queryx(`SELECT entry_type, id, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points, COALESCE(name, '') AS name FROM gacha_entries WHERE gacha_id = $1 ORDER BY weight DESC`, pkt.ShopID)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
var divisor float64
|
||||
_ = s.server.db.QueryRow(`SELECT COALESCE(SUM(weight) / 100000.0, 0) AS chance FROM gacha_entries WHERE gacha_id = $1`, pkt.ShopID).Scan(&divisor)
|
||||
|
||||
var entry GachaEntry
|
||||
var entries []GachaEntry
|
||||
var item GachaItem
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&entry)
|
||||
if err == nil {
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
}
|
||||
bf.WriteUint16(uint16(len(entries)))
|
||||
for _, ge := range entries {
|
||||
var items []GachaItem
|
||||
bf.WriteUint8(ge.EntryType)
|
||||
bf.WriteUint32(ge.ID)
|
||||
bf.WriteUint8(ge.ItemType)
|
||||
bf.WriteUint32(ge.ItemNumber)
|
||||
bf.WriteUint16(ge.ItemQuantity)
|
||||
if gachaType >= 4 { // If box
|
||||
bf.WriteUint16(1)
|
||||
} else {
|
||||
bf.WriteUint16(uint16(ge.Weight / divisor))
|
||||
}
|
||||
bf.WriteUint8(ge.Rarity)
|
||||
bf.WriteUint8(ge.Rolls)
|
||||
|
||||
rows, err = s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id=$1`, ge.ID)
|
||||
if err != nil {
|
||||
bf.WriteUint8(0)
|
||||
} else {
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&item)
|
||||
if err == nil {
|
||||
items = append(items, item)
|
||||
}
|
||||
}
|
||||
bf.WriteUint8(uint8(len(items)))
|
||||
}
|
||||
|
||||
bf.WriteUint16(ge.FrontierPoints)
|
||||
bf.WriteUint8(ge.DailyLimit)
|
||||
if ge.EntryType < 10 {
|
||||
ps.Uint8(bf, ge.Name, true)
|
||||
} else {
|
||||
bf.WriteUint8(0)
|
||||
}
|
||||
for _, gi := range items {
|
||||
bf.WriteUint16(uint16(gi.ItemType))
|
||||
bf.WriteUint16(gi.ItemID)
|
||||
bf.WriteUint16(gi.Quantity)
|
||||
}
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
case 3: // Hunting Festival Exchange
|
||||
fallthrough
|
||||
case 4: // N Points, 0-6
|
||||
fallthrough
|
||||
case 5: // GCP->Item, 0-6
|
||||
fallthrough
|
||||
case 6: // Gacha coin->Item
|
||||
fallthrough
|
||||
case 7: // Item->GCP
|
||||
fallthrough
|
||||
case 8: // Diva
|
||||
fallthrough
|
||||
case 9: // Diva song shop
|
||||
fallthrough
|
||||
case 10: // Item shop, 0-8
|
||||
bf := byteframe.NewByteFrame()
|
||||
items := getShopItems(s, pkt.ShopType, pkt.ShopID)
|
||||
if len(items) > int(pkt.Limit) {
|
||||
items = items[:pkt.Limit]
|
||||
}
|
||||
writeShopItems(bf, items)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireExchangeShop(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAcquireExchangeShop)
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
|
||||
exchanges := int(bf.ReadUint16())
|
||||
for i := 0; i < exchanges; i++ {
|
||||
itemHash := bf.ReadUint32()
|
||||
if itemHash == 0 {
|
||||
continue
|
||||
}
|
||||
buyCount := bf.ReadUint32()
|
||||
if _, err := s.server.db.Exec(`INSERT INTO shop_items_bought (character_id, shop_item_id, bought)
|
||||
VALUES ($1,$2,$3) ON CONFLICT (character_id, shop_item_id)
|
||||
DO UPDATE SET bought = bought + $3
|
||||
WHERE EXCLUDED.character_id=$1 AND EXCLUDED.shop_item_id=$2
|
||||
`, s.charID, itemHash, buyCount); err != nil {
|
||||
s.logger.Error("Failed to update shop item purchase count", zap.Error(err))
|
||||
}
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
type FPointExchange struct {
|
||||
ID uint32 `db:"id"`
|
||||
ItemType uint8 `db:"item_type"`
|
||||
ItemID uint16 `db:"item_id"`
|
||||
Quantity uint16 `db:"quantity"`
|
||||
FPoints uint16 `db:"fpoints"`
|
||||
Buyable bool `db:"buyable"`
|
||||
}
|
||||
|
||||
func handleMsgMhfExchangeFpoint2Item(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfExchangeFpoint2Item)
|
||||
var balance uint32
|
||||
var itemValue, quantity int
|
||||
_ = s.server.db.QueryRow("SELECT quantity, fpoints FROM fpoint_items WHERE id=$1", pkt.TradeID).Scan(&quantity, &itemValue)
|
||||
cost := (int(pkt.Quantity) * quantity) * itemValue
|
||||
_ = s.server.db.QueryRow("UPDATE users u SET frontier_points=frontier_points::int - $1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2) RETURNING frontier_points", cost, s.charID).Scan(&balance)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(balance)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfExchangeItem2Fpoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfExchangeItem2Fpoint)
|
||||
var balance uint32
|
||||
var itemValue, quantity int
|
||||
_ = s.server.db.QueryRow("SELECT quantity, fpoints FROM fpoint_items WHERE id=$1", pkt.TradeID).Scan(&quantity, &itemValue)
|
||||
cost := (int(pkt.Quantity) / quantity) * itemValue
|
||||
_ = s.server.db.QueryRow("UPDATE users u SET frontier_points=COALESCE(frontier_points::int + $1, $1) WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2) RETURNING frontier_points", cost, s.charID).Scan(&balance)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(balance)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfGetFpointExchangeList(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetFpointExchangeList)
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
var exchange FPointExchange
|
||||
var exchanges []FPointExchange
|
||||
var buyables uint16
|
||||
rows, err := s.server.db.Queryx(`SELECT id, item_type, item_id, quantity, fpoints, buyable FROM fpoint_items ORDER BY buyable DESC`)
|
||||
if err == nil {
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&exchange)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if exchange.Buyable {
|
||||
buyables++
|
||||
}
|
||||
exchanges = append(exchanges, exchange)
|
||||
}
|
||||
}
|
||||
if _config.ErupeConfig.RealClientMode <= _config.Z2 {
|
||||
bf.WriteUint8(uint8(len(exchanges)))
|
||||
bf.WriteUint8(uint8(buyables))
|
||||
} else {
|
||||
bf.WriteUint16(uint16(len(exchanges)))
|
||||
bf.WriteUint16(buyables)
|
||||
}
|
||||
for _, e := range exchanges {
|
||||
bf.WriteUint32(e.ID)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint8(e.ItemType)
|
||||
bf.WriteUint16(e.ItemID)
|
||||
bf.WriteUint16(e.Quantity)
|
||||
bf.WriteUint16(e.FPoints)
|
||||
}
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
Reference in New Issue
Block a user