implement normal gacha functionality

This commit is contained in:
wish
2023-01-15 19:55:08 +11:00
parent 5a9d22a28a
commit 341276c0ff
4 changed files with 464 additions and 379 deletions

View File

@@ -1,18 +1,18 @@
package mhfpacket package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
) )
// MsgMhfPlayNormalGacha represents the MSG_MHF_PLAY_NORMAL_GACHA // MsgMhfPlayNormalGacha represents the MSG_MHF_PLAY_NORMAL_GACHA
type MsgMhfPlayNormalGacha struct{ type MsgMhfPlayNormalGacha struct {
AckHandle uint32 AckHandle uint32
GachaHash uint32 GachaID uint32
RollType uint8 RollType uint8
CurrencyMode uint8 CurrencyMode uint8
} }
@@ -24,7 +24,7 @@ func (m *MsgMhfPlayNormalGacha) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfPlayNormalGacha) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfPlayNormalGacha) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
m.GachaHash = bf.ReadUint32() m.GachaID = bf.ReadUint32()
m.RollType = bf.ReadUint8() m.RollType = bf.ReadUint8()
m.CurrencyMode = bf.ReadUint8() m.CurrencyMode = bf.ReadUint8()
return nil return nil

View File

@@ -1,17 +1,18 @@
package mhfpacket package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
) )
// MsgMhfReceiveGachaItem represents the MSG_MHF_RECEIVE_GACHA_ITEM // MsgMhfReceiveGachaItem represents the MSG_MHF_RECEIVE_GACHA_ITEM
type MsgMhfReceiveGachaItem struct{ type MsgMhfReceiveGachaItem struct {
AckHandle uint32 AckHandle uint32
Unk0 uint16 Max uint8
Freeze bool
} }
// Opcode returns the ID associated with this packet type. // Opcode returns the ID associated with this packet type.
@@ -22,7 +23,8 @@ func (m *MsgMhfReceiveGachaItem) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfReceiveGachaItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfReceiveGachaItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint16() m.Max = bf.ReadUint8()
m.Freeze = bf.ReadBool()
return nil return nil
} }

View File

@@ -0,0 +1,61 @@
BEGIN;
ALTER TABLE characters
DROP COLUMN IF EXISTS gacha_prem;
ALTER TABLE characters
DROP COLUMN IF EXISTS gacha_trial;
ALTER TABLE characters
DROP COLUMN IF EXISTS frontier_points;
ALTER TABLE users
ADD IF NOT EXISTS gacha_premium INT;
ALTER TABLE users
ADD IF NOT EXISTS gacha_trial INT;
ALTER TABLE users
ADD IF NOT EXISTS frontier_points INT;
DROP TABLE IF EXISTS public.gacha_shop;
CREATE TABLE IF NOT EXISTS public.gacha_shop (
id SERIAL PRIMARY KEY,
min_gr INTEGER,
min_hr INTEGER,
name TEXT,
link1 TEXT,
link2 TEXT,
link3 TEXT,
is_wide_banner BOOLEAN,
flag1 INTEGER,
flag2 INTEGER,
flag3 INTEGER,
flag4 INTEGER
);
DROP TABLE IF EXISTS public.gacha_shop_items;
CREATE TABLE IF NOT EXISTS public.gacha_entries (
id SERIAL PRIMARY KEY,
gacha_id INTEGER,
entry_type INTEGER,
item_type INTEGER,
item_number INTEGER,
item_quantity INTEGER,
weight INTEGER,
rarity INTEGER,
rolls INTEGER,
daily_limit INTEGER
);
CREATE TABLE IF NOT EXISTS public.gacha_items (
id SERIAL PRIMARY KEY,
entry_id INTEGER,
item_type INTEGER,
item_id INTEGER,
quantity INTEGER
);
END;

View File

@@ -2,14 +2,11 @@ package channelserver
import ( import (
"encoding/hex" "encoding/hex"
ps "erupe-ce/common/pascalstring"
"time"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
"erupe-ce/network/mhfpacket" "erupe-ce/network/mhfpacket"
"github.com/lib/pq" "github.com/lib/pq"
"github.com/sachaos/lottery" "math/rand"
"go.uber.org/zap"
) )
type ShopItem struct { type ShopItem struct {
@@ -28,16 +25,36 @@ type ShopItem struct {
} }
type Gacha struct { type Gacha struct {
ID uint32 `db:"id"` ID uint32 `db:"id"`
MinGR uint32 `db:"min_gr"` MinGR uint32 `db:"min_gr"`
MinHR uint32 `db:"min_hr"` MinHR uint32 `db:"min_hr"`
Name string `db:"name"` Name string `db:"name"`
Link1 string `db:"link1"` Link1 string `db:"link1"`
Link2 string `db:"link2"` Link2 string `db:"link2"`
Link3 string `db:"link3"` Link3 string `db:"link3"`
Icon uint16 `db:"icon"` IsWideBanner bool `db:"is_wide_banner"`
Type uint16 `db:"type"` Flag1 uint8 `db:"flag1"`
Hide bool `db:"hide"` Flag2 uint8 `db:"flag2"`
Flag3 uint8 `db:"flag3"`
Flag4 uint8 `db:"flag4"`
}
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"`
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) { func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
@@ -55,7 +72,7 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
switch pkt.ShopType { switch pkt.ShopType {
case 1: // Running gachas case 1: // Running gachas
var count uint16 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, link1, link2, link3, is_wide_banner, flag1, flag2, flag3, flag4 FROM gacha_shop")
if err != nil { if err != nil {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return return
@@ -76,10 +93,12 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
ps.Uint8(resp, gacha.Name, true) ps.Uint8(resp, gacha.Name, true)
ps.Uint8(resp, gacha.Link1, false) ps.Uint8(resp, gacha.Link1, false)
ps.Uint8(resp, gacha.Link2, false) ps.Uint8(resp, gacha.Link2, false)
resp.WriteBool(gacha.Hide) resp.WriteBool(gacha.IsWideBanner)
ps.Uint8(resp, gacha.Link3, false) ps.Uint8(resp, gacha.Link3, false)
resp.WriteUint16(gacha.Icon) resp.WriteUint8(gacha.Flag1)
resp.WriteUint16(gacha.Type) resp.WriteUint8(gacha.Flag2)
resp.WriteUint8(gacha.Flag3)
resp.WriteUint8(gacha.Flag4)
count++ count++
} }
resp.Seek(0, 0) resp.Seek(0, 0)
@@ -87,47 +106,58 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
resp.WriteUint16(count) resp.WriteUint16(count)
doAckBufSucceed(s, pkt.AckHandle, resp.Data()) doAckBufSucceed(s, pkt.AckHandle, resp.Data())
case 2: // Actual gacha 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)
entries, err := s.server.db.Queryx(`SELECT entry_type, id, item_type, item_number, item_quantity, weight, rarity, rolls FROM gacha_entries WHERE gacha_id = $1 ORDER BY weight DESC`, pkt.ShopID)
if err != nil { if err != nil {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return return
} }
var entryType, currType, rarityIcon, rollsCount, itemCount, dailyLimit uint8
var currQuant, currNumber, percentage uint16 var divisor float64
var itemhash uint32 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 itemType, itemId, quantity pq.Int64Array
var count uint16 var entryCount uint16
resp := byteframe.NewByteFrame() bf.WriteUint16(0)
resp.WriteUint32(pkt.ShopID) gachaEntry := GachaEntry{}
resp.WriteUint16(0) // total defs gachaItem := GachaItem{}
for shopEntries.Next() { for entries.Next() {
err = shopEntries.Scan(&entryType, &itemhash, &currType, &currNumber, &currQuant, &percentage, &rarityIcon, &rollsCount, &itemCount, &dailyLimit, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity)) 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)
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 { 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) bf.WriteUint16(0)
resp.WriteUint8(currType) bf.WriteUint8(gachaEntry.DailyLimit)
resp.WriteUint16(0) // unk, always 0 in existing packets bf.WriteUint8(0)
resp.WriteUint16(currNumber) // it's either item ID or quantity for gacha coins bf.WriteBytes(temp.Data())
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++
} }
resp.Seek(4, 0) bf.Seek(4, 0)
resp.WriteUint16(count) bf.WriteUint16(entryCount)
doAckBufSucceed(s, pkt.AckHandle, resp.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
case 4: // N Points, 0-6 case 4: // N Points, 0-6
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
case 5: // GCP->Item, 0-6 case 5: // GCP->Item, 0-6
@@ -206,109 +236,95 @@ func handleMsgMhfAcquireExchangeShop(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfGetGachaPlayHistory(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfGetGachaPlayHistory(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetGachaPlayHistory) pkt := p.(*mhfpacket.MsgMhfGetGachaPlayHistory)
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
bf.WriteUint8(0) bf.WriteUint8(1)
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }
func handleMsgMhfGetGachaPoint(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfGetGachaPoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetGachaPoint) pkt := p.(*mhfpacket.MsgMhfGetGachaPoint)
var fp, gp, gt uint32 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, &gt) s.server.db.QueryRow("SELECT COALESCE(frontier_points, 0), COALESCE(gacha_premium, 0), COALESCE(gacha_trial, 0) FROM users WHERE id=$1", s.charID).Scan(&fp, &gp, &gt)
resp := byteframe.NewByteFrame() resp := byteframe.NewByteFrame()
resp.WriteUint32(gp) // Real Gacha Points? resp.WriteUint32(gp)
resp.WriteUint32(gt) // Trial Gacha Point? resp.WriteUint32(gt)
resp.WriteUint32(fp) // Frontier Points? resp.WriteUint32(fp)
doAckBufSucceed(s, pkt.AckHandle, resp.Data()) 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) { func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPlayNormalGacha) pkt := p.(*mhfpacket.MsgMhfPlayNormalGacha)
// needs to query db for input gacha and return a result or number of results bf := byteframe.NewByteFrame()
// uint8 number of results var gachaEntries []GachaEntry
// uint8 item type var entry GachaEntry
// uint16 item id var rewards []GachaItem
// uint16 quantity var reward GachaItem
var totalWeight float64
var currType, rarityIcon, rollsCount, itemCount byte 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)
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 { if err != nil {
panic(err) doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
return
} }
// get existing items in storage if any for entries.Next() {
entries.StructScan(&entry)
gachaEntries = append(gachaEntries, entry)
totalWeight += entry.Weight
}
result := rand.Float64() * totalWeight
var rewardCount uint8
temp := byteframe.NewByteFrame()
for _, entry := range gachaEntries {
result -= entry.Weight
if result < 0 {
items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = $1`, entry.ID)
if err != nil {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
return
}
for items.Next() {
rewardCount++
items.StructScan(&reward)
rewards = append(rewards, reward)
temp.WriteUint8(reward.ItemType)
temp.WriteUint16(reward.ItemID)
temp.WriteUint16(reward.Quantity)
temp.WriteUint8(entry.Rarity)
}
break
}
}
bf.WriteUint8(rewardCount)
bf.WriteBytes(temp.Data())
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
addGachaItem(s, rewards)
// TODO: subtract gacha coins if spent
}
func addGachaItem(s *Session, items []GachaItem) {
var data []byte var data []byte
_ = s.server.db.QueryRow("SELECT gacha_items FROM characters WHERE id = $1", s.charID).Scan(&data) s.server.db.QueryRow(`SELECT gacha_items FROM characters WHERE id = $1`, s.charID).Scan(&data)
if len(data) == 0 { if len(data) > 0 {
data = []byte{0x00} numItems := int(data[0])
} data = data[1:]
// get gacha items and iterate through them for gacha roll oldItem := byteframe.NewByteFrameFromBytes(data)
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) for i := 0; i < numItems; i++ {
if err != nil { items = append(items, GachaItem{
panic(err) ItemType: oldItem.ReadUint8(),
} ItemID: oldItem.ReadUint16(),
for shopEntries.Next() { Quantity: oldItem.ReadUint16(),
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)
} }
} }
resp.Seek(0, 0) if len(items) > 36 {
resp.WriteUint8(results) items = items[:36]
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 newItem := byteframe.NewByteFrame()
if currType == 19 { newItem.WriteUint8(uint8(len(items)))
_, 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) for i := range items {
} newItem.WriteUint8(items[i].ItemType)
if err != nil { newItem.WriteUint16(items[i].ItemID)
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err)) newItem.WriteUint16(items[i].Quantity)
} }
s.server.db.Exec(`UPDATE characters SET gacha_items = $1 WHERE id = $2`, newItem.Data(), s.charID)
} }
func handleMsgMhfUseGachaPoint(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfUseGachaPoint(s *Session, p mhfpacket.MHFPacket) {
@@ -323,7 +339,7 @@ func handleMsgMhfExchangeFpoint2Item(s *Session, p mhfpacket.MHFPacket) {
var itemValue, quantity int 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 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 SET frontier_points=frontier_points::int - $1 WHERE id=$2 RETURNING frontier_points", cost, s.charID).Scan(&balance)
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
bf.WriteUint32(balance) bf.WriteUint32(balance)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
@@ -335,7 +351,7 @@ func handleMsgMhfExchangeItem2Fpoint(s *Session, p mhfpacket.MHFPacket) {
var itemValue, quantity int 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 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 SET frontier_points=COALESCE(frontier_points::int + $1, $1) WHERE id=$2 RETURNING frontier_points", cost, s.charID).Scan(&balance)
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
bf.WriteUint32(balance) bf.WriteUint32(balance)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
@@ -396,164 +412,164 @@ func handleMsgMhfGetFpointExchangeList(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfPlayStepupGacha(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfPlayStepupGacha(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPlayStepupGacha) pkt := p.(*mhfpacket.MsgMhfPlayStepupGacha)
results := byte(0) bf := byteframe.NewByteFrame()
stepResults := byte(0) bf.WriteUint8(1) // num items
resp := byteframe.NewByteFrame() bf.WriteUint8(0) // num non-guaranteed items
rollFrame := byteframe.NewByteFrame() bf.WriteUint8(7) // item type
stepFrame := byteframe.NewByteFrame() bf.WriteUint16(7) // item id
stepData := []byte{} bf.WriteUint16(1) // quantity
var currType, rarityIcon, rollsCount, itemCount byte bf.WriteUint8(0) // rarity
var currQuant, currNumber, percentage uint16 doAckBufSucceed(s, pkt.AckHandle, bf.Data())
var itemhash uint32 /*
var itemType, itemId, quantity pq.Int64Array results := byte(0)
var items []lottery.Weighter stepResults := byte(0)
// get info for updating data and calculating costs resp := byteframe.NewByteFrame()
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)) rollFrame := byteframe.NewByteFrame()
if err != nil { stepFrame := byteframe.NewByteFrame()
panic(err) stepData := []byte{}
} var currType, rarityIcon, rollsCount, itemCount byte
// get existing items in storage if any var currQuant, currNumber, percentage uint16
var data []byte var itemhash uint32
_ = s.server.db.QueryRow("SELECT gacha_items FROM characters WHERE id = $1", s.charID).Scan(&data) var itemType, itemId, quantity pq.Int64Array
if len(data) == 0 { var items []lottery.Weighter
data = []byte{0x00} // 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))
// 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 { if err != nil {
panic(err) panic(err)
} }
items = append(items, &gachaItem{itemhash: itemhash, percentage: percentage, rarityIcon: rarityIcon, itemCount: itemCount, itemType: itemType, itemId: itemId, quantity: quantity}) // get existing items in storage if any
} var data []byte
// execute rolls, build response and update database _ = s.server.db.QueryRow("SELECT gacha_items FROM characters WHERE id = $1", s.charID).Scan(&data)
resp.WriteUint16(0) // results count goes here later if len(data) == 0 {
l := lottery.NewDefaultLottery() data = []byte{0x00}
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)
} }
} // roll definition includes items with step up gachas that are appended last
resp.WriteBytes(stepFrame.Data()) for x := 0; x < int(itemCount); x++ {
resp.Seek(0, 0) stepFrame.WriteUint8(uint8(itemType[x]))
resp.WriteUint8(results + stepResults) stepFrame.WriteUint16(uint16(itemId[x]))
resp.WriteUint8(results) stepFrame.WriteUint16(uint16(quantity[x]))
doAckBufSucceed(s, pkt.AckHandle, resp.Data()) 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 // add claimables to DB
data = append(data, stepData...) data = append(data, stepData...)
data[0] = data[0] + results + stepResults data[0] = data[0] + results + stepResults
_, err = s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", data, s.charID) _, err = s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", data, s.charID)
if err != nil { if err != nil {
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err)) 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 // deduct gacha coins if relevant, items are handled fine by the standard savedata packet immediately afterwards
// reduce real if trial don't cover cost // reduce real if trial don't cover cost
if currType == 19 { if currType == 19 {
_, err = s.server.db.Exec(`UPDATE characters _, err = s.server.db.Exec(`UPDATE characters
SET gacha_trial = CASE WHEN (gacha_trial > $1) then gacha_trial - $1 else gacha_trial end, 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 gacha_prem = CASE WHEN NOT (gacha_trial > $1) then gacha_prem - $1 else gacha_prem end
WHERE id=$2`, currNumber, s.charID) WHERE id=$2`, currNumber, s.charID)
} }
if err != nil { if err != nil {
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err)) s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err))
} }
// update step progression // update step progression
_, err = s.server.db.Exec("UPDATE stepup_state SET step_progression = $1 WHERE char_id = $2", pkt.RollType+1, s.charID) _, err = s.server.db.Exec("UPDATE stepup_state SET step_progression = $1 WHERE char_id = $2", pkt.RollType+1, s.charID)
if err != nil { if err != nil {
s.logger.Fatal("Failed to update step_progression in db", zap.Error(err)) s.logger.Fatal("Failed to update step_progression in db", zap.Error(err))
} }
*/
} }
func handleMsgMhfReceiveGachaItem(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfReceiveGachaItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReceiveGachaItem) pkt := p.(*mhfpacket.MsgMhfReceiveGachaItem)
// persistent for claimable items on cat
var data []byte var data []byte
err := s.server.db.QueryRow("SELECT COALESCE(gacha_items, $2) FROM characters WHERE id = $1", s.charID, []byte{0x00}).Scan(&data) err := s.server.db.QueryRow("SELECT COALESCE(gacha_items, $2) FROM characters WHERE id = $1", s.charID, []byte{0x00}).Scan(&data)
if err != nil { if err != nil {
panic("Failed to get gacha_items") data = []byte{0x00}
} }
// limit of 36 items are returned doAckBufSucceed(s, pkt.AckHandle, data)
if data[0] > 36 { if !pkt.Freeze {
outData := make([]byte, 181) s.server.db.Exec("UPDATE characters SET gacha_items = null WHERE id = $1", s.charID)
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) { func handleMsgMhfGetStepupStatus(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetStepupStatus) 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 bf := byteframe.NewByteFrame()
var t = time.Now().In(time.FixedZone("UTC+9", 9*60*60)) bf.WriteUint8(0) // step
year, month, day := t.Date() bf.WriteUint32(uint32(Time_Current_Adjusted().Unix()))
midday := time.Date(year, month, day, 12, 0, 0, 0, t.Location()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
if t.After(midday) {
midday = midday.Add(24 * time.Hour) /*
} // get the reset time from db
// after midday or not set var step_progression int
if t.After(step_time) { var step_time time.Time
step_progression = 0 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 {
_, err = s.server.db.Exec(`INSERT INTO stepup_state (shophash, step_progression, step_time, char_id) s.logger.Fatal("Failed to Select coalesce in db", zap.Error(err))
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) // calculate next midday
if err != nil { var t = time.Now().In(time.FixedZone("UTC+9", 9*60*60))
s.logger.Fatal("Failed to update platedata savedata in db", zap.Error(err)) year, month, day := t.Date()
} midday := time.Date(year, month, day, 12, 0, 0, 0, t.Location())
resp := byteframe.NewByteFrame() if t.After(midday) {
resp.WriteUint8(uint8(step_progression)) midday = midday.Add(24 * time.Hour)
resp.WriteUint32(uint32(time.Now().In(time.FixedZone("UTC+9", 9*60*60)).Unix())) }
doAckBufSucceed(s, pkt.AckHandle, resp.Data()) // 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) { func handleMsgMhfPlayFreeGacha(s *Session, p mhfpacket.MHFPacket) {
@@ -581,97 +597,103 @@ func handleMsgMhfGetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfPlayBoxGacha(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfPlayBoxGacha(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPlayBoxGacha) 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 bf := byteframe.NewByteFrame()
var currQuant, currNumber, percentage uint16 bf.WriteUint8(1) // num items
var itemhash uint32 bf.WriteUint8(7) // item type
var itemType, itemId, quantity, usedItemHash pq.Int64Array bf.WriteUint16(7) // item id
var items []lottery.Weighter bf.WriteUint16(1) // quantity
// get info for updating data and calculating costs bf.WriteUint8(0) // rarity
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) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
if err != nil {
panic(err) /*
} var currType, rarityIcon, rollsCount, itemCount byte
// get existing items in storage if any var currQuant, currNumber, percentage uint16
var data []byte var itemhash uint32
_ = s.server.db.QueryRow("SELECT gacha_items FROM characters WHERE id = $1", s.charID).Scan(&data) var itemType, itemId, quantity, usedItemHash pq.Int64Array
if len(data) == 0 { var items []lottery.Weighter
data = []byte{0x00} // 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)
// 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 { if err != nil {
panic(err) panic(err)
} }
items = append(items, &gachaItem{itemhash: itemhash, percentage: percentage, rarityIcon: rarityIcon, itemCount: itemCount, itemType: itemType, itemId: itemId, quantity: quantity}) // get existing items in storage if any
} var data []byte
// execute rolls, build response and update database _ = s.server.db.QueryRow("SELECT gacha_items FROM characters WHERE id = $1", s.charID).Scan(&data)
results := byte(0) if len(data) == 0 {
resp := byteframe.NewByteFrame() data = []byte{0x00}
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 // get gacha items and iterate through them for gacha roll
items = append(items[:ind], items[ind+1:]...) shopEntries, err := s.server.db.Query(`SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity
} FROM gacha_shop_items
resp.Seek(0, 0) WHERE shophash=$1 AND entryType=100
resp.WriteUint8(results) EXCEPT ALL SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity
doAckBufSucceed(s, pkt.AckHandle, resp.Data()) 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)
// add claimables to DB usedItemHash = append(usedItemHash, int64(items[ind].(*gachaItem).itemhash))
data[0] = data[0] + results }
_, err = s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", data, s.charID) // remove rolled
if err != nil { items = append(items[:ind], items[ind+1:]...)
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err)) }
} resp.Seek(0, 0)
// update lucky_box_state resp.WriteUint8(results)
_, err = s.server.db.Exec(`INSERT INTO lucky_box_state (char_id, shophash, used_itemhash) doAckBufSucceed(s, pkt.AckHandle, resp.Data())
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[]) // add claimables to DB
WHERE EXCLUDED.char_id=$1 AND EXCLUDED.shophash=$2`, s.charID, pkt.GachaHash, usedItemHash) data[0] = data[0] + results
if err != nil { _, err = s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", data, s.charID)
s.logger.Fatal("Failed to update lucky box state in db", zap.Error(err)) 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 }
if currType == 19 { // update lucky_box_state
_, err = s.server.db.Exec(`UPDATE characters _, err = s.server.db.Exec(`INSERT INTO lucky_box_state (char_id, shophash, used_itemhash)
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 VALUES ($1,$2,$3) ON CONFLICT (char_id, shophash)
WHERE id=$2`, currNumber, s.charID) 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 { if err != nil {
s.logger.Fatal("Failed to update gacha_trial in db", zap.Error(err)) 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) { func handleMsgMhfResetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) {