From 9103debe995d5abaabd930460bd947d1921e9242 Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 21 Nov 2022 09:27:29 +1100 Subject: [PATCH] shop enumeration first pass --- .../msg_mhf_get_gacha_play_history.go | 14 +- patch-schema/gacha-db.sql | 18 + patch-schema/shop-db.sql | 39 ++ server/channelserver/handlers_shop_gacha.go | 369 +++++++++--------- 4 files changed, 240 insertions(+), 200 deletions(-) create mode 100644 patch-schema/gacha-db.sql create mode 100644 patch-schema/shop-db.sql diff --git a/network/mhfpacket/msg_mhf_get_gacha_play_history.go b/network/mhfpacket/msg_mhf_get_gacha_play_history.go index 2825f4c74..d8ccee4fb 100644 --- a/network/mhfpacket/msg_mhf_get_gacha_play_history.go +++ b/network/mhfpacket/msg_mhf_get_gacha_play_history.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" ) // MsgMhfGetGachaPlayHistory represents the MSG_MHF_GET_GACHA_PLAY_HISTORY -type MsgMhfGetGachaPlayHistory struct{ +type MsgMhfGetGachaPlayHistory struct { AckHandle uint32 - GachaHash uint32 + GachaID uint32 } // Opcode returns the ID associated with this packet type. @@ -22,7 +22,7 @@ func (m *MsgMhfGetGachaPlayHistory) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfGetGachaPlayHistory) 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.sql b/patch-schema/gacha-db.sql new file mode 100644 index 000000000..4492f29ee --- /dev/null +++ b/patch-schema/gacha-db.sql @@ -0,0 +1,18 @@ +BEGIN; + +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, + icon integer, + type integer, + hide boolean +); + +END; \ No newline at end of file diff --git a/patch-schema/shop-db.sql b/patch-schema/shop-db.sql new file mode 100644 index 000000000..b95b26ca1 --- /dev/null +++ b/patch-schema/shop-db.sql @@ -0,0 +1,39 @@ +BEGIN; + +ALTER TABLE IF EXISTS public.normal_shop_items + RENAME COLUMN itemhash TO id; + +ALTER TABLE IF EXISTS public.normal_shop_items + ALTER COLUMN points TYPE integer; + +ALTER TABLE IF EXISTS public.normal_shop_items + RENAME COLUMN points TO cost; + +ALTER TABLE IF EXISTS public.normal_shop_items + RENAME COLUMN tradequantity TO quantity; + +ALTER TABLE IF EXISTS public.normal_shop_items + RENAME COLUMN rankreqlow TO min_hr; + +ALTER TABLE IF EXISTS public.normal_shop_items + RENAME COLUMN rankreqhigh TO min_sr; + +ALTER TABLE IF EXISTS public.normal_shop_items + RENAME COLUMN rankreqg TO min_gr; + +ALTER TABLE IF EXISTS public.normal_shop_items + RENAME COLUMN storelevelreq TO req_store_level; + +ALTER TABLE IF EXISTS public.normal_shop_items + RENAME COLUMN maximumquantity TO max_quantity; + +ALTER TABLE IF EXISTS public.normal_shop_items + DROP COLUMN boughtquantity; + +ALTER TABLE IF EXISTS public.normal_shop_items + RENAME COLUMN roadfloorsrequired TO road_floors; + +ALTER TABLE IF EXISTS public.normal_shop_items + RENAME COLUMN weeklyfataliskills TO road_fatalis; + +END; \ No newline at end of file diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index 7ee712c1a..8ba914a18 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -2,6 +2,7 @@ package channelserver import ( "encoding/hex" + ps "erupe-ce/common/pascalstring" "time" "erupe-ce/common/byteframe" @@ -11,43 +12,91 @@ import ( "go.uber.org/zap" ) +type ShopItem struct { + ID uint32 `db:"id"` + ItemID uint16 `db:"itemid"` + Cost uint32 `db:"cost"` + Quantity uint16 `db:"quantity"` + MinHR uint16 `db:"min_hr"` + MinSR uint16 `db:"min_sr"` + MinGR uint16 `db:"min_gr"` + ReqStoreLevel uint16 `db:"req_store_level"` + MaxQuantity uint16 `db:"max_quantity"` + CharQuantity uint16 `db:"char_quantity"` + RoadFloors uint16 `db:"road_floors"` + RoadFatalis uint16 `db:"road_fatalis"` +} + +type Gacha struct { + ID uint32 + MinGR uint32 + MinHR uint32 + Name string + Link1 string + Link2 string + Link3 string + Icon uint16 + Type uint16 + Hide bool +} + func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfEnumerateShop) - // SHOP TYPES: - // 01 = Running Gachas, 02 = actual gacha, 04 = N Points, 05 = GCP, 07 = Item to GCP, 08 = Diva Defense, 10 = Hunter's Road - - // GACHA FORMAT: - // int32: gacha id - - // STORE FORMAT: - // Int16: total item count - // Int16: total item count - - // ITEM FORMAT: - // int32: Unique item hash for tracking purchases - // int16: padding? - // int16: Item ID - // int16: padding? - // int16: GCP returns - // int16: Number traded at once - // int16: HR or SR Requirement - // int16: Whichever of the above it isn't - // int16: GR Requirement - // int16: Store level requirement - // int16: Maximum quantity purchasable - // int16: Unk - // int16: Road floors cleared requirement - // int16: Road White Fatalis weekly kills - if pkt.ShopType == 2 { + // Generic Shop IDs + // 0: basic item + // 1: gatherables + // 2: hr1-4 materials + // 3: hr5-7 materials + // 4: decos + // 5: other item + // 6: g mats + // 7: limited item + // 8: special item + switch pkt.ShopType { + case 1: // Running gachas + 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") + if err != nil { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) + return + } + resp := byteframe.NewByteFrame() + resp.WriteUint32(0) + var gacha Gacha + for shopEntries.Next() { + err = shopEntries.StructScan(&gacha) + if err != nil { + continue + } + resp.WriteUint32(gacha.ID) + resp.WriteBytes(make([]byte, 16)) // Rank restriction + resp.WriteUint32(gacha.MinGR) + resp.WriteUint32(gacha.MinHR) + resp.WriteUint32(0) // only 0 in known packet + ps.Uint8(resp, gacha.Name, true) + ps.Uint8(resp, gacha.Link1, false) + ps.Uint8(resp, gacha.Link2, false) + resp.WriteBool(gacha.Hide) + ps.Uint8(resp, gacha.Link3, false) + resp.WriteUint16(gacha.Icon) + resp.WriteUint16(gacha.Type) + count++ + } + resp.Seek(0, 0) + resp.WriteUint16(count) + 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) if err != nil { - panic(err) + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) + return } - var entryType, currType, rarityIcon, rollsCount, itemCount, dailyLimit byte + var entryType, currType, rarityIcon, rollsCount, itemCount, dailyLimit uint8 var currQuant, currNumber, percentage uint16 var itemhash uint32 var itemType, itemId, quantity pq.Int64Array - var entryCount int + var count uint16 resp := byteframe.NewByteFrame() resp.WriteUint32(pkt.ShopID) resp.WriteUint16(0) // total defs @@ -74,164 +123,101 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) { resp.WriteUint16(uint16(itemId[i])) // unk, always 0 in existing packets resp.WriteUint16(uint16(quantity[i])) // unk, always 0 in existing packets } - entryCount++ - } - if entryCount == 0 { - doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) - return + count++ } resp.Seek(4, 0) - resp.WriteUint16(uint16(entryCount)) + resp.WriteUint16(count) doAckBufSucceed(s, pkt.AckHandle, resp.Data()) - } else if pkt.ShopType == 1 { - gachaCount := 0 - shopEntries, err := s.server.db.Query("SELECT hash, reqGR, reqHR, gachaName, gachaLink0, gachaLink1, COALESCE(gachaLink2, ''), extraIcon, gachaType, hideFlag FROM gacha_shop") - if err != nil { - panic(err) - } - resp := byteframe.NewByteFrame() - resp.WriteUint32(0) - var gachaName, gachaLink0, gachaLink1, gachaLink2 string - var hash, reqGR, reqHR, extraIcon, gachaType int - var hideFlag bool - for shopEntries.Next() { - err = shopEntries.Scan(&hash, &reqGR, &reqHR, &gachaName, &gachaLink0, &gachaLink1, &gachaLink2, &extraIcon, &gachaType, &hideFlag) - if err != nil { - panic(err) - } - resp.WriteUint32(uint32(hash)) - resp.WriteUint32(0) // only 0 in known packets - resp.WriteUint32(0) // all of these seem to trigger the 'rank restriction' - resp.WriteUint32(0) // message so they are presumably placeholders for a - resp.WriteUint32(0) // Z Rank or similar that never turned up? - resp.WriteUint32(uint32(reqGR)) - resp.WriteUint32(uint32(reqHR)) - resp.WriteUint32(0) // only 0 in known packet - stringBytes := append([]byte(gachaName), 0x00) - resp.WriteUint8(byte(len(stringBytes))) - resp.WriteBytes(stringBytes) - stringBytes = append([]byte(gachaLink0), 0x00) - resp.WriteUint8(byte(len(stringBytes))) - resp.WriteBytes(stringBytes) - stringBytes = append([]byte(gachaLink1), 0x00) - resp.WriteUint8(byte(len(stringBytes))) - resp.WriteBytes(stringBytes) - stringBytes = append([]byte(gachaLink2), 0x00) - resp.WriteBool(hideFlag) - resp.WriteUint8(uint8(len(stringBytes))) - resp.WriteBytes(stringBytes) - resp.WriteUint16(uint16(extraIcon)) - resp.WriteUint16(uint16(gachaType)) - gachaCount++ - } - resp.Seek(0, 0) - resp.WriteUint16(uint16(gachaCount)) - resp.WriteUint16(uint16(gachaCount)) - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) - - } else if pkt.ShopType == 7 { - // GCP conversion store - if pkt.ShopID == 0 { - // Items to GCP exchange. Gou Tickets, Shiten Tickets, GP Tickets - data, _ := hex.DecodeString("000300033a9186fb000033860000000a000100000000000000000000000000000000097fdb1c0000067e0000000a0001000000000000000000000000000000001374db29000027c300000064000100000000000000000000000000000000") - doAckBufSucceed(s, pkt.AckHandle, data) - } else { - doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) - } - } else if pkt.ShopType == 8 { - // Dive Defense sections - // 00 = normal level limited exchange store, 05 = GCP skill store, 07 = limited quantity exchange - if pkt.ShopID == 5 { - // diva defense skill level limited store + case 4: // N Points, 0-6 + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) + case 5: // GCP->Item, 0-6 + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) + case 6: // Gacha coin->Item + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) + case 7: // Item->GCP + data, _ := hex.DecodeString("000300033a9186fb000033860000000a000100000000000000000000000000000000097fdb1c0000067e0000000a0001000000000000000000000000000000001374db29000027c300000064000100000000000000000000000000000000") + doAckBufSucceed(s, pkt.AckHandle, data) + case 8: // Diva + switch pkt.ShopID { + case 0: // Normal exchange + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) + case 5: // GCP skills data, _ := hex.DecodeString("001f001f2c9365c1000000010000001e000a0000000000000000000a0000000000001979f1c2000000020000003c000a0000000000000000000a0000000000003e5197df000000030000003c000a0000000000000000000a000000000000219337c0000000040000001e000a0000000000000000000a00000000000009b24c9d000000140000001e000a0000000000000000000a0000000000001f1d496e000000150000001e000a0000000000000000000a0000000000003b918fcb000000160000003c000a0000000000000000000a0000000000000b7fd81c000000170000003c000a0000000000000000000a0000000000001374f239000000180000003c000a0000000000000000000a00000000000026950cba0000001c0000003c000a0000000000000000000a0000000000003797eae70000001d0000003c000a012b000000000000000a00000000000015758ad8000000050000003c00000000000000000000000a0000000000003c7035050000000600000050000a0000000000000001000a00000000000024f3b5560000000700000050000a0000000000000001000a00000000000000b600330000000800000050000a0000000000000001000a0000000000002efdce840000001900000050000a0000000000000001000a0000000000002d9365f10000001a00000050000a0000000000000001000a0000000000001979f3420000001f00000050000a012b000000000001000a0000000000003f5397cf0000002000000050000a012b000000000001000a000000000000319337c00000002100000050000a012b000000000001000a00000000000008b04cbd0000000900000064000a0000000000000002000a0000000000000b1d4b6e0000000a00000064000a0000000000000002000a0000000000003b918feb0000000b00000064000a0000000000000002000a0000000000001b7fd81c0000000c00000064000a0000000000000002000a0000000000001276f2290000000d00000064000a0000000000000002000a00000000000022950cba0000000e000000c8000a0000000000000002000a0000000000003697ead70000000f000001f4000a0000000000000003000a00000000000005758a5800000010000003e8000a0000000000000003000a0000000000003c7035250000001b000001f4000a0000000000010003000a00000000000034f3b5d60000001e00000064000a012b000000000003000a00000000000000b600030000002200000064000a0000000000010003000a000000000000") doAckBufSucceed(s, pkt.AckHandle, data) - } else { - doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + case 7: // Note exchange + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) } - } else { - shopEntries, err := s.server.db.Query("SELECT itemhash,itemID,Points,TradeQuantity,rankReqLow,rankReqHigh,rankReqG,storeLevelReq,maximumQuantity,boughtQuantity,roadFloorsRequired,weeklyFatalisKills FROM normal_shop_items WHERE shoptype=$1 AND shopid=$2", pkt.ShopType, pkt.ShopID) + case 10: // Item shop, 0-8 + shopEntries, err := s.server.db.Queryx(`SELECT id, itemid, cost, quantity, min_hr, min_sr, min_gr, req_store_level, max_quantity, + COALESCE((SELECT usedquantity FROM shop_item_state WHERE itemhash=nsi.id AND char_id=$3), 0) as char_quantity, + road_floors, road_fatalis FROM normal_shop_items nsi WHERE shoptype=$1 AND shopid=$2 + `, pkt.ShopType, pkt.ShopID, s.charID) if err != nil { - panic(err) - } - var ItemHash, entryCount int - var itemID, Points, TradeQuantity, rankReqLow, rankReqHigh, rankReqG, storeLevelReq, maximumQuantity, boughtQuantity, roadFloorsRequired, weeklyFatalisKills, charQuantity uint16 - resp := byteframe.NewByteFrame() - resp.WriteUint32(0) // total defs - for shopEntries.Next() { - err = shopEntries.Scan(&ItemHash, &itemID, &Points, &TradeQuantity, &rankReqLow, &rankReqHigh, &rankReqG, &storeLevelReq, &maximumQuantity, &boughtQuantity, &roadFloorsRequired, &weeklyFatalisKills) - if err != nil { - panic(err) - } - resp.WriteUint32(uint32(ItemHash)) - resp.WriteUint16(0) // unk, always 0 in existing packets - resp.WriteUint16(itemID) - resp.WriteUint16(0) // unk, always 0 in existing packets - resp.WriteUint16(Points) // it's either item ID or quantity for gacha coins - resp.WriteUint16(TradeQuantity) // only for item ID - resp.WriteUint16(rankReqLow) - resp.WriteUint16(rankReqHigh) - resp.WriteUint16(rankReqG) - resp.WriteUint16(storeLevelReq) - resp.WriteUint16(maximumQuantity) - if maximumQuantity > 0 { - err = s.server.db.QueryRow("SELECT COALESCE(usedquantity,0) FROM shop_item_state WHERE itemhash=$1 AND char_id=$2", ItemHash, s.charID).Scan(&charQuantity) - if err != nil { - resp.WriteUint16(0) - } else { - resp.WriteUint16(charQuantity) - } - } else { - resp.WriteUint16(boughtQuantity) - } - resp.WriteUint16(roadFloorsRequired) - resp.WriteUint16(weeklyFatalisKills) - entryCount++ - } - if entryCount == 0 { - doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) return } + var count uint16 + resp := byteframe.NewByteFrame() + resp.WriteBytes(make([]byte, 4)) + var shopItem ShopItem + for shopEntries.Next() { + err = shopEntries.StructScan(&shopItem) + if err != nil { + continue + } + resp.WriteUint32(shopItem.ID) + resp.WriteUint16(0) + resp.WriteUint16(shopItem.ItemID) + resp.WriteUint32(shopItem.Cost) + resp.WriteUint16(shopItem.Quantity) + resp.WriteUint16(shopItem.MinHR) + resp.WriteUint16(shopItem.MinSR) + resp.WriteUint16(shopItem.MinGR) + resp.WriteUint16(shopItem.ReqStoreLevel) + resp.WriteUint16(shopItem.MaxQuantity) + resp.WriteUint16(shopItem.CharQuantity) + resp.WriteUint16(shopItem.RoadFloors) + resp.WriteUint16(shopItem.RoadFatalis) + count++ + } resp.Seek(0, 0) - resp.WriteUint16(uint16(entryCount)) - resp.WriteUint16(uint16(entryCount)) + resp.WriteUint16(count) + resp.WriteUint16(count) doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } } func handleMsgMhfAcquireExchangeShop(s *Session, p mhfpacket.MHFPacket) { - // writing out to an editable shop enumeration pkt := p.(*mhfpacket.MsgMhfAcquireExchangeShop) - if pkt.DataSize == 10 { - bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload) - _ = bf.ReadUint16() // unk, always 1 in examples + bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload) + exchanges := int(bf.ReadUint16()) + for i := 0; i < exchanges; i++ { itemHash := bf.ReadUint32() buyCount := bf.ReadUint32() - _, err := s.server.db.Exec(`INSERT INTO shop_item_state (char_id, itemhash, usedquantity) - VALUES ($1,$2,$3) ON CONFLICT (char_id, itemhash) - DO UPDATE SET usedquantity = shop_item_state.usedquantity + $3 - WHERE EXCLUDED.char_id=$1 AND EXCLUDED.itemhash=$2`, s.charID, itemHash, buyCount) - if err != nil { - s.logger.Fatal("Failed to update shop_item_state in db", zap.Error(err)) - } + s.server.db.Exec(`INSERT INTO shop_item_state (char_id, itemhash, usedquantity) + VALUES ($1,$2,$3) ON CONFLICT (char_id, itemhash) + DO UPDATE SET usedquantity = shop_item_state.usedquantity + $3 + WHERE EXCLUDED.char_id=$1 AND EXCLUDED.itemhash=$2 + `, s.charID, itemHash, buyCount) } doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) } func handleMsgMhfGetGachaPlayHistory(s *Session, p mhfpacket.MHFPacket) { - // returns number of times the gacha was played, will need persistent db stuff pkt := p.(*mhfpacket.MsgMhfGetGachaPlayHistory) - doAckBufSucceed(s, pkt.AckHandle, []byte{0x0A}) + bf := byteframe.NewByteFrame() + bf.WriteUint8(0) + 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_prem, 0), COALESCE(gacha_trial,0) FROM characters WHERE id=$1", s.charID).Scan(&fp, &gp, >) resp := byteframe.NewByteFrame() resp.WriteUint32(gp) // Real Gacha Points? resp.WriteUint32(gt) // Trial Gacha Point? resp.WriteUint32(fp) // Frontier Points? - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } @@ -362,54 +348,51 @@ func handleMsgMhfExchangeItem2Fpoint(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfGetFpointExchangeList(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetFpointExchangeList) - //absurd, probably lists every single item to trade to FP? - - var buyables int - var sellables int - - buyRows, err := s.server.db.Query("SELECT hash,itemType,itemID,quant,itemValue FROM fpoint_items WHERE tradeType=0") - if err != nil { - panic(err) - } resp := byteframe.NewByteFrame() resp.WriteUint32(0) - var hash, itemType, itemID, quant, itemValue int - for buyRows.Next() { - err = buyRows.Scan(&hash, &itemType, &itemID, &quant, &itemValue) - if err != nil { - panic("Error in fpoint_items") + var buyables, sellables uint16 + var hash uint32 + var itemType uint8 + var itemID, quant, itemValue uint16 + + buyRows, err := s.server.db.Query("SELECT hash,itemType,itemID,quant,itemValue FROM fpoint_items WHERE tradeType=0") + if err == nil { + for buyRows.Next() { + err = buyRows.Scan(&hash, &itemType, &itemID, &quant, &itemValue) + if err != nil { + continue + } + resp.WriteUint32(hash) + resp.WriteUint32(0) // this and following only 0 in known packets + resp.WriteUint16(0) + resp.WriteUint8(itemType) + resp.WriteUint16(itemID) + resp.WriteUint16(quant) + resp.WriteUint16(itemValue) + buyables++ } - resp.WriteUint32(uint32(hash)) - resp.WriteUint32(0) // this and following only 0 in known packets - resp.WriteUint16(0) - resp.WriteUint8(byte(itemType)) - resp.WriteUint16(uint16(itemID)) - resp.WriteUint16(uint16(quant)) - resp.WriteUint16(uint16(itemValue)) - buyables++ } sellRows, err := s.server.db.Query("SELECT hash,itemType,itemID,quant,itemValue FROM fpoint_items WHERE tradeType=1") - if err != nil { - panic(err) - } - for sellRows.Next() { - err = sellRows.Scan(&hash, &itemType, &itemID, &quant, &itemValue) - if err != nil { - panic("Error in fpoint_items") + if err == nil { + for sellRows.Next() { + err = sellRows.Scan(&hash, &itemType, &itemID, &quant, &itemValue) + if err != nil { + continue + } + resp.WriteUint32(hash) + resp.WriteUint32(0) // this and following only 0 in known packets + resp.WriteUint16(0) + resp.WriteUint8(itemType) + resp.WriteUint16(itemID) + resp.WriteUint16(quant) + resp.WriteUint16(itemValue) + sellables++ } - resp.WriteUint32(uint32(hash)) - resp.WriteUint32(0) // this and following only 0 in known packets - resp.WriteUint16(0) - resp.WriteUint8(byte(itemType)) - resp.WriteUint16(uint16(itemID)) - resp.WriteUint16(uint16(quant)) - resp.WriteUint16(uint16(itemValue)) - sellables++ } resp.Seek(0, 0) - resp.WriteUint16(uint16(sellables)) - resp.WriteUint16(uint16(buyables)) + resp.WriteUint16(sellables) + resp.WriteUint16(buyables) doAckBufSucceed(s, pkt.AckHandle, resp.Data()) }