From 341276c0ffa2778b54134c11de05d55edaad1024 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 15 Jan 2023 19:55:08 +1100 Subject: [PATCH 01/18] implement normal gacha functionality --- .../mhfpacket/msg_mhf_play_normal_gacha.go | 18 +- .../mhfpacket/msg_mhf_receive_gacha_item.go | 18 +- patch-schema/gacha-db-2.sql | 61 ++ server/channelserver/handlers_shop_gacha.go | 746 +++++++++--------- 4 files changed, 464 insertions(+), 379 deletions(-) create mode 100644 patch-schema/gacha-db-2.sql diff --git a/network/mhfpacket/msg_mhf_play_normal_gacha.go b/network/mhfpacket/msg_mhf_play_normal_gacha.go index bc75f3ca1..8d1844c4d 100644 --- a/network/mhfpacket/msg_mhf_play_normal_gacha.go +++ b/network/mhfpacket/msg_mhf_play_normal_gacha.go @@ -1,18 +1,18 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfPlayNormalGacha represents the MSG_MHF_PLAY_NORMAL_GACHA -type MsgMhfPlayNormalGacha struct{ - AckHandle uint32 - GachaHash uint32 - RollType uint8 +type MsgMhfPlayNormalGacha struct { + AckHandle uint32 + GachaID uint32 + RollType uint8 CurrencyMode uint8 } @@ -24,7 +24,7 @@ func (m *MsgMhfPlayNormalGacha) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfPlayNormalGacha) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.GachaHash = bf.ReadUint32() + m.GachaID = bf.ReadUint32() m.RollType = bf.ReadUint8() m.CurrencyMode = bf.ReadUint8() return nil diff --git a/network/mhfpacket/msg_mhf_receive_gacha_item.go b/network/mhfpacket/msg_mhf_receive_gacha_item.go index 841c5deab..3f83d1d1b 100644 --- a/network/mhfpacket/msg_mhf_receive_gacha_item.go +++ b/network/mhfpacket/msg_mhf_receive_gacha_item.go @@ -1,17 +1,18 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfReceiveGachaItem represents the MSG_MHF_RECEIVE_GACHA_ITEM -type MsgMhfReceiveGachaItem struct{ - AckHandle uint32 - Unk0 uint16 +type MsgMhfReceiveGachaItem struct { + AckHandle uint32 + Max uint8 + Freeze bool } // 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 func (m *MsgMhfReceiveGachaItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint16() + m.Max = bf.ReadUint8() + m.Freeze = bf.ReadBool() return nil } diff --git a/patch-schema/gacha-db-2.sql b/patch-schema/gacha-db-2.sql new file mode 100644 index 000000000..857ce8a47 --- /dev/null +++ b/patch-schema/gacha-db-2.sql @@ -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; \ No newline at end of file diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index 350e31acd..9cb849ca3 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -2,14 +2,11 @@ package channelserver import ( "encoding/hex" - ps "erupe-ce/common/pascalstring" - "time" - "erupe-ce/common/byteframe" + ps "erupe-ce/common/pascalstring" "erupe-ce/network/mhfpacket" "github.com/lib/pq" - "github.com/sachaos/lottery" - "go.uber.org/zap" + "math/rand" ) type ShopItem struct { @@ -28,16 +25,36 @@ type ShopItem struct { } type Gacha struct { - ID uint32 `db:"id"` - MinGR uint32 `db:"min_gr"` - MinHR uint32 `db:"min_hr"` - Name string `db:"name"` - Link1 string `db:"link1"` - Link2 string `db:"link2"` - Link3 string `db:"link3"` - Icon uint16 `db:"icon"` - Type uint16 `db:"type"` - Hide bool `db:"hide"` + ID uint32 `db:"id"` + MinGR uint32 `db:"min_gr"` + MinHR uint32 `db:"min_hr"` + Name string `db:"name"` + Link1 string `db:"link1"` + Link2 string `db:"link2"` + Link3 string `db:"link3"` + IsWideBanner bool `db:"is_wide_banner"` + Flag1 uint8 `db:"flag1"` + 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) { @@ -55,7 +72,7 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) { switch pkt.ShopType { case 1: // Running gachas var count uint16 - shopEntries, err := s.server.db.Queryx("SELECT id, min_gr, min_hr, name, link1, link2, link3, icon, type, hide FROM gacha_shop") + shopEntries, err := s.server.db.Queryx("SELECT id, min_gr, min_hr, name, link1, link2, link3, is_wide_banner, flag1, flag2, flag3, flag4 FROM gacha_shop") if err != nil { doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) return @@ -76,10 +93,12 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) { ps.Uint8(resp, gacha.Name, true) ps.Uint8(resp, gacha.Link1, false) ps.Uint8(resp, gacha.Link2, false) - resp.WriteBool(gacha.Hide) + resp.WriteBool(gacha.IsWideBanner) ps.Uint8(resp, gacha.Link3, false) - resp.WriteUint16(gacha.Icon) - resp.WriteUint16(gacha.Type) + resp.WriteUint8(gacha.Flag1) + resp.WriteUint8(gacha.Flag2) + resp.WriteUint8(gacha.Flag3) + resp.WriteUint8(gacha.Flag4) count++ } resp.Seek(0, 0) @@ -87,47 +106,58 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) { resp.WriteUint16(count) doAckBufSucceed(s, pkt.AckHandle, resp.Data()) case 2: // Actual gacha - shopEntries, err := s.server.db.Query("SELECT entryType, itemhash, currType, currNumber, currQuant, percentage, rarityIcon, rollsCount, itemCount, dailyLimit, itemType, itemId, quantity FROM gacha_shop_items WHERE shophash=$1", pkt.ShopID) + bf := byteframe.NewByteFrame() + bf.WriteUint32(pkt.ShopID) + 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 { doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) return } - var entryType, currType, rarityIcon, rollsCount, itemCount, dailyLimit uint8 - var currQuant, currNumber, percentage uint16 - var itemhash uint32 - var itemType, itemId, quantity pq.Int64Array - var count uint16 - resp := byteframe.NewByteFrame() - resp.WriteUint32(pkt.ShopID) - resp.WriteUint16(0) // total defs - for shopEntries.Next() { - err = shopEntries.Scan(&entryType, &itemhash, &currType, &currNumber, &currQuant, &percentage, &rarityIcon, &rollsCount, &itemCount, &dailyLimit, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity)) + + var divisor float64 + s.server.db.QueryRow(`SELECT COALESCE(SUM(weight) / 100000.0, 0) AS chance FROM gacha_entries WHERE gacha_id = $1`, pkt.ShopID).Scan(&divisor) + + var entryCount uint16 + bf.WriteUint16(0) + gachaEntry := GachaEntry{} + gachaItem := GachaItem{} + for entries.Next() { + entryCount++ + entries.StructScan(&gachaEntry) + bf.WriteUint8(gachaEntry.EntryType) + bf.WriteUint32(gachaEntry.ID) + bf.WriteUint8(gachaEntry.ItemType) + bf.WriteUint16(0) + bf.WriteUint16(gachaEntry.ItemNumber) + bf.WriteUint16(gachaEntry.ItemQuantity) + bf.WriteUint16(uint16(gachaEntry.Weight / divisor)) + bf.WriteUint8(gachaEntry.Rarity) + bf.WriteUint8(gachaEntry.Rolls) + + var itemCount uint8 + temp := byteframe.NewByteFrame() + items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id=$1`, gachaEntry.ID) if err != nil { - panic(err) + bf.WriteUint8(0) + } else { + for items.Next() { + itemCount++ + items.StructScan(&gachaItem) + temp.WriteUint16(uint16(gachaItem.ItemType)) + temp.WriteUint16(gachaItem.ItemID) + temp.WriteUint16(gachaItem.Quantity) + } + bf.WriteUint8(itemCount) } - resp.WriteUint8(entryType) - resp.WriteUint32(itemhash) - resp.WriteUint8(currType) - resp.WriteUint16(0) // unk, always 0 in existing packets - resp.WriteUint16(currNumber) // it's either item ID or quantity for gacha coins - resp.WriteUint16(currQuant) // only for item ID - resp.WriteUint16(percentage) - resp.WriteUint8(rarityIcon) - resp.WriteUint8(rollsCount) - resp.WriteUint8(itemCount) - resp.WriteUint16(0) // unk, always 0 in existing packets - resp.WriteUint8(dailyLimit) - resp.WriteUint8(0) // unk, always 0 in existing packets - for i := 0; i < int(itemCount); i++ { - resp.WriteUint16(uint16(itemType[i])) // unk, always 0 in existing packets - resp.WriteUint16(uint16(itemId[i])) // unk, always 0 in existing packets - resp.WriteUint16(uint16(quantity[i])) // unk, always 0 in existing packets - } - count++ + + bf.WriteUint16(0) + bf.WriteUint8(gachaEntry.DailyLimit) + bf.WriteUint8(0) + bf.WriteBytes(temp.Data()) } - resp.Seek(4, 0) - resp.WriteUint16(count) - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) + bf.Seek(4, 0) + bf.WriteUint16(entryCount) + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) case 4: // N Points, 0-6 doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) case 5: // GCP->Item, 0-6 @@ -206,109 +236,95 @@ func handleMsgMhfAcquireExchangeShop(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfGetGachaPlayHistory(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetGachaPlayHistory) bf := byteframe.NewByteFrame() - bf.WriteUint8(0) + bf.WriteUint8(1) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } func handleMsgMhfGetGachaPoint(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetGachaPoint) var fp, gp, gt uint32 - s.server.db.QueryRow("SELECT COALESCE(frontier_points, 0), COALESCE(gacha_prem, 0), COALESCE(gacha_trial,0) FROM characters WHERE id=$1", s.charID).Scan(&fp, &gp, >) + s.server.db.QueryRow("SELECT COALESCE(frontier_points, 0), COALESCE(gacha_premium, 0), COALESCE(gacha_trial, 0) FROM users 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? + resp.WriteUint32(gp) + resp.WriteUint32(gt) + resp.WriteUint32(fp) doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } -type gachaItem struct { - itemhash uint32 - percentage uint16 - rarityIcon byte - itemCount byte - itemType pq.Int64Array - itemId pq.Int64Array - quantity pq.Int64Array -} - -func (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) + bf := byteframe.NewByteFrame() + var gachaEntries []GachaEntry + var entry GachaEntry + var rewards []GachaItem + var reward GachaItem + var totalWeight float64 + entries, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID) if err != nil { - panic(err) + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) + return } - // 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 - _ = 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(items[ind].(*gachaItem).rarityIcon) + s.server.db.QueryRow(`SELECT gacha_items FROM characters WHERE id = $1`, s.charID).Scan(&data) + if len(data) > 0 { + numItems := int(data[0]) + data = data[1:] + oldItem := byteframe.NewByteFrameFromBytes(data) + for i := 0; i < numItems; i++ { + items = append(items, GachaItem{ + ItemType: oldItem.ReadUint8(), + ItemID: oldItem.ReadUint16(), + Quantity: oldItem.ReadUint16(), + }) } } - resp.Seek(0, 0) - resp.WriteUint8(results) - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) - - // add claimables to DB - data[0] = data[0] + results - _, err = s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", data, s.charID) - if err != nil { - s.logger.Fatal("Failed to update minidata in db", zap.Error(err)) + if len(items) > 36 { + items = items[:36] } - // 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)) + newItem := byteframe.NewByteFrame() + newItem.WriteUint8(uint8(len(items))) + for i := range items { + newItem.WriteUint8(items[i].ItemType) + newItem.WriteUint16(items[i].ItemID) + newItem.WriteUint16(items[i].Quantity) } + s.server.db.Exec(`UPDATE characters SET gacha_items = $1 WHERE id = $2`, newItem.Data(), s.charID) } func handleMsgMhfUseGachaPoint(s *Session, p mhfpacket.MHFPacket) { @@ -323,7 +339,7 @@ func handleMsgMhfExchangeFpoint2Item(s *Session, p mhfpacket.MHFPacket) { var itemValue, quantity int _ = s.server.db.QueryRow("SELECT quantity, fpoints FROM fpoint_items WHERE id=$1", pkt.TradeID).Scan(&quantity, &itemValue) cost := (int(pkt.Quantity) * quantity) * itemValue - s.server.db.QueryRow("UPDATE characters SET frontier_points=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.WriteUint32(balance) doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) @@ -335,7 +351,7 @@ func handleMsgMhfExchangeItem2Fpoint(s *Session, p mhfpacket.MHFPacket) { var itemValue, quantity int s.server.db.QueryRow("SELECT quantity, fpoints FROM fpoint_items WHERE id=$1", pkt.TradeID).Scan(&quantity, &itemValue) cost := (int(pkt.Quantity) / quantity) * itemValue - s.server.db.QueryRow("UPDATE characters SET frontier_points=COALESCE(frontier_points::int + $1, $1) WHERE id=$2 RETURNING frontier_points", cost, s.charID).Scan(&balance) + s.server.db.QueryRow("UPDATE users SET frontier_points=COALESCE(frontier_points::int + $1, $1) WHERE id=$2 RETURNING frontier_points", cost, s.charID).Scan(&balance) bf := byteframe.NewByteFrame() bf.WriteUint32(balance) doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) @@ -396,164 +412,164 @@ func handleMsgMhfGetFpointExchangeList(s *Session, p mhfpacket.MHFPacket) { 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)) + bf := byteframe.NewByteFrame() + bf.WriteUint8(1) // num items + bf.WriteUint8(0) // num non-guaranteed items + bf.WriteUint8(7) // item type + bf.WriteUint16(7) // item id + bf.WriteUint16(1) // quantity + bf.WriteUint8(0) // rarity + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) + /* + 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) } - 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) + // 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} } - } - resp.WriteBytes(stepFrame.Data()) - resp.Seek(0, 0) - resp.WriteUint8(results + stepResults) - resp.WriteUint8(results) - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) + // roll definition includes items with step up gachas that are appended last + for x := 0; x < int(itemCount); x++ { + stepFrame.WriteUint8(uint8(itemType[x])) + stepFrame.WriteUint16(uint16(itemId[x])) + stepFrame.WriteUint16(uint16(quantity[x])) + stepData = append(stepData, stepFrame.Data()...) + stepFrame.WriteUint8(0) // rarity still defined + stepResults++ + } + // get gacha items and iterate through them for gacha roll + shopEntries, err := s.server.db.Query("SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity FROM gacha_shop_items WHERE shophash=$1 AND entryType=100", pkt.GachaHash) + if err != nil { + panic(err) + } + for shopEntries.Next() { + err = shopEntries.Scan(&itemhash, &percentage, &rarityIcon, &itemCount, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity)) + if err != nil { + panic(err) + } + items = append(items, &gachaItem{itemhash: itemhash, percentage: percentage, rarityIcon: rarityIcon, itemCount: itemCount, itemType: itemType, itemId: itemId, quantity: quantity}) + } + // execute rolls, build response and update database + resp.WriteUint16(0) // results count goes here later + l := lottery.NewDefaultLottery() + for x := 0; x < int(rollsCount); x++ { + ind := l.Draw(items) + results += items[ind].(*gachaItem).itemCount + for y := 0; y < int(items[ind].(*gachaItem).itemCount); y++ { + // items in storage don't get rarity + rollFrame.WriteUint8(uint8(items[ind].(*gachaItem).itemType[y])) + rollFrame.WriteUint16(uint16(items[ind].(*gachaItem).itemId[y])) + rollFrame.WriteUint16(uint16(items[ind].(*gachaItem).quantity[y])) + data = append(data, rollFrame.Data()...) + rollFrame.Seek(0, 0) + // response needs all item info and the rarity + resp.WriteBytes(rollFrame.Data()) + resp.WriteUint8(items[ind].(*gachaItem).rarityIcon) + } + } + resp.WriteBytes(stepFrame.Data()) + resp.Seek(0, 0) + resp.WriteUint8(results + stepResults) + resp.WriteUint8(results) + doAckBufSucceed(s, pkt.AckHandle, resp.Data()) - // add claimables to DB - data = append(data, stepData...) - data[0] = data[0] + results + stepResults - _, err = s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", data, s.charID) - if err != nil { - s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err)) - } - // deduct gacha coins if relevant, items are handled fine by the standard savedata packet immediately afterwards - // reduce real if trial don't cover cost - if currType == 19 { - _, err = s.server.db.Exec(`UPDATE characters - SET gacha_trial = CASE WHEN (gacha_trial > $1) then gacha_trial - $1 else gacha_trial end, - gacha_prem = CASE WHEN NOT (gacha_trial > $1) then gacha_prem - $1 else gacha_prem end - WHERE id=$2`, currNumber, s.charID) - } - if err != nil { - s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err)) - } - // update step progression - _, err = s.server.db.Exec("UPDATE stepup_state SET step_progression = $1 WHERE char_id = $2", pkt.RollType+1, s.charID) - if err != nil { - s.logger.Fatal("Failed to update step_progression in db", zap.Error(err)) - } + // 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") + data = []byte{0x00} } - // 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)) - } - } + doAckBufSucceed(s, pkt.AckHandle, data) + if !pkt.Freeze { + s.server.db.Exec("UPDATE characters SET gacha_items = null WHERE id = $1", s.charID) } } 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()) + bf := byteframe.NewByteFrame() + bf.WriteUint8(0) // step + bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) + + /* + // 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) { @@ -581,97 +597,103 @@ func handleMsgMhfGetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) { 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)) + bf := byteframe.NewByteFrame() + bf.WriteUint8(1) // num items + bf.WriteUint8(7) // item type + bf.WriteUint16(7) // item id + bf.WriteUint16(1) // quantity + bf.WriteUint8(0) // rarity + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) + + /* + 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) } - items = append(items, &gachaItem{itemhash: itemhash, percentage: percentage, rarityIcon: rarityIcon, itemCount: itemCount, itemType: itemType, itemId: itemId, quantity: quantity}) - } - // execute rolls, build response and update database - results := byte(0) - resp := byteframe.NewByteFrame() - dbUpdate := byteframe.NewByteFrame() - resp.WriteUint8(0) // results go here later - l := lottery.NewDefaultLottery() - for x := 0; x < int(rollsCount); x++ { - ind := l.Draw(items) - results += items[ind].(*gachaItem).itemCount - for y := 0; y < int(items[ind].(*gachaItem).itemCount); y++ { - // items in storage don't get rarity - dbUpdate.WriteUint8(uint8(items[ind].(*gachaItem).itemType[y])) - dbUpdate.WriteUint16(uint16(items[ind].(*gachaItem).itemId[y])) - dbUpdate.WriteUint16(uint16(items[ind].(*gachaItem).quantity[y])) - data = append(data, dbUpdate.Data()...) - dbUpdate.Seek(0, 0) - // response needs all item info and the rarity - resp.WriteBytes(dbUpdate.Data()) - resp.WriteUint8(items[ind].(*gachaItem).rarityIcon) - - usedItemHash = append(usedItemHash, int64(items[ind].(*gachaItem).itemhash)) + // 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} } - // remove rolled - items = append(items[:ind], items[ind+1:]...) - } - resp.Seek(0, 0) - resp.WriteUint8(results) - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) + // get gacha items and iterate through them for gacha roll + shopEntries, err := s.server.db.Query(`SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity + FROM gacha_shop_items + WHERE shophash=$1 AND entryType=100 + EXCEPT ALL SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity + FROM gacha_shop_items gsi JOIN lucky_box_state lbs ON gsi.itemhash = ANY(lbs.used_itemhash) + WHERE lbs.char_id=$2`, pkt.GachaHash, s.charID) + if err != nil { + panic(err) + } + for shopEntries.Next() { + err = shopEntries.Scan(&itemhash, &percentage, &rarityIcon, &itemCount, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity)) + if err != nil { + panic(err) + } + items = append(items, &gachaItem{itemhash: itemhash, percentage: percentage, rarityIcon: rarityIcon, itemCount: itemCount, itemType: itemType, itemId: itemId, quantity: quantity}) + } + // execute rolls, build response and update database + results := byte(0) + resp := byteframe.NewByteFrame() + dbUpdate := byteframe.NewByteFrame() + resp.WriteUint8(0) // results go here later + l := lottery.NewDefaultLottery() + for x := 0; x < int(rollsCount); x++ { + ind := l.Draw(items) + results += items[ind].(*gachaItem).itemCount + for y := 0; y < int(items[ind].(*gachaItem).itemCount); y++ { + // items in storage don't get rarity + dbUpdate.WriteUint8(uint8(items[ind].(*gachaItem).itemType[y])) + dbUpdate.WriteUint16(uint16(items[ind].(*gachaItem).itemId[y])) + dbUpdate.WriteUint16(uint16(items[ind].(*gachaItem).quantity[y])) + data = append(data, dbUpdate.Data()...) + dbUpdate.Seek(0, 0) + // response needs all item info and the rarity + resp.WriteBytes(dbUpdate.Data()) + resp.WriteUint8(items[ind].(*gachaItem).rarityIcon) - // 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)) - } + usedItemHash = append(usedItemHash, int64(items[ind].(*gachaItem).itemhash)) + } + // remove rolled + items = append(items[:ind], items[ind+1:]...) + } + resp.Seek(0, 0) + resp.WriteUint8(results) + doAckBufSucceed(s, pkt.AckHandle, resp.Data()) + + // add claimables to DB + data[0] = data[0] + results + _, err = s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", data, s.charID) + if err != nil { + s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err)) + } + // update lucky_box_state + _, err = s.server.db.Exec(`INSERT INTO lucky_box_state (char_id, shophash, used_itemhash) + VALUES ($1,$2,$3) ON CONFLICT (char_id, shophash) + DO UPDATE SET used_itemhash = COALESCE(lucky_box_state.used_itemhash::int[] || $3::int[], $3::int[]) + WHERE EXCLUDED.char_id=$1 AND EXCLUDED.shophash=$2`, s.charID, pkt.GachaHash, usedItemHash) + if err != nil { + s.logger.Fatal("Failed to update lucky box state in db", zap.Error(err)) + } + // deduct gacha coins if relevant, items are handled fine by the standard savedata packet immediately afterwards + if currType == 19 { + _, err = s.server.db.Exec(`UPDATE characters + SET gacha_trial = CASE WHEN (gacha_trial > $1) then gacha_trial - $1 else gacha_trial end, gacha_prem = CASE WHEN NOT (gacha_trial > $1) then gacha_prem - $1 else gacha_prem end + WHERE id=$2`, currNumber, s.charID) + } + if err != nil { + s.logger.Fatal("Failed to update gacha_trial in db", zap.Error(err)) + } + + */ } func handleMsgMhfResetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) { From e1df9fca0401f1910514b5c6d55ddda47995d324 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 15 Jan 2023 20:42:17 +1100 Subject: [PATCH 02/18] fix p2w currency enumeration --- server/channelserver/handlers_shop_gacha.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index 9cb849ca3..589dd2a6d 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -243,7 +243,7 @@ func handleMsgMhfGetGachaPlayHistory(s *Session, p mhfpacket.MHFPacket) { 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_premium, 0), COALESCE(gacha_trial, 0) FROM users WHERE id=$1", s.charID).Scan(&fp, &gp, >) + s.server.db.QueryRow("SELECT COALESCE(frontier_points, 0), COALESCE(gacha_premium, 0), COALESCE(gacha_trial, 0) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)", s.charID).Scan(&fp, &gp, >) resp := byteframe.NewByteFrame() resp.WriteUint32(gp) resp.WriteUint32(gt) @@ -337,9 +337,9 @@ func handleMsgMhfExchangeFpoint2Item(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfExchangeFpoint2Item) var balance uint32 var itemValue, quantity int - _ = s.server.db.QueryRow("SELECT quantity, fpoints FROM fpoint_items WHERE id=$1", pkt.TradeID).Scan(&quantity, &itemValue) + s.server.db.QueryRow("SELECT quantity, fpoints FROM fpoint_items WHERE id=$1", pkt.TradeID).Scan(&quantity, &itemValue) cost := (int(pkt.Quantity) * quantity) * itemValue - s.server.db.QueryRow("UPDATE users SET frontier_points=frontier_points::int - $1 WHERE id=$2 RETURNING frontier_points", cost, s.charID).Scan(&balance) + s.server.db.QueryRow("UPDATE users u SET frontier_points=frontier_points::int - $1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2) RETURNING frontier_points", cost, s.charID).Scan(&balance) bf := byteframe.NewByteFrame() bf.WriteUint32(balance) doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) @@ -351,7 +351,7 @@ func handleMsgMhfExchangeItem2Fpoint(s *Session, p mhfpacket.MHFPacket) { var itemValue, quantity int s.server.db.QueryRow("SELECT quantity, fpoints FROM fpoint_items WHERE id=$1", pkt.TradeID).Scan(&quantity, &itemValue) cost := (int(pkt.Quantity) / quantity) * itemValue - s.server.db.QueryRow("UPDATE users SET frontier_points=COALESCE(frontier_points::int + $1, $1) WHERE id=$2 RETURNING frontier_points", cost, s.charID).Scan(&balance) + s.server.db.QueryRow("UPDATE users u SET frontier_points=COALESCE(frontier_points::int + $1, $1) WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2) RETURNING frontier_points", cost, s.charID).Scan(&balance) bf := byteframe.NewByteFrame() bf.WriteUint32(balance) doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) From 7d4559b58950dc3c0fb8d9f980fe4fbf81e438de Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 15 Jan 2023 21:36:50 +1100 Subject: [PATCH 03/18] variable changes --- network/mhfpacket/msg_mhf_play_box_gacha.go | 20 ++++++------ .../mhfpacket/msg_mhf_play_normal_gacha.go | 10 +++--- .../mhfpacket/msg_mhf_play_stepup_gacha.go | 20 ++++++------ patch-schema/gacha-db-2.sql | 15 ++++----- server/channelserver/handlers_shop_gacha.go | 32 +++++++++---------- 5 files changed, 47 insertions(+), 50 deletions(-) diff --git a/network/mhfpacket/msg_mhf_play_box_gacha.go b/network/mhfpacket/msg_mhf_play_box_gacha.go index c47100e6f..f09c018f8 100644 --- a/network/mhfpacket/msg_mhf_play_box_gacha.go +++ b/network/mhfpacket/msg_mhf_play_box_gacha.go @@ -1,19 +1,19 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfPlayBoxGacha represents the MSG_MHF_PLAY_BOX_GACHA -type MsgMhfPlayBoxGacha struct{ +type MsgMhfPlayBoxGacha struct { AckHandle uint32 - GachaHash uint32 - RollType uint8 - CurrencyMode uint8 + GachaID uint32 + RollType uint8 + GachaType uint8 } // Opcode returns the ID associated with this packet type. @@ -24,9 +24,9 @@ func (m *MsgMhfPlayBoxGacha) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfPlayBoxGacha) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.GachaHash = bf.ReadUint32() + m.GachaID = bf.ReadUint32() m.RollType = bf.ReadUint8() - m.CurrencyMode = bf.ReadUint8() + m.GachaType = bf.ReadUint8() return nil } diff --git a/network/mhfpacket/msg_mhf_play_normal_gacha.go b/network/mhfpacket/msg_mhf_play_normal_gacha.go index 8d1844c4d..46f2706a6 100644 --- a/network/mhfpacket/msg_mhf_play_normal_gacha.go +++ b/network/mhfpacket/msg_mhf_play_normal_gacha.go @@ -10,10 +10,10 @@ import ( // MsgMhfPlayNormalGacha represents the MSG_MHF_PLAY_NORMAL_GACHA type MsgMhfPlayNormalGacha struct { - AckHandle uint32 - GachaID uint32 - RollType uint8 - CurrencyMode uint8 + AckHandle uint32 + GachaID uint32 + RollType uint8 + GachaType uint8 } // Opcode returns the ID associated with this packet type. @@ -26,7 +26,7 @@ func (m *MsgMhfPlayNormalGacha) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Cl m.AckHandle = bf.ReadUint32() m.GachaID = bf.ReadUint32() m.RollType = bf.ReadUint8() - m.CurrencyMode = bf.ReadUint8() + m.GachaType = bf.ReadUint8() return nil } diff --git a/network/mhfpacket/msg_mhf_play_stepup_gacha.go b/network/mhfpacket/msg_mhf_play_stepup_gacha.go index 653fc799f..83808ae1a 100644 --- a/network/mhfpacket/msg_mhf_play_stepup_gacha.go +++ b/network/mhfpacket/msg_mhf_play_stepup_gacha.go @@ -1,19 +1,19 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfPlayStepupGacha represents the MSG_MHF_PLAY_STEPUP_GACHA -type MsgMhfPlayStepupGacha struct{ +type MsgMhfPlayStepupGacha struct { AckHandle uint32 - GachaHash uint32 - RollType uint8 - CurrencyMode uint8 + GachaID uint32 + RollType uint8 + GachaType uint8 } // Opcode returns the ID associated with this packet type. @@ -24,9 +24,9 @@ func (m *MsgMhfPlayStepupGacha) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfPlayStepupGacha) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.GachaHash = bf.ReadUint32() + m.GachaID = bf.ReadUint32() m.RollType = bf.ReadUint8() - m.CurrencyMode = bf.ReadUint8() + m.GachaType = bf.ReadUint8() return nil } diff --git a/patch-schema/gacha-db-2.sql b/patch-schema/gacha-db-2.sql index 857ce8a47..1ed90d7a4 100644 --- a/patch-schema/gacha-db-2.sql +++ b/patch-schema/gacha-db-2.sql @@ -25,14 +25,13 @@ CREATE TABLE IF NOT EXISTS public.gacha_shop ( 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 + url_banner TEXT, + url_feature TEXT, + url_thumbnail TEXT, + wide BOOLEAN, + recommended BOOLEAN, + gacha_type INTEGER, + hidden BOOLEAN ); DROP TABLE IF EXISTS public.gacha_shop_items; diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index 589dd2a6d..b50809967 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -29,14 +29,13 @@ type Gacha struct { MinGR uint32 `db:"min_gr"` MinHR uint32 `db:"min_hr"` Name string `db:"name"` - Link1 string `db:"link1"` - Link2 string `db:"link2"` - Link3 string `db:"link3"` - IsWideBanner bool `db:"is_wide_banner"` - Flag1 uint8 `db:"flag1"` - Flag2 uint8 `db:"flag2"` - Flag3 uint8 `db:"flag3"` - Flag4 uint8 `db:"flag4"` + URLBanner string `db:"url_banner"` + URLFeature string `db:"url_feature"` + URLThumbnail string `db:"url_thumbnail"` + Wide bool `db:"wide"` + Recommended bool `db:"recommended"` + GachaType uint8 `db:"gacha_type"` + Hidden bool `db:"hidden"` } type GachaEntry struct { @@ -72,7 +71,7 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) { switch pkt.ShopType { case 1: // Running gachas var count uint16 - shopEntries, err := s.server.db.Queryx("SELECT id, min_gr, min_hr, name, link1, link2, link3, is_wide_banner, flag1, flag2, flag3, flag4 FROM gacha_shop") + shopEntries, err := s.server.db.Queryx("SELECT id, min_gr, min_hr, name, url_banner, url_feature, url_thumbnail, wide, recommended, gacha_type, hidden FROM gacha_shop") if err != nil { doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) return @@ -91,14 +90,13 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) { resp.WriteUint32(gacha.MinHR) resp.WriteUint32(0) // only 0 in known packet ps.Uint8(resp, gacha.Name, true) - ps.Uint8(resp, gacha.Link1, false) - ps.Uint8(resp, gacha.Link2, false) - resp.WriteBool(gacha.IsWideBanner) - ps.Uint8(resp, gacha.Link3, false) - resp.WriteUint8(gacha.Flag1) - resp.WriteUint8(gacha.Flag2) - resp.WriteUint8(gacha.Flag3) - resp.WriteUint8(gacha.Flag4) + ps.Uint8(resp, gacha.URLBanner, false) + ps.Uint8(resp, gacha.URLFeature, false) + resp.WriteBool(gacha.Wide) + ps.Uint8(resp, gacha.URLThumbnail, false) + resp.WriteBool(gacha.Recommended) + resp.WriteUint8(gacha.GachaType) + resp.WriteBool(gacha.Hidden) count++ } resp.Seek(0, 0) From 452404e48ca947c61166f6acccafe7460750ef18 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 12 Feb 2023 17:00:37 +1100 Subject: [PATCH 04/18] fix gacha enumeration recommendation tag --- server/channelserver/handlers_shop_gacha.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index b50809967..df1f77288 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -94,7 +94,12 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) { ps.Uint8(resp, gacha.URLFeature, false) resp.WriteBool(gacha.Wide) ps.Uint8(resp, gacha.URLThumbnail, false) - resp.WriteBool(gacha.Recommended) + resp.WriteUint8(0) // Unk + if gacha.Recommended { + resp.WriteUint8(2) + } else { + resp.WriteUint8(0) + } resp.WriteUint8(gacha.GachaType) resp.WriteBool(gacha.Hidden) count++ From 9805991c9558a56cab60477e3c47b6112081d3fe Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 13 Feb 2023 23:41:01 +1100 Subject: [PATCH 05/18] track gacha coin spending correctly --- server/channelserver/handlers_shop_gacha.go | 23 +++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index df1f77288..2a4efff20 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -254,6 +254,16 @@ func handleMsgMhfGetGachaPoint(s *Session, p mhfpacket.MHFPacket) { doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } +func spendGachaCoin(s *Session, quantity uint16) { + var gt uint16 + s.server.db.QueryRow(`SELECT COALESCE(gacha_trial, 0) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(>) + if quantity <= gt { + s.server.db.Exec(`UPDATE users u SET gacha_trial=gacha_trial-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, quantity, s.charID) + } else { + s.server.db.Exec(`UPDATE users u SET gacha_premium=gacha_premium-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, quantity, s.charID) + } +} + func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfPlayNormalGacha) bf := byteframe.NewByteFrame() @@ -262,6 +272,19 @@ func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) { var rewards []GachaItem var reward GachaItem var totalWeight float64 + + err := s.server.db.QueryRowx(`SELECT item_type, item_number, rolls FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2`, pkt.GachaID, pkt.RollType).StructScan(&entry) + if err != nil { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) + return + } + switch entry.ItemType { + case 19: + fallthrough + case 20: + spendGachaCoin(s, entry.ItemNumber) + } + entries, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID) if err != nil { doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) From 403b5f1c7f84212578ab49adaf9fe1d32fec2368 Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 15 Feb 2023 00:13:43 +1100 Subject: [PATCH 06/18] add support for multiple rolls --- server/channelserver/handlers_shop_gacha.go | 51 +++++++++++++-------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index 2a4efff20..2648bd889 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -279,11 +279,25 @@ func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) { return } switch entry.ItemType { + /* + valid types that need manual savedata manipulation: + - Ryoudan Points + - Bond Points + - Image Change Points + valid types that work (no additional code needed): + - Tore Points + - Festa Points + */ + case 17: + _ = addPointNetcafe(s, int(entry.ItemNumber)*-1) case 19: fallthrough case 20: spendGachaCoin(s, entry.ItemNumber) + case 21: + s.server.db.Exec("UPDATE users u SET frontier_points=frontier_points-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", entry.ItemNumber, s.charID) } + rolls := int(entry.Rolls) entries, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID) if err != nil { @@ -298,31 +312,32 @@ func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) { 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 i := 0; i < rolls; i++ { + 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 } - 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) { From e1986cb58b95575c93fba39aad30e69b96a1842a Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 15 Feb 2023 00:22:28 +1100 Subject: [PATCH 07/18] create transactGacha function --- server/channelserver/handlers_shop_gacha.go | 42 ++++++++++++--------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index 2648bd889..df993a8ca 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -264,21 +264,15 @@ func spendGachaCoin(s *Session, quantity uint16) { } } -func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfPlayNormalGacha) - bf := byteframe.NewByteFrame() - var gachaEntries []GachaEntry - var entry GachaEntry - var rewards []GachaItem - var reward GachaItem - var totalWeight float64 - - err := s.server.db.QueryRowx(`SELECT item_type, item_number, rolls FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2`, pkt.GachaID, pkt.RollType).StructScan(&entry) +func transactGacha(s *Session, gachaID uint32, rollID uint8) (error, int) { + var itemType uint8 + var itemNumber uint16 + var rolls int + err := s.server.db.QueryRowx(`SELECT item_type, item_number, rolls FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2`, gachaID, rollID).Scan(&itemType, &itemNumber, &rolls) if err != nil { - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) - return + return err, 0 } - switch entry.ItemType { + switch itemType { /* valid types that need manual savedata manipulation: - Ryoudan Points @@ -289,16 +283,30 @@ func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) { - Festa Points */ case 17: - _ = addPointNetcafe(s, int(entry.ItemNumber)*-1) + _ = addPointNetcafe(s, int(itemNumber)*-1) case 19: fallthrough case 20: - spendGachaCoin(s, entry.ItemNumber) + spendGachaCoin(s, itemNumber) case 21: - s.server.db.Exec("UPDATE users u SET frontier_points=frontier_points-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", entry.ItemNumber, s.charID) + s.server.db.Exec("UPDATE users u SET frontier_points=frontier_points-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", itemNumber, s.charID) } - rolls := int(entry.Rolls) + return nil, rolls +} +func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfPlayNormalGacha) + bf := byteframe.NewByteFrame() + var gachaEntries []GachaEntry + var entry GachaEntry + var rewards []GachaItem + var reward GachaItem + var totalWeight float64 + err, rolls := transactGacha(s, pkt.GachaID, pkt.RollType) + if err != nil { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) + return + } entries, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID) if err != nil { doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) From e73d4a03f4b4e05e76433eee722eeba0cd6d676e Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 15 Feb 2023 23:30:42 +1100 Subject: [PATCH 08/18] fix gacha rolling and add guaranteed rewards --- server/channelserver/handlers_shop_gacha.go | 35 ++++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index df993a8ca..045fe5815 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -294,6 +294,19 @@ func transactGacha(s *Session, gachaID uint32, rollID uint8) (error, int) { return nil, rolls } +func getGuaranteedItems(s *Session, gachaID uint32, rollID uint8) []GachaItem { + var rewards []GachaItem + var reward GachaItem + items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = (SELECT id FROM gacha_entries WHERE entry_type = $1 AND gacha_id = $2)`, rollID, gachaID) + if err == nil { + for items.Next() { + items.StructScan(&reward) + rewards = append(rewards, reward) + } + } + return rewards +} + func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfPlayNormalGacha) bf := byteframe.NewByteFrame() @@ -307,6 +320,19 @@ func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) { doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) return } + + temp := byteframe.NewByteFrame() + + /* Optional extended functionality + guaranteedItems := getGuaranteedItems(s, pkt.GachaID, pkt.RollType) + for _, item := range guaranteedItems { + temp.WriteUint8(item.ItemType) + temp.WriteUint16(reward.ItemID) + temp.WriteUint16(reward.Quantity) + temp.WriteUint8(0) // Lowest rarity + } + */ + entries, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID) if err != nil { doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) @@ -317,10 +343,9 @@ func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) { gachaEntries = append(gachaEntries, entry) totalWeight += entry.Weight } - result := rand.Float64() * totalWeight - var rewardCount uint8 - temp := byteframe.NewByteFrame() + for i := 0; i < rolls; i++ { + result := rand.Float64() * totalWeight for _, entry := range gachaEntries { result -= entry.Weight if result < 0 { @@ -330,7 +355,6 @@ func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) { return } for items.Next() { - rewardCount++ items.StructScan(&reward) rewards = append(rewards, reward) temp.WriteUint8(reward.ItemType) @@ -342,7 +366,8 @@ func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) { } } } - bf.WriteUint8(rewardCount) + + bf.WriteUint8(uint8(len(rewards))) bf.WriteBytes(temp.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) addGachaItem(s, rewards) From 036c4adba1f0f4450199988a09a6ca8995534c97 Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 15 Feb 2023 23:31:14 +1100 Subject: [PATCH 09/18] fix gacha koban my mission exchange --- server/channelserver/handlers_shop_gacha.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index 045fe5815..1a7b2d8c7 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -402,9 +402,14 @@ func addGachaItem(s *Session, items []GachaItem) { } 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}) + if pkt.TrialCoins > 0 { + s.server.db.Exec(`UPDATE users u SET gacha_trial=gacha_trial-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, pkt.TrialCoins, s.charID) + } + if pkt.PremiumCoins > 0 { + s.server.db.Exec(`UPDATE users u SET gacha_premium=gacha_premium-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, pkt.PremiumCoins, s.charID) + } + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } func handleMsgMhfExchangeFpoint2Item(s *Session, p mhfpacket.MHFPacket) { From 8e6fa5e349ede7d9824482db239af16d84953024 Mon Sep 17 00:00:00 2001 From: wish Date: Fri, 17 Feb 2023 00:22:58 +1100 Subject: [PATCH 10/18] fix gacha reward handling --- server/channelserver/handlers_shop_gacha.go | 23 ++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index 1a7b2d8c7..955c19e7e 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -388,9 +388,6 @@ func addGachaItem(s *Session, items []GachaItem) { }) } } - if len(items) > 36 { - items = items[:36] - } newItem := byteframe.NewByteFrame() newItem.WriteUint8(uint8(len(items))) for i := range items { @@ -602,9 +599,25 @@ func handleMsgMhfReceiveGachaItem(s *Session, p mhfpacket.MHFPacket) { if err != nil { data = []byte{0x00} } - doAckBufSucceed(s, pkt.AckHandle, data) + + if data[0] > 36 { + resp := byteframe.NewByteFrame() + resp.WriteUint8(36) + resp.WriteBytes(data[1:181]) + doAckBufSucceed(s, pkt.AckHandle, resp.Data()) + } else { + doAckBufSucceed(s, pkt.AckHandle, data) + } + if !pkt.Freeze { - s.server.db.Exec("UPDATE characters SET gacha_items = null WHERE id = $1", s.charID) + if data[0] > 36 { + update := byteframe.NewByteFrame() + update.WriteUint8(uint8(len(data[181:]) / 5)) + update.WriteBytes(data[181:]) + s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", update.Data(), s.charID) + } else { + s.server.db.Exec("UPDATE characters SET gacha_items = null WHERE id = $1", s.charID) + } } } From c3306de2eeb7d4b9869ea68b460cf87d6fd986f1 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 18 Feb 2023 10:43:47 +1100 Subject: [PATCH 11/18] fix gacha rewards overflowing --- server/channelserver/handlers_shop_gacha.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index 955c19e7e..7119592f6 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -600,7 +600,8 @@ func handleMsgMhfReceiveGachaItem(s *Session, p mhfpacket.MHFPacket) { data = []byte{0x00} } - if data[0] > 36 { + // I think there are still some edge cases where rewards can be nulled via overflow + if data[0] > 36 || len(data) > 181 { resp := byteframe.NewByteFrame() resp.WriteUint8(36) resp.WriteBytes(data[1:181]) @@ -610,7 +611,7 @@ func handleMsgMhfReceiveGachaItem(s *Session, p mhfpacket.MHFPacket) { } if !pkt.Freeze { - if data[0] > 36 { + if data[0] > 36 || len(data) > 181 { update := byteframe.NewByteFrame() update.WriteUint8(uint8(len(data[181:]) / 5)) update.WriteBytes(data[181:]) From 0fcacc24a0aa2e2321e5aff4e4f18d22b923764c Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 18 Feb 2023 12:36:17 +1100 Subject: [PATCH 12/18] implement stepup gacha, fix unfulfilled rewards --- .../mhfpacket/msg_mhf_get_stepup_status.go | 16 +- patch-schema/gacha-db-2.sql | 9 + server/channelserver/handlers_shop_gacha.go | 238 +++++++----------- 3 files changed, 102 insertions(+), 161 deletions(-) diff --git a/network/mhfpacket/msg_mhf_get_stepup_status.go b/network/mhfpacket/msg_mhf_get_stepup_status.go index 1f04c7740..dcced8f4f 100644 --- a/network/mhfpacket/msg_mhf_get_stepup_status.go +++ b/network/mhfpacket/msg_mhf_get_stepup_status.go @@ -1,18 +1,18 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfGetStepupStatus represents the MSG_MHF_GET_STEPUP_STATUS -type MsgMhfGetStepupStatus struct{ +type MsgMhfGetStepupStatus struct { AckHandle uint32 - GachaHash uint32 - Unk uint8 + GachaID uint32 + Unk uint8 } // Opcode returns the ID associated with this packet type. @@ -23,7 +23,7 @@ func (m *MsgMhfGetStepupStatus) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfGetStepupStatus) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.GachaHash = bf.ReadUint32() + m.GachaID = bf.ReadUint32() m.Unk = bf.ReadUint8() return nil } diff --git a/patch-schema/gacha-db-2.sql b/patch-schema/gacha-db-2.sql index 1ed90d7a4..e789a5f5f 100644 --- a/patch-schema/gacha-db-2.sql +++ b/patch-schema/gacha-db-2.sql @@ -46,6 +46,7 @@ CREATE TABLE IF NOT EXISTS public.gacha_entries ( weight INTEGER, rarity INTEGER, rolls INTEGER, + frontier_points INTEGER, daily_limit INTEGER ); @@ -57,4 +58,12 @@ CREATE TABLE IF NOT EXISTS public.gacha_items ( quantity INTEGER ); +DROP TABLE IF EXISTS public.stepup_state; + +CREATE TABLE IF NOT EXISTS public.gacha_stepup ( + gacha_id INTEGER, + step INTEGER, + character_id INTEGER +); + END; \ No newline at end of file diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index 7119592f6..a2231aac3 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -39,15 +39,16 @@ type Gacha struct { } 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"` + EntryType uint8 `db:"entry_type"` + ID uint32 `db:"id"` + ItemType uint8 `db:"item_type"` + ItemNumber uint16 `db:"item_number"` + ItemQuantity uint16 `db:"item_quantity"` + Weight float64 `db:"weight"` + Rarity uint8 `db:"rarity"` + Rolls uint8 `db:"rolls"` + FrontierPoints uint16 `db:"frontier_points"` + DailyLimit uint8 `db:"daily_limit"` } type GachaItem struct { @@ -111,7 +112,7 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) { case 2: // Actual gacha 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) + entries, err := s.server.db.Queryx(`SELECT entry_type, id, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points FROM gacha_entries WHERE gacha_id = $1 ORDER BY weight DESC`, pkt.ShopID) if err != nil { doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) return @@ -153,7 +154,7 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint8(itemCount) } - bf.WriteUint16(0) + bf.WriteUint16(gachaEntry.FrontierPoints) bf.WriteUint8(gachaEntry.DailyLimit) bf.WriteUint8(0) bf.WriteBytes(temp.Data()) @@ -320,9 +321,7 @@ func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) { doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) return } - temp := byteframe.NewByteFrame() - /* Optional extended functionality guaranteedItems := getGuaranteedItems(s, pkt.GachaID, pkt.RollType) for _, item := range guaranteedItems { @@ -332,7 +331,6 @@ func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) { temp.WriteUint8(0) // Lowest rarity } */ - entries, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID) if err != nil { doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) @@ -343,8 +341,7 @@ func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) { gachaEntries = append(gachaEntries, entry) totalWeight += entry.Weight } - - for i := 0; i < rolls; i++ { + for { result := rand.Float64() * totalWeight for _, entry := range gachaEntries { result -= entry.Weight @@ -365,8 +362,10 @@ func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) { break } } + if rolls == len(rewards) { + break + } } - bf.WriteUint8(uint8(len(rewards))) bf.WriteBytes(temp.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) @@ -489,107 +488,66 @@ func handleMsgMhfGetFpointExchangeList(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfPlayStepupGacha(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfPlayStepupGacha) bf := byteframe.NewByteFrame() - bf.WriteUint8(1) // num items - bf.WriteUint8(0) // num non-guaranteed items - bf.WriteUint8(7) // item type - bf.WriteUint16(7) // item id - bf.WriteUint16(1) // quantity - bf.WriteUint8(0) // rarity + var gachaEntries []GachaEntry + var entry GachaEntry + var rewards []GachaItem + var reward GachaItem + var totalWeight float64 + err, rolls := transactGacha(s, pkt.GachaID, pkt.RollType) + if err != nil { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) + return + } + s.server.db.Exec("UPDATE users u SET frontier_points=frontier_points+(SELECT frontier_points FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2) WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$3)", pkt.GachaID, pkt.RollType, s.charID) + s.server.db.Exec(`DELETE FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID) + s.server.db.Exec(`INSERT INTO gacha_stepup (gacha_id, step, character_id) VALUES ($1, $2, $3)`, pkt.GachaID, pkt.RollType+1, s.charID) + temp := byteframe.NewByteFrame() + guaranteedItems := getGuaranteedItems(s, pkt.GachaID, pkt.RollType) + for _, item := range guaranteedItems { + temp.WriteUint8(item.ItemType) + temp.WriteUint16(item.ItemID) + temp.WriteUint16(item.Quantity) + temp.WriteUint8(0) + } + entries, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID) + if err != nil { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) + return + } + for entries.Next() { + entries.StructScan(&entry) + gachaEntries = append(gachaEntries, entry) + totalWeight += entry.Weight + } + for { + result := rand.Float64() * totalWeight + 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() { + items.StructScan(&reward) + rewards = append(rewards, reward) + temp.WriteUint8(reward.ItemType) + temp.WriteUint16(reward.ItemID) + temp.WriteUint16(reward.Quantity) + temp.WriteUint8(entry.Rarity) + } + break + } + } + if rolls == len(rewards) { + break + } + } + bf.WriteUint8(uint8(len(rewards) + len(guaranteedItems))) + bf.WriteUint8(uint8(len(rewards))) + bf.WriteBytes(temp.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) - /* - results := byte(0) - stepResults := byte(0) - resp := byteframe.NewByteFrame() - rollFrame := byteframe.NewByteFrame() - stepFrame := byteframe.NewByteFrame() - stepData := []byte{} - var currType, rarityIcon, rollsCount, itemCount byte - var currQuant, currNumber, percentage uint16 - var itemhash uint32 - var itemType, itemId, quantity pq.Int64Array - var items []lottery.Weighter - // get info for updating data and calculating costs - err := s.server.db.QueryRow("SELECT currType, currNumber, currQuant, rollsCount, itemCount, itemType, itemId, quantity FROM gacha_shop_items WHERE shophash=$1 AND entryType=$2", pkt.GachaHash, pkt.RollType).Scan(&currType, &currNumber, &currQuant, &rollsCount, &itemCount, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity)) - if err != nil { - panic(err) - } - // get existing items in storage if any - var data []byte - _ = s.server.db.QueryRow("SELECT gacha_items FROM characters WHERE id = $1", s.charID).Scan(&data) - if len(data) == 0 { - data = []byte{0x00} - } - // roll definition includes items with step up gachas that are appended last - for x := 0; x < int(itemCount); x++ { - stepFrame.WriteUint8(uint8(itemType[x])) - stepFrame.WriteUint16(uint16(itemId[x])) - stepFrame.WriteUint16(uint16(quantity[x])) - stepData = append(stepData, stepFrame.Data()...) - stepFrame.WriteUint8(0) // rarity still defined - stepResults++ - } - // get gacha items and iterate through them for gacha roll - shopEntries, err := s.server.db.Query("SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity FROM gacha_shop_items WHERE shophash=$1 AND entryType=100", pkt.GachaHash) - if err != nil { - panic(err) - } - for shopEntries.Next() { - err = shopEntries.Scan(&itemhash, &percentage, &rarityIcon, &itemCount, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity)) - if err != nil { - panic(err) - } - items = append(items, &gachaItem{itemhash: itemhash, percentage: percentage, rarityIcon: rarityIcon, itemCount: itemCount, itemType: itemType, itemId: itemId, quantity: quantity}) - } - // execute rolls, build response and update database - resp.WriteUint16(0) // results count goes here later - l := lottery.NewDefaultLottery() - for x := 0; x < int(rollsCount); x++ { - ind := l.Draw(items) - results += items[ind].(*gachaItem).itemCount - for y := 0; y < int(items[ind].(*gachaItem).itemCount); y++ { - // items in storage don't get rarity - rollFrame.WriteUint8(uint8(items[ind].(*gachaItem).itemType[y])) - rollFrame.WriteUint16(uint16(items[ind].(*gachaItem).itemId[y])) - rollFrame.WriteUint16(uint16(items[ind].(*gachaItem).quantity[y])) - data = append(data, rollFrame.Data()...) - rollFrame.Seek(0, 0) - // response needs all item info and the rarity - resp.WriteBytes(rollFrame.Data()) - resp.WriteUint8(items[ind].(*gachaItem).rarityIcon) - } - } - resp.WriteBytes(stepFrame.Data()) - resp.Seek(0, 0) - resp.WriteUint8(results + stepResults) - resp.WriteUint8(results) - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) - - // add claimables to DB - data = append(data, stepData...) - data[0] = data[0] + results + stepResults - _, err = s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", data, s.charID) - if err != nil { - s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err)) - } - // deduct gacha coins if relevant, items are handled fine by the standard savedata packet immediately afterwards - // reduce real if trial don't cover cost - if currType == 19 { - _, err = s.server.db.Exec(`UPDATE characters - SET gacha_trial = CASE WHEN (gacha_trial > $1) then gacha_trial - $1 else gacha_trial end, - gacha_prem = CASE WHEN NOT (gacha_trial > $1) then gacha_prem - $1 else gacha_prem end - WHERE id=$2`, currNumber, s.charID) - } - if err != nil { - s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err)) - } - // update step progression - _, err = s.server.db.Exec("UPDATE stepup_state SET step_progression = $1 WHERE char_id = $2", pkt.RollType+1, s.charID) - if err != nil { - s.logger.Fatal("Failed to update step_progression in db", zap.Error(err)) - } - - */ - } func handleMsgMhfReceiveGachaItem(s *Session, p mhfpacket.MHFPacket) { @@ -624,45 +582,19 @@ func handleMsgMhfReceiveGachaItem(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfGetStepupStatus(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetStepupStatus) - + // TODO: Reset daily (noon) + var step uint8 + s.server.db.QueryRow(`SELECT step FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID).Scan(&step) + var stepCheck int + s.server.db.QueryRow(`SELECT COUNT(1) FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2`, pkt.GachaID, step).Scan(&stepCheck) + if stepCheck == 0 { + s.server.db.Exec(`DELETE FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID) + step = 0 + } bf := byteframe.NewByteFrame() - bf.WriteUint8(0) // step + bf.WriteUint8(step) bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) - - /* - // 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) { From 7d5ec5a67e60463a9a7959613d9233d624b49517 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 18 Feb 2023 14:55:45 +1100 Subject: [PATCH 13/18] correctly enumerate box gacha --- server/channelserver/handlers_shop_gacha.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index a2231aac3..ccc1cd056 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -112,15 +112,15 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) { 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) entries, err := s.server.db.Queryx(`SELECT entry_type, id, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points FROM gacha_entries WHERE gacha_id = $1 ORDER BY weight DESC`, pkt.ShopID) if err != nil { doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) return } - var divisor float64 s.server.db.QueryRow(`SELECT COALESCE(SUM(weight) / 100000.0, 0) AS chance FROM gacha_entries WHERE gacha_id = $1`, pkt.ShopID).Scan(&divisor) - var entryCount uint16 bf.WriteUint16(0) gachaEntry := GachaEntry{} @@ -134,7 +134,11 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint16(0) bf.WriteUint16(gachaEntry.ItemNumber) bf.WriteUint16(gachaEntry.ItemQuantity) - bf.WriteUint16(uint16(gachaEntry.Weight / divisor)) + if gachaType >= 4 { // If box + bf.WriteUint16(1) + } else { + bf.WriteUint16(uint16(gachaEntry.Weight / divisor)) + } bf.WriteUint8(gachaEntry.Rarity) bf.WriteUint8(gachaEntry.Rolls) From dfe4998649842266c66eb8ca6b3170a6b3cbad86 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 18 Feb 2023 15:57:17 +1100 Subject: [PATCH 14/18] implement box gacha --- .../mhfpacket/msg_mhf_get_box_gacha_info.go | 14 +- .../mhfpacket/msg_mhf_reset_box_gacha_info.go | 14 +- patch-schema/gacha-db-2.sql | 8 + server/channelserver/handlers_shop_gacha.go | 177 ++++++------------ 4 files changed, 84 insertions(+), 129 deletions(-) diff --git a/network/mhfpacket/msg_mhf_get_box_gacha_info.go b/network/mhfpacket/msg_mhf_get_box_gacha_info.go index 54e7eb111..b944e6c11 100644 --- a/network/mhfpacket/msg_mhf_get_box_gacha_info.go +++ b/network/mhfpacket/msg_mhf_get_box_gacha_info.go @@ -1,17 +1,17 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfGetBoxGachaInfo represents the MSG_MHF_GET_BOX_GACHA_INFO -type MsgMhfGetBoxGachaInfo struct{ +type MsgMhfGetBoxGachaInfo struct { AckHandle uint32 - GachaHash uint32 + GachaID uint32 } // Opcode returns the ID associated with this packet type. @@ -22,7 +22,7 @@ func (m *MsgMhfGetBoxGachaInfo) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfGetBoxGachaInfo) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.GachaHash = bf.ReadUint32() + m.GachaID = bf.ReadUint32() return nil } diff --git a/network/mhfpacket/msg_mhf_reset_box_gacha_info.go b/network/mhfpacket/msg_mhf_reset_box_gacha_info.go index 233bc49d9..7a8c3ffcf 100644 --- a/network/mhfpacket/msg_mhf_reset_box_gacha_info.go +++ b/network/mhfpacket/msg_mhf_reset_box_gacha_info.go @@ -1,17 +1,17 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfResetBoxGachaInfo represents the MSG_MHF_RESET_BOX_GACHA_INFO -type MsgMhfResetBoxGachaInfo struct{ +type MsgMhfResetBoxGachaInfo struct { AckHandle uint32 - GachaHash uint32 + GachaID uint32 } // Opcode returns the ID associated with this packet type. @@ -22,7 +22,7 @@ func (m *MsgMhfResetBoxGachaInfo) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfResetBoxGachaInfo) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.GachaHash = bf.ReadUint32() + m.GachaID = bf.ReadUint32() return nil } diff --git a/patch-schema/gacha-db-2.sql b/patch-schema/gacha-db-2.sql index e789a5f5f..d3faa1ed9 100644 --- a/patch-schema/gacha-db-2.sql +++ b/patch-schema/gacha-db-2.sql @@ -66,4 +66,12 @@ CREATE TABLE IF NOT EXISTS public.gacha_stepup ( character_id INTEGER ); +DROP TABLE IF EXISTS public.lucky_box_state; + +CREATE TABLE IF NOT EXISTS public.gacha_box ( + gacha_id INTEGER, + entry_id INTEGER, + character_id INTEGER +); + END; \ No newline at end of file diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index ccc1cd056..12b23dff0 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -5,7 +5,6 @@ import ( "erupe-ce/common/byteframe" ps "erupe-ce/common/pascalstring" "erupe-ce/network/mhfpacket" - "github.com/lib/pq" "math/rand" ) @@ -607,129 +606,77 @@ func handleMsgMhfPlayFreeGacha(s *Session, p mhfpacket.MHFPacket) { 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++ + entries, err := s.server.db.Queryx(`SELECT entry_id FROM gacha_box WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID) + if err != nil { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) + return } - resp.Seek(0, 0) - resp.WriteUint8(uint8(count)) - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) + var entryIDs []uint32 + for entries.Next() { + var entryID uint32 + entries.Scan(&entryID) + entryIDs = append(entryIDs, entryID) + } + bf := byteframe.NewByteFrame() + bf.WriteUint8(uint8(len(entryIDs))) + for i := range entryIDs { + bf.WriteUint32(entryIDs[i]) + bf.WriteBool(true) + } + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } func handleMsgMhfPlayBoxGacha(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfPlayBoxGacha) - bf := byteframe.NewByteFrame() - bf.WriteUint8(1) // num items - bf.WriteUint8(7) // item type - bf.WriteUint16(7) // item id - bf.WriteUint16(1) // quantity - bf.WriteUint8(0) // rarity + var gachaEntries []GachaEntry + var entry GachaEntry + var rewards []GachaItem + var reward GachaItem + err, rolls := transactGacha(s, pkt.GachaID, pkt.RollType) + if err != nil { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) + return + } + temp := byteframe.NewByteFrame() + entries, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID) + if err != nil { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) + return + } + for entries.Next() { + entries.StructScan(&entry) + gachaEntries = append(gachaEntries, entry) + } + for { + randomEntry := rand.Intn(len(gachaEntries)) + items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = $1`, gachaEntries[randomEntry].ID) + if err != nil { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) + return + } + for items.Next() { + items.StructScan(&reward) + rewards = append(rewards, reward) + temp.WriteUint8(reward.ItemType) + temp.WriteUint16(reward.ItemID) + temp.WriteUint16(reward.Quantity) + temp.WriteUint8(0) + } + s.server.db.Exec(`INSERT INTO gacha_box (gacha_id, entry_id, character_id) VALUES ($1, $2, $3)`, pkt.GachaID, gachaEntries[randomEntry].ID, s.charID) + gachaEntries[randomEntry] = gachaEntries[len(gachaEntries)-1] + gachaEntries = gachaEntries[:len(gachaEntries)-1] + if rolls == len(rewards) { + break + } + } + bf.WriteUint8(uint8(len(rewards))) + bf.WriteBytes(temp.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) - - /* - var currType, rarityIcon, rollsCount, itemCount byte - var currQuant, currNumber, percentage uint16 - var itemhash uint32 - var itemType, itemId, quantity, usedItemHash pq.Int64Array - var items []lottery.Weighter - // get info for updating data and calculating costs - err := s.server.db.QueryRow("SELECT currType, currNumber, currQuant, rollsCount FROM gacha_shop_items WHERE shophash=$1 AND entryType=$2", pkt.GachaHash, pkt.RollType).Scan(&currType, &currNumber, &currQuant, &rollsCount) - if err != nil { - panic(err) - } - // get existing items in storage if any - var data []byte - _ = s.server.db.QueryRow("SELECT gacha_items FROM characters WHERE id = $1", s.charID).Scan(&data) - if len(data) == 0 { - data = []byte{0x00} - } - // get gacha items and iterate through them for gacha roll - shopEntries, err := s.server.db.Query(`SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity - FROM gacha_shop_items - WHERE shophash=$1 AND entryType=100 - EXCEPT ALL SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity - FROM gacha_shop_items gsi JOIN lucky_box_state lbs ON gsi.itemhash = ANY(lbs.used_itemhash) - WHERE lbs.char_id=$2`, pkt.GachaHash, s.charID) - if err != nil { - panic(err) - } - for shopEntries.Next() { - err = shopEntries.Scan(&itemhash, &percentage, &rarityIcon, &itemCount, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity)) - if err != nil { - panic(err) - } - items = append(items, &gachaItem{itemhash: itemhash, percentage: percentage, rarityIcon: rarityIcon, itemCount: itemCount, itemType: itemType, itemId: itemId, quantity: quantity}) - } - // execute rolls, build response and update database - results := byte(0) - resp := byteframe.NewByteFrame() - dbUpdate := byteframe.NewByteFrame() - resp.WriteUint8(0) // results go here later - l := lottery.NewDefaultLottery() - for x := 0; x < int(rollsCount); x++ { - ind := l.Draw(items) - results += items[ind].(*gachaItem).itemCount - for y := 0; y < int(items[ind].(*gachaItem).itemCount); y++ { - // items in storage don't get rarity - dbUpdate.WriteUint8(uint8(items[ind].(*gachaItem).itemType[y])) - dbUpdate.WriteUint16(uint16(items[ind].(*gachaItem).itemId[y])) - dbUpdate.WriteUint16(uint16(items[ind].(*gachaItem).quantity[y])) - data = append(data, dbUpdate.Data()...) - dbUpdate.Seek(0, 0) - // response needs all item info and the rarity - resp.WriteBytes(dbUpdate.Data()) - resp.WriteUint8(items[ind].(*gachaItem).rarityIcon) - - usedItemHash = append(usedItemHash, int64(items[ind].(*gachaItem).itemhash)) - } - // remove rolled - items = append(items[:ind], items[ind+1:]...) - } - resp.Seek(0, 0) - resp.WriteUint8(results) - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) - - // add claimables to DB - data[0] = data[0] + results - _, err = s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", data, s.charID) - if err != nil { - s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err)) - } - // update lucky_box_state - _, err = s.server.db.Exec(`INSERT INTO lucky_box_state (char_id, shophash, used_itemhash) - VALUES ($1,$2,$3) ON CONFLICT (char_id, shophash) - DO UPDATE SET used_itemhash = COALESCE(lucky_box_state.used_itemhash::int[] || $3::int[], $3::int[]) - WHERE EXCLUDED.char_id=$1 AND EXCLUDED.shophash=$2`, s.charID, pkt.GachaHash, usedItemHash) - if err != nil { - s.logger.Fatal("Failed to update lucky box state in db", zap.Error(err)) - } - // deduct gacha coins if relevant, items are handled fine by the standard savedata packet immediately afterwards - if currType == 19 { - _, err = s.server.db.Exec(`UPDATE characters - SET gacha_trial = CASE WHEN (gacha_trial > $1) then gacha_trial - $1 else gacha_trial end, gacha_prem = CASE WHEN NOT (gacha_trial > $1) then gacha_prem - $1 else gacha_prem end - WHERE id=$2`, currNumber, s.charID) - } - if err != nil { - s.logger.Fatal("Failed to update gacha_trial in db", zap.Error(err)) - } - - */ } func handleMsgMhfResetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfResetBoxGachaInfo) - _, err := s.server.db.Exec("DELETE FROM lucky_box_state WHERE shophash=$1 AND char_id=$2", pkt.GachaHash, s.charID) - if err != nil { - panic(err) - } - doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + s.server.db.Exec("DELETE FROM gacha_box WHERE gacha_id = $1 AND character_id = $2", pkt.GachaID, s.charID) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } From 9b866967b86de25a151e7847509117627817a485 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 18 Feb 2023 15:58:38 +1100 Subject: [PATCH 15/18] add stepup and box gacha rewards to storage --- server/channelserver/handlers_shop_gacha.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index 12b23dff0..0cade9863 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -551,6 +551,8 @@ func handleMsgMhfPlayStepupGacha(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint8(uint8(len(rewards))) bf.WriteBytes(temp.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) + addGachaItem(s, rewards) + addGachaItem(s, guaranteedItems) } func handleMsgMhfReceiveGachaItem(s *Session, p mhfpacket.MHFPacket) { @@ -673,6 +675,7 @@ func handleMsgMhfPlayBoxGacha(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint8(uint8(len(rewards))) bf.WriteBytes(temp.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) + addGachaItem(s, rewards) } func handleMsgMhfResetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) { From 66b13c8f58fe30e61b901e7db59edd99587e3fba Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 18 Feb 2023 17:20:54 +1100 Subject: [PATCH 16/18] rearrange gacha functions --- server/channelserver/handlers_shop_gacha.go | 438 ++++++++++---------- 1 file changed, 219 insertions(+), 219 deletions(-) diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index 0cade9863..71ceb66d6 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -258,6 +258,17 @@ func handleMsgMhfGetGachaPoint(s *Session, p mhfpacket.MHFPacket) { doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } +func handleMsgMhfUseGachaPoint(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfUseGachaPoint) + if pkt.TrialCoins > 0 { + s.server.db.Exec(`UPDATE users u SET gacha_trial=gacha_trial-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, pkt.TrialCoins, s.charID) + } + if pkt.PremiumCoins > 0 { + s.server.db.Exec(`UPDATE users u SET gacha_premium=gacha_premium-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, pkt.PremiumCoins, s.charID) + } + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) +} + func spendGachaCoin(s *Session, quantity uint16) { var gt uint16 s.server.db.QueryRow(`SELECT COALESCE(gacha_trial, 0) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(>) @@ -311,6 +322,61 @@ func getGuaranteedItems(s *Session, gachaID uint32, rollID uint8) []GachaItem { return rewards } +func addGachaItem(s *Session, items []GachaItem) { + var data []byte + s.server.db.QueryRow(`SELECT gacha_items FROM characters WHERE id = $1`, s.charID).Scan(&data) + if len(data) > 0 { + numItems := int(data[0]) + data = data[1:] + oldItem := byteframe.NewByteFrameFromBytes(data) + for i := 0; i < numItems; i++ { + items = append(items, GachaItem{ + ItemType: oldItem.ReadUint8(), + ItemID: oldItem.ReadUint16(), + Quantity: oldItem.ReadUint16(), + }) + } + } + newItem := byteframe.NewByteFrame() + newItem.WriteUint8(uint8(len(items))) + for i := range items { + newItem.WriteUint8(items[i].ItemType) + newItem.WriteUint16(items[i].ItemID) + newItem.WriteUint16(items[i].Quantity) + } + s.server.db.Exec(`UPDATE characters SET gacha_items = $1 WHERE id = $2`, newItem.Data(), s.charID) +} + +func handleMsgMhfReceiveGachaItem(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfReceiveGachaItem) + var data []byte + err := s.server.db.QueryRow("SELECT COALESCE(gacha_items, $2) FROM characters WHERE id = $1", s.charID, []byte{0x00}).Scan(&data) + if err != nil { + data = []byte{0x00} + } + + // I think there are still some edge cases where rewards can be nulled via overflow + if data[0] > 36 || len(data) > 181 { + resp := byteframe.NewByteFrame() + resp.WriteUint8(36) + resp.WriteBytes(data[1:181]) + doAckBufSucceed(s, pkt.AckHandle, resp.Data()) + } else { + doAckBufSucceed(s, pkt.AckHandle, data) + } + + if !pkt.Freeze { + if data[0] > 36 || len(data) > 181 { + update := byteframe.NewByteFrame() + update.WriteUint8(uint8(len(data[181:]) / 5)) + update.WriteBytes(data[181:]) + s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", update.Data(), s.charID) + } else { + s.server.db.Exec("UPDATE characters SET gacha_items = null WHERE id = $1", s.charID) + } + } +} + func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfPlayNormalGacha) bf := byteframe.NewByteFrame() @@ -375,39 +441,165 @@ func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) { addGachaItem(s, rewards) } -func addGachaItem(s *Session, items []GachaItem) { - var data []byte - s.server.db.QueryRow(`SELECT gacha_items FROM characters WHERE id = $1`, s.charID).Scan(&data) - if len(data) > 0 { - numItems := int(data[0]) - data = data[1:] - oldItem := byteframe.NewByteFrameFromBytes(data) - for i := 0; i < numItems; i++ { - items = append(items, GachaItem{ - ItemType: oldItem.ReadUint8(), - ItemID: oldItem.ReadUint16(), - Quantity: oldItem.ReadUint16(), - }) +func handleMsgMhfPlayStepupGacha(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfPlayStepupGacha) + bf := byteframe.NewByteFrame() + var gachaEntries []GachaEntry + var entry GachaEntry + var rewards []GachaItem + var reward GachaItem + var totalWeight float64 + err, rolls := transactGacha(s, pkt.GachaID, pkt.RollType) + if err != nil { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) + return + } + s.server.db.Exec("UPDATE users u SET frontier_points=frontier_points+(SELECT frontier_points FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2) WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$3)", pkt.GachaID, pkt.RollType, s.charID) + s.server.db.Exec(`DELETE FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID) + s.server.db.Exec(`INSERT INTO gacha_stepup (gacha_id, step, character_id) VALUES ($1, $2, $3)`, pkt.GachaID, pkt.RollType+1, s.charID) + temp := byteframe.NewByteFrame() + guaranteedItems := getGuaranteedItems(s, pkt.GachaID, pkt.RollType) + for _, item := range guaranteedItems { + temp.WriteUint8(item.ItemType) + temp.WriteUint16(item.ItemID) + temp.WriteUint16(item.Quantity) + temp.WriteUint8(0) + } + entries, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID) + if err != nil { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) + return + } + for entries.Next() { + entries.StructScan(&entry) + gachaEntries = append(gachaEntries, entry) + totalWeight += entry.Weight + } + for { + result := rand.Float64() * totalWeight + 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() { + items.StructScan(&reward) + rewards = append(rewards, reward) + temp.WriteUint8(reward.ItemType) + temp.WriteUint16(reward.ItemID) + temp.WriteUint16(reward.Quantity) + temp.WriteUint8(entry.Rarity) + } + break + } + } + if rolls == len(rewards) { + break } } - newItem := byteframe.NewByteFrame() - newItem.WriteUint8(uint8(len(items))) - for i := range items { - newItem.WriteUint8(items[i].ItemType) - newItem.WriteUint16(items[i].ItemID) - newItem.WriteUint16(items[i].Quantity) - } - s.server.db.Exec(`UPDATE characters SET gacha_items = $1 WHERE id = $2`, newItem.Data(), s.charID) + bf.WriteUint8(uint8(len(rewards) + len(guaranteedItems))) + bf.WriteUint8(uint8(len(rewards))) + bf.WriteBytes(temp.Data()) + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) + addGachaItem(s, rewards) + addGachaItem(s, guaranteedItems) } -func handleMsgMhfUseGachaPoint(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfUseGachaPoint) - if pkt.TrialCoins > 0 { - s.server.db.Exec(`UPDATE users u SET gacha_trial=gacha_trial-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, pkt.TrialCoins, s.charID) +func handleMsgMhfGetStepupStatus(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfGetStepupStatus) + // TODO: Reset daily (noon) + var step uint8 + s.server.db.QueryRow(`SELECT step FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID).Scan(&step) + var stepCheck int + s.server.db.QueryRow(`SELECT COUNT(1) FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2`, pkt.GachaID, step).Scan(&stepCheck) + if stepCheck == 0 { + s.server.db.Exec(`DELETE FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID) + step = 0 } - if pkt.PremiumCoins > 0 { - s.server.db.Exec(`UPDATE users u SET gacha_premium=gacha_premium-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, pkt.PremiumCoins, s.charID) + bf := byteframe.NewByteFrame() + bf.WriteUint8(step) + bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) +} + +func handleMsgMhfGetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfGetBoxGachaInfo) + entries, err := s.server.db.Queryx(`SELECT entry_id FROM gacha_box WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID) + if err != nil { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) + return } + var entryIDs []uint32 + for entries.Next() { + var entryID uint32 + entries.Scan(&entryID) + entryIDs = append(entryIDs, entryID) + } + bf := byteframe.NewByteFrame() + bf.WriteUint8(uint8(len(entryIDs))) + for i := range entryIDs { + bf.WriteUint32(entryIDs[i]) + bf.WriteBool(true) + } + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) +} + +func handleMsgMhfPlayBoxGacha(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfPlayBoxGacha) + bf := byteframe.NewByteFrame() + var gachaEntries []GachaEntry + var entry GachaEntry + var rewards []GachaItem + var reward GachaItem + err, rolls := transactGacha(s, pkt.GachaID, pkt.RollType) + if err != nil { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) + return + } + temp := byteframe.NewByteFrame() + entries, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID) + if err != nil { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) + return + } + for entries.Next() { + entries.StructScan(&entry) + gachaEntries = append(gachaEntries, entry) + } + for { + randomEntry := rand.Intn(len(gachaEntries)) + items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = $1`, gachaEntries[randomEntry].ID) + if err != nil { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) + return + } + for items.Next() { + items.StructScan(&reward) + rewards = append(rewards, reward) + temp.WriteUint8(reward.ItemType) + temp.WriteUint16(reward.ItemID) + temp.WriteUint16(reward.Quantity) + temp.WriteUint8(0) + } + s.server.db.Exec(`INSERT INTO gacha_box (gacha_id, entry_id, character_id) VALUES ($1, $2, $3)`, pkt.GachaID, gachaEntries[randomEntry].ID, s.charID) + gachaEntries[randomEntry] = gachaEntries[len(gachaEntries)-1] + gachaEntries = gachaEntries[:len(gachaEntries)-1] + if rolls == len(rewards) { + break + } + } + bf.WriteUint8(uint8(len(rewards))) + bf.WriteBytes(temp.Data()) + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) + addGachaItem(s, rewards) +} + +func handleMsgMhfResetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfResetBoxGachaInfo) + s.server.db.Exec("DELETE FROM gacha_box WHERE gacha_id = $1 AND character_id = $2", pkt.GachaID, s.charID) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } @@ -488,198 +680,6 @@ func handleMsgMhfGetFpointExchangeList(s *Session, p mhfpacket.MHFPacket) { doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } -func handleMsgMhfPlayStepupGacha(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfPlayStepupGacha) - bf := byteframe.NewByteFrame() - var gachaEntries []GachaEntry - var entry GachaEntry - var rewards []GachaItem - var reward GachaItem - var totalWeight float64 - err, rolls := transactGacha(s, pkt.GachaID, pkt.RollType) - if err != nil { - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) - return - } - s.server.db.Exec("UPDATE users u SET frontier_points=frontier_points+(SELECT frontier_points FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2) WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$3)", pkt.GachaID, pkt.RollType, s.charID) - s.server.db.Exec(`DELETE FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID) - s.server.db.Exec(`INSERT INTO gacha_stepup (gacha_id, step, character_id) VALUES ($1, $2, $3)`, pkt.GachaID, pkt.RollType+1, s.charID) - temp := byteframe.NewByteFrame() - guaranteedItems := getGuaranteedItems(s, pkt.GachaID, pkt.RollType) - for _, item := range guaranteedItems { - temp.WriteUint8(item.ItemType) - temp.WriteUint16(item.ItemID) - temp.WriteUint16(item.Quantity) - temp.WriteUint8(0) - } - entries, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID) - if err != nil { - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) - return - } - for entries.Next() { - entries.StructScan(&entry) - gachaEntries = append(gachaEntries, entry) - totalWeight += entry.Weight - } - for { - result := rand.Float64() * totalWeight - 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() { - items.StructScan(&reward) - rewards = append(rewards, reward) - temp.WriteUint8(reward.ItemType) - temp.WriteUint16(reward.ItemID) - temp.WriteUint16(reward.Quantity) - temp.WriteUint8(entry.Rarity) - } - break - } - } - if rolls == len(rewards) { - break - } - } - bf.WriteUint8(uint8(len(rewards) + len(guaranteedItems))) - bf.WriteUint8(uint8(len(rewards))) - bf.WriteBytes(temp.Data()) - doAckBufSucceed(s, pkt.AckHandle, bf.Data()) - addGachaItem(s, rewards) - addGachaItem(s, guaranteedItems) -} - -func handleMsgMhfReceiveGachaItem(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfReceiveGachaItem) - var data []byte - err := s.server.db.QueryRow("SELECT COALESCE(gacha_items, $2) FROM characters WHERE id = $1", s.charID, []byte{0x00}).Scan(&data) - if err != nil { - data = []byte{0x00} - } - - // I think there are still some edge cases where rewards can be nulled via overflow - if data[0] > 36 || len(data) > 181 { - resp := byteframe.NewByteFrame() - resp.WriteUint8(36) - resp.WriteBytes(data[1:181]) - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) - } else { - doAckBufSucceed(s, pkt.AckHandle, data) - } - - if !pkt.Freeze { - if data[0] > 36 || len(data) > 181 { - update := byteframe.NewByteFrame() - update.WriteUint8(uint8(len(data[181:]) / 5)) - update.WriteBytes(data[181:]) - s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", update.Data(), s.charID) - } else { - s.server.db.Exec("UPDATE characters SET gacha_items = null WHERE id = $1", s.charID) - } - } -} - -func handleMsgMhfGetStepupStatus(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfGetStepupStatus) - // TODO: Reset daily (noon) - var step uint8 - s.server.db.QueryRow(`SELECT step FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID).Scan(&step) - var stepCheck int - s.server.db.QueryRow(`SELECT COUNT(1) FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2`, pkt.GachaID, step).Scan(&stepCheck) - if stepCheck == 0 { - s.server.db.Exec(`DELETE FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID) - step = 0 - } - bf := byteframe.NewByteFrame() - bf.WriteUint8(step) - bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) - doAckBufSucceed(s, pkt.AckHandle, bf.Data()) -} - func 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) - entries, err := s.server.db.Queryx(`SELECT entry_id FROM gacha_box WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID) - if err != nil { - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) - return - } - var entryIDs []uint32 - for entries.Next() { - var entryID uint32 - entries.Scan(&entryID) - entryIDs = append(entryIDs, entryID) - } - bf := byteframe.NewByteFrame() - bf.WriteUint8(uint8(len(entryIDs))) - for i := range entryIDs { - bf.WriteUint32(entryIDs[i]) - bf.WriteBool(true) - } - doAckBufSucceed(s, pkt.AckHandle, bf.Data()) -} - -func handleMsgMhfPlayBoxGacha(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfPlayBoxGacha) - bf := byteframe.NewByteFrame() - var gachaEntries []GachaEntry - var entry GachaEntry - var rewards []GachaItem - var reward GachaItem - err, rolls := transactGacha(s, pkt.GachaID, pkt.RollType) - if err != nil { - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) - return - } - temp := byteframe.NewByteFrame() - entries, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID) - if err != nil { - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) - return - } - for entries.Next() { - entries.StructScan(&entry) - gachaEntries = append(gachaEntries, entry) - } - for { - randomEntry := rand.Intn(len(gachaEntries)) - items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = $1`, gachaEntries[randomEntry].ID) - if err != nil { - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) - return - } - for items.Next() { - items.StructScan(&reward) - rewards = append(rewards, reward) - temp.WriteUint8(reward.ItemType) - temp.WriteUint16(reward.ItemID) - temp.WriteUint16(reward.Quantity) - temp.WriteUint8(0) - } - s.server.db.Exec(`INSERT INTO gacha_box (gacha_id, entry_id, character_id) VALUES ($1, $2, $3)`, pkt.GachaID, gachaEntries[randomEntry].ID, s.charID) - gachaEntries[randomEntry] = gachaEntries[len(gachaEntries)-1] - gachaEntries = gachaEntries[:len(gachaEntries)-1] - if rolls == len(rewards) { - break - } - } - bf.WriteUint8(uint8(len(rewards))) - bf.WriteBytes(temp.Data()) - doAckBufSucceed(s, pkt.AckHandle, bf.Data()) - addGachaItem(s, rewards) -} - -func handleMsgMhfResetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfResetBoxGachaInfo) - s.server.db.Exec("DELETE FROM gacha_box WHERE gacha_id = $1 AND character_id = $2", pkt.GachaID, s.charID) - doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) -} From a7aa0f1c33d9c6f5a1e2ae29143f70617a9a9572 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 18 Feb 2023 18:05:33 +1100 Subject: [PATCH 17/18] separate out gacha function code --- server/channelserver/handlers_shop_gacha.go | 117 ++++++++++---------- 1 file changed, 58 insertions(+), 59 deletions(-) diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index 71ceb66d6..ef1d35b95 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -347,6 +347,35 @@ func addGachaItem(s *Session, items []GachaItem) { s.server.db.Exec(`UPDATE characters SET gacha_items = $1 WHERE id = $2`, newItem.Data(), s.charID) } +func getRandomEntries(entries []GachaEntry, rolls int, isBox bool) ([]GachaEntry, error) { + var chosen []GachaEntry + var totalWeight float64 + for i := range entries { + totalWeight += entries[i].Weight + } + for { + if !isBox { + result := rand.Float64() * totalWeight + for _, entry := range entries { + result -= entry.Weight + if result < 0 { + chosen = append(chosen, entry) + break + } + } + } else { + result := rand.Intn(len(entries)) + chosen = append(chosen, entries[result]) + entries[result] = entries[len(entries)-1] + entries = entries[:len(entries)-1] + } + if rolls == len(chosen) { + break + } + } + return chosen, nil +} + func handleMsgMhfReceiveGachaItem(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfReceiveGachaItem) var data []byte @@ -384,7 +413,6 @@ func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) { var entry GachaEntry var rewards []GachaItem var reward GachaItem - var totalWeight float64 err, rolls := transactGacha(s, pkt.GachaID, pkt.RollType) if err != nil { doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) @@ -408,31 +436,20 @@ func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) { for entries.Next() { entries.StructScan(&entry) gachaEntries = append(gachaEntries, entry) - totalWeight += entry.Weight } - for { - result := rand.Float64() * totalWeight - 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() { - items.StructScan(&reward) - rewards = append(rewards, reward) - temp.WriteUint8(reward.ItemType) - temp.WriteUint16(reward.ItemID) - temp.WriteUint16(reward.Quantity) - temp.WriteUint8(entry.Rarity) - } - break - } + rewardEntries, err := getRandomEntries(gachaEntries, rolls, false) + for i := range rewardEntries { + items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = $1`, rewardEntries[i].ID) + if err != nil { + continue } - if rolls == len(rewards) { - break + for items.Next() { + items.StructScan(&reward) + rewards = append(rewards, reward) + temp.WriteUint8(reward.ItemType) + temp.WriteUint16(reward.ItemID) + temp.WriteUint16(reward.Quantity) + temp.WriteUint8(entry.Rarity) } } bf.WriteUint8(uint8(len(rewards))) @@ -448,7 +465,6 @@ func handleMsgMhfPlayStepupGacha(s *Session, p mhfpacket.MHFPacket) { var entry GachaEntry var rewards []GachaItem var reward GachaItem - var totalWeight float64 err, rolls := transactGacha(s, pkt.GachaID, pkt.RollType) if err != nil { doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) @@ -473,31 +489,20 @@ func handleMsgMhfPlayStepupGacha(s *Session, p mhfpacket.MHFPacket) { for entries.Next() { entries.StructScan(&entry) gachaEntries = append(gachaEntries, entry) - totalWeight += entry.Weight } - for { - result := rand.Float64() * totalWeight - 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() { - items.StructScan(&reward) - rewards = append(rewards, reward) - temp.WriteUint8(reward.ItemType) - temp.WriteUint16(reward.ItemID) - temp.WriteUint16(reward.Quantity) - temp.WriteUint8(entry.Rarity) - } - break - } + rewardEntries, err := getRandomEntries(gachaEntries, rolls, false) + for i := range rewardEntries { + items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = $1`, rewardEntries[i].ID) + if err != nil { + continue } - if rolls == len(rewards) { - break + for items.Next() { + items.StructScan(&reward) + rewards = append(rewards, reward) + temp.WriteUint8(reward.ItemType) + temp.WriteUint16(reward.ItemID) + temp.WriteUint16(reward.Quantity) + temp.WriteUint8(entry.Rarity) } } bf.WriteUint8(uint8(len(rewards) + len(guaranteedItems))) @@ -569,13 +574,13 @@ func handleMsgMhfPlayBoxGacha(s *Session, p mhfpacket.MHFPacket) { entries.StructScan(&entry) gachaEntries = append(gachaEntries, entry) } - for { - randomEntry := rand.Intn(len(gachaEntries)) - items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = $1`, gachaEntries[randomEntry].ID) + rewardEntries, err := getRandomEntries(gachaEntries, rolls, true) + for i := range rewardEntries { + items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = $1`, rewardEntries[i].ID) if err != nil { - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) - return + continue } + s.server.db.Exec(`INSERT INTO gacha_box (gacha_id, entry_id, character_id) VALUES ($1, $2, $3)`, pkt.GachaID, rewardEntries[i].ID, s.charID) for items.Next() { items.StructScan(&reward) rewards = append(rewards, reward) @@ -584,12 +589,6 @@ func handleMsgMhfPlayBoxGacha(s *Session, p mhfpacket.MHFPacket) { temp.WriteUint16(reward.Quantity) temp.WriteUint8(0) } - s.server.db.Exec(`INSERT INTO gacha_box (gacha_id, entry_id, character_id) VALUES ($1, $2, $3)`, pkt.GachaID, gachaEntries[randomEntry].ID, s.charID) - gachaEntries[randomEntry] = gachaEntries[len(gachaEntries)-1] - gachaEntries = gachaEntries[:len(gachaEntries)-1] - if rolls == len(rewards) { - break - } } bf.WriteUint8(uint8(len(rewards))) bf.WriteBytes(temp.Data()) From a47303bec2a74f0fc256d52be308c7b64475bfd6 Mon Sep 17 00:00:00 2001 From: wish Date: Sat, 18 Feb 2023 18:05:51 +1100 Subject: [PATCH 18/18] remove unused code --- server/channelserver/handlers_shop_gacha.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index ef1d35b95..29c3f4c64 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -419,15 +419,6 @@ func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) { return } temp := byteframe.NewByteFrame() - /* Optional extended functionality - guaranteedItems := getGuaranteedItems(s, pkt.GachaID, pkt.RollType) - for _, item := range guaranteedItems { - temp.WriteUint8(item.ItemType) - temp.WriteUint16(reward.ItemID) - temp.WriteUint16(reward.Quantity) - temp.WriteUint8(0) // Lowest rarity - } - */ entries, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID) if err != nil { doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))