diff --git a/bundled-schema/DivaShops.sql b/bundled-schema/DivaShops.sql new file mode 100644 index 000000000..f49b29d6f --- /dev/null +++ b/bundled-schema/DivaShops.sql @@ -0,0 +1,45 @@ +BEGIN; + +INSERT INTO public.shop_items + (shop_type, shop_id, item_id, cost, quantity, min_hr, min_sr, min_gr, store_level, max_quantity, road_floors, road_fatalis) +VALUES + (8,5,1,30,10,0,0,0,0,10,0,0), + (8,5,2,60,10,0,0,0,0,10,0,0), + (8,5,3,60,10,0,0,0,0,10,0,0), + (8,5,4,30,10,0,0,0,0,10,0,0), + (8,5,5,60,10,0,0,0,0,10,0,0), + (8,5,6,80,10,0,0,0,1,10,0,0), + (8,5,7,80,10,0,0,0,1,10,0,0), + (8,5,8,80,10,0,0,0,1,10,0,0), + (8,5,9,100,10,0,0,0,2,10,0,0), + (8,5,10,100,10,0,0,0,2,10,0,0), + (8,5,11,100,10,0,0,0,2,10,0,0), + (8,5,12,100,10,0,0,0,2,10,0,0), + (8,5,13,100,10,0,0,0,2,10,0,0), + (8,5,14,200,10,0,0,0,2,10,0,0), + (8,5,15,500,10,0,0,0,3,10,0,0), + (8,5,16,1000,10,0,0,0,3,10,0,0), + (8,5,20,30,10,0,0,0,0,10,0,0), + (8,5,21,30,10,0,0,0,0,10,0,0), + (8,5,22,60,10,0,0,0,0,10,0,0), + (8,5,23,60,10,0,0,0,0,10,0,0), + (8,5,24,60,10,0,0,0,0,10,0,0), + (8,5,25,80,10,0,0,0,1,10,0,0), + (8,5,26,80,10,0,0,0,1,10,0,0), + (8,5,27,500,10,0,0,1,3,10,0,0), + (8,5,28,60,10,0,0,0,0,10,0,0), + (8,5,29,60,10,299,0,0,0,10,0,0), + (8,5,30,100,10,0,0,1,3,10,0,0), + (8,5,31,80,10,299,0,0,1,10,0,0), + (8,5,32,80,10,299,0,0,1,10,0,0), + (8,5,33,80,10,299,0,0,1,10,0,0), + (8,7,2209,400,1,299,0,0,2,5,0,0), + (8,7,2208,400,1,299,0,0,2,5,0,0), + (8,7,5113,400,1,299,0,0,2,5,0,0), + (8,7,3571,400,1,299,0,0,2,5,0,0), + (8,7,3572,400,1,299,0,0,2,5,0,0), + (8,7,3738,400,1,299,0,0,2,5,0,0), + (8,7,3737,400,1,299,0,0,2,5,0,0), + (8,7,4399,400,1,299,0,0,2,5,0,0); + +END; \ No newline at end of file diff --git a/bundled-schema/OtherShops.sql b/bundled-schema/OtherShops.sql new file mode 100644 index 000000000..ea2487451 --- /dev/null +++ b/bundled-schema/OtherShops.sql @@ -0,0 +1,12 @@ +BEGIN; + +INSERT INTO public.shop_items + (shop_type, shop_id, item_id, cost, quantity, min_hr, min_sr, min_gr, store_level, max_quantity, road_floors, road_fatalis) +VALUES + (5,5,16516,100,1,0,0,1,0,0,0,0), + (5,5,16517,100,1,0,0,1,0,0,0,0), + (7,0,13190,10,1,0,0,0,0,0,0,0), + (7,0,1662,10,1,0,0,0,0,0,0,0), + (7,0,10179,100,1,0,0,0,0,0,0,0); + +END; \ No newline at end of file diff --git a/patch-schema/shop-db.sql b/patch-schema/shop-db.sql index b95b26ca1..3911b13bc 100644 --- a/patch-schema/shop-db.sql +++ b/patch-schema/shop-db.sql @@ -1,39 +1,29 @@ BEGIN; -ALTER TABLE IF EXISTS public.normal_shop_items - RENAME COLUMN itemhash TO id; +DROP TABLE IF EXISTS public.normal_shop_items; -ALTER TABLE IF EXISTS public.normal_shop_items - ALTER COLUMN points TYPE integer; +CREATE TABLE IF NOT EXISTS public.shop_items ( + id SERIAL PRIMARY KEY, + shop_type INTEGER, + shop_id INTEGER, + item_id INTEGER, + cost INTEGER, + quantity INTEGER, + min_hr INTEGER, + min_sr INTEGER, + min_gr INTEGER, + store_level INTEGER, + max_quantity INTEGER, + road_floors INTEGER, + road_fatalis INTEGER +); -ALTER TABLE IF EXISTS public.normal_shop_items - RENAME COLUMN points TO cost; +DROP TABLE IF EXISTS public.shop_item_state; -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; +CREATE TABLE IF NOT EXISTS public.shop_items_bought ( + character_id INTEGER, + shop_item_id INTEGER, + bought 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 9ae45b2f1..91f92205e 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -8,18 +8,18 @@ import ( ) 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"` + ID uint32 `db:"id"` + ItemID uint16 `db:"item_id"` + Cost uint32 `db:"cost"` + Quantity uint16 `db:"quantity"` + MinHR uint16 `db:"min_hr"` + MinSR uint16 `db:"min_sr"` + MinGR uint16 `db:"min_gr"` + StoreLevel uint16 `db:"store_level"` + MaxQuantity uint16 `db:"max_quantity"` + UsedQuantity uint16 `db:"used_quantity"` + RoadFloors uint16 `db:"road_floors"` + RoadFatalis uint16 `db:"road_fatalis"` } type Gacha struct { @@ -67,14 +67,33 @@ func writeShopItems(bf *byteframe.ByteFrame, items []ShopItem) { bf.WriteUint16(item.MinHR) bf.WriteUint16(item.MinSR) bf.WriteUint16(item.MinGR) - bf.WriteUint16(item.ReqStoreLevel) + bf.WriteUint16(item.StoreLevel) bf.WriteUint16(item.MaxQuantity) - bf.WriteUint16(item.CharQuantity) + bf.WriteUint16(item.UsedQuantity) bf.WriteUint16(item.RoadFloors) bf.WriteUint16(item.RoadFatalis) } } +func getShopItems(s *Session, shopType uint8, shopID uint32) []ShopItem { + var items []ShopItem + var temp ShopItem + rows, err := s.server.db.Queryx(`SELECT id, item_id, cost, quantity, min_hr, min_sr, min_gr, store_level, max_quantity, + COALESCE((SELECT bought FROM shop_items_bought WHERE shop_item_id=si.id AND character_id=$3), 0) as used_quantity, + road_floors, road_fatalis FROM shop_items si WHERE shop_type=$1 AND shop_id=$2 + `, shopType, shopID, s.charID) + if err == nil { + for rows.Next() { + err = rows.StructScan(&temp) + if err != nil { + continue + } + items = append(items, temp) + } + } + return items +} + func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfEnumerateShop) // Generic Shop IDs @@ -184,109 +203,41 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) { bf.Seek(4, 0) bf.WriteUint16(entryCount) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) + case 3: // Hunting Festival Exchange + bf := byteframe.NewByteFrame() + items := getShopItems(s, pkt.ShopType, pkt.ShopID) + writeShopItems(bf, items) + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) case 4: // N Points, 0-6 - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) + bf := byteframe.NewByteFrame() + items := getShopItems(s, pkt.ShopType, pkt.ShopID) + writeShopItems(bf, items) + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) case 5: // GCP->Item, 0-6 - switch pkt.ShopID { - case 5: - bf := byteframe.NewByteFrame() - items := []ShopItem{ - {ItemID: 16516, Cost: 100, Quantity: 1, MinGR: 1}, - {ItemID: 16517, Cost: 100, Quantity: 1, MinGR: 1}, - } - writeShopItems(bf, items) - doAckBufSucceed(s, pkt.AckHandle, bf.Data()) - default: - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) - } + bf := byteframe.NewByteFrame() + items := getShopItems(s, pkt.ShopType, pkt.ShopID) + writeShopItems(bf, items) + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) case 6: // Gacha coin->Item doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) case 7: // Item->GCP bf := byteframe.NewByteFrame() - items := []ShopItem{ - {ItemID: 13190, Cost: 10, Quantity: 1}, - {ItemID: 1662, Cost: 10, Quantity: 1}, - {ItemID: 10179, Cost: 100, Quantity: 1}, - } + items := getShopItems(s, pkt.ShopType, pkt.ShopID) writeShopItems(bf, items) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) case 8: // Diva - switch pkt.ShopID { - case 0: // Normal exchange - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) - case 5: // GCP skills - bf := byteframe.NewByteFrame() - items := []ShopItem{ - {ItemID: 1, Cost: 30, Quantity: 10, MaxQuantity: 10}, - {ItemID: 2, Cost: 60, Quantity: 10, MaxQuantity: 10}, - {ItemID: 3, Cost: 60, Quantity: 10, MaxQuantity: 10}, - {ItemID: 4, Cost: 30, Quantity: 10, MaxQuantity: 10}, - {ItemID: 20, Cost: 30, Quantity: 10, MaxQuantity: 10}, - {ItemID: 21, Cost: 30, Quantity: 10, MaxQuantity: 10}, - {ItemID: 22, Cost: 60, Quantity: 10, MaxQuantity: 10}, - {ItemID: 23, Cost: 60, Quantity: 10, MaxQuantity: 10}, - {ItemID: 24, Cost: 60, Quantity: 10, MaxQuantity: 10}, - {ItemID: 28, Cost: 60, Quantity: 10, MaxQuantity: 10}, - {ItemID: 29, Cost: 60, Quantity: 10, MinHR: 299, MaxQuantity: 10}, - {ItemID: 5, Cost: 60, Quantity: 10, MaxQuantity: 10}, - {ItemID: 6, Cost: 80, Quantity: 10, ReqStoreLevel: 1, MaxQuantity: 10}, - {ItemID: 7, Cost: 80, Quantity: 10, ReqStoreLevel: 1, MaxQuantity: 10}, - {ItemID: 8, Cost: 80, Quantity: 10, ReqStoreLevel: 1, MaxQuantity: 10}, - {ItemID: 25, Cost: 80, Quantity: 10, ReqStoreLevel: 1, MaxQuantity: 10}, - {ItemID: 26, Cost: 80, Quantity: 10, ReqStoreLevel: 1, MaxQuantity: 10}, - {ItemID: 31, Cost: 80, Quantity: 10, MinHR: 299, ReqStoreLevel: 1, MaxQuantity: 10}, - {ItemID: 32, Cost: 80, Quantity: 10, MinHR: 299, ReqStoreLevel: 1, MaxQuantity: 10}, - {ItemID: 33, Cost: 80, Quantity: 10, MinHR: 299, ReqStoreLevel: 1, MaxQuantity: 10}, - {ItemID: 9, Cost: 100, Quantity: 10, ReqStoreLevel: 2, MaxQuantity: 10}, - {ItemID: 10, Cost: 100, Quantity: 10, ReqStoreLevel: 2, MaxQuantity: 10}, - {ItemID: 11, Cost: 100, Quantity: 10, ReqStoreLevel: 2, MaxQuantity: 10}, - {ItemID: 12, Cost: 100, Quantity: 10, ReqStoreLevel: 2, MaxQuantity: 10}, - {ItemID: 13, Cost: 100, Quantity: 10, ReqStoreLevel: 2, MaxQuantity: 10}, - {ItemID: 14, Cost: 200, Quantity: 10, ReqStoreLevel: 2, MaxQuantity: 10}, - {ItemID: 15, Cost: 500, Quantity: 10, ReqStoreLevel: 3, MaxQuantity: 10}, - {ItemID: 16, Cost: 1000, Quantity: 10, ReqStoreLevel: 3, MaxQuantity: 10}, - {ItemID: 27, Cost: 500, Quantity: 10, MinGR: 1, ReqStoreLevel: 3, MaxQuantity: 10}, - {ItemID: 30, Cost: 100, Quantity: 10, MinHR: 299, ReqStoreLevel: 3, MaxQuantity: 10}, - {ItemID: 30, Cost: 100, Quantity: 10, MinGR: 1, ReqStoreLevel: 3, MaxQuantity: 10}, - } - writeShopItems(bf, items) - doAckBufSucceed(s, pkt.AckHandle, bf.Data()) - case 7: // Limited exchange - bf := byteframe.NewByteFrame() - items := []ShopItem{ - {ItemID: 2209, Cost: 400, Quantity: 1, MinHR: 299, ReqStoreLevel: 2, MaxQuantity: 5}, - {ItemID: 2208, Cost: 400, Quantity: 1, MinHR: 299, ReqStoreLevel: 2, MaxQuantity: 5}, - {ItemID: 5113, Cost: 400, Quantity: 1, MinHR: 299, ReqStoreLevel: 2, MaxQuantity: 5}, - {ItemID: 3571, Cost: 400, Quantity: 1, MinHR: 299, ReqStoreLevel: 2, MaxQuantity: 5}, - {ItemID: 3572, Cost: 400, Quantity: 1, MinHR: 299, ReqStoreLevel: 2, MaxQuantity: 5}, - {ItemID: 3738, Cost: 400, Quantity: 1, MinHR: 299, ReqStoreLevel: 2, MaxQuantity: 5}, - {ItemID: 3737, Cost: 400, Quantity: 1, MinHR: 299, ReqStoreLevel: 2, MaxQuantity: 5}, - {ItemID: 4399, Cost: 400, Quantity: 1, MinHR: 299, ReqStoreLevel: 2, MaxQuantity: 5}, - } - writeShopItems(bf, items) - doAckBufSucceed(s, pkt.AckHandle, bf.Data()) - } - case 9: // Diva song shop - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) - 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 { - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) - return - } bf := byteframe.NewByteFrame() - var items []ShopItem - for shopEntries.Next() { - item := ShopItem{} - err = shopEntries.StructScan(&item) - if err != nil { - continue - } - items = append(items, item) - } + items := getShopItems(s, pkt.ShopType, pkt.ShopID) + writeShopItems(bf, items) + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) + case 9: // Diva song shop + bf := byteframe.NewByteFrame() + items := getShopItems(s, pkt.ShopType, pkt.ShopID) + writeShopItems(bf, items) + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) + case 10: // Item shop, 0-8 + bf := byteframe.NewByteFrame() + items := getShopItems(s, pkt.ShopType, pkt.ShopID) writeShopItems(bf, items) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } @@ -302,10 +253,10 @@ func handleMsgMhfAcquireExchangeShop(s *Session, p mhfpacket.MHFPacket) { continue } buyCount := bf.ReadUint32() - 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.server.db.Exec(`INSERT INTO shop_items_bought (character_id, shop_item_id, bought) + VALUES ($1,$2,$3) ON CONFLICT (character_id, shop_item_id) + DO UPDATE SET bought = bought + $3 + WHERE EXCLUDED.character_id=$1 AND EXCLUDED.shop_item_id=$2 `, s.charID, itemHash, buyCount) } doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})