| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585 |
- package mapExchange
- import (
- actorRemote "f1-game/internal/actor_remote"
- "f1-game/internal/code"
- "f1-game/internal/data"
- "f1-game/internal/enum"
- nameActor "f1-game/internal/name/actor"
- nameRemote "f1-game/internal/name/remote"
- "f1-game/internal/pb"
- "f1-game/internal/types"
- ao "f1-game/nodes/game/player/asset/origin"
- mapCall "f1-game/nodes/map/internal/call"
- "f1-game/nodes/map/internal/db"
- dbExchange "f1-game/nodes/map/internal/db/data/exchange"
- "time"
- cherryNUID "github.com/cherry-game/cherry/extend/nuid"
- ctime "github.com/cherry-game/cherry/extend/time"
- cfacade "github.com/cherry-game/cherry/facade"
- clog "github.com/cherry-game/cherry/logger"
- cactor "github.com/cherry-game/cherry/net/actor"
- )
- // 交易所管理actor
- type actor struct {
- cactor.Base
- tables *db.MapExchangeTables
- }
- func Init(app cfacade.IApplication, tables *db.MapExchangeTables, exchangeDeliverTables map[int64]*db.MapExchangeDeliverTable) {
- actor := &actor{
- tables: tables,
- }
- app.ActorSystem().CreateActor(actor.AliasID(), actor)
- // 创建发货的 子actor
- actor.Child().Create(nameActor.Map_ExchangeDeliver, newActorDeliver(exchangeDeliverTables))
- }
- func (p *actor) AliasID() string {
- return nameActor.Map_Exchange
- }
- func (p *actor) OnInit() {
- p.Remote().Register(nameRemote.MapExchange_HistoryPrice, p.historyPrice)
- p.Remote().Register(nameRemote.MapExchange_Item, p.exchangeItem)
- p.Remote().Register(nameRemote.MapExchange_BuyItem, p.exchangeBuyItem)
- p.Remote().Register(nameRemote.MapExchange_QueryFilter, p.queryFilter)
- p.Remote().Register(nameRemote.MapExchange_MyItems, p.myItems)
- p.Remote().Register(nameRemote.MapExchange_ItemUpdate, p.itemUpdate)
- p.Timer().Add(3*time.Second, p.every3STimer)
- p.Timer().Add(15*time.Second, p.every15STimer)
- }
- func (p *actor) OnFindChild(m *cfacade.Message) (cfacade.IActor, bool) {
- childID := m.TargetPath().ChildID
- if childActor, found := p.Child().Get(childID); found {
- return childActor, true
- }
- clog.Warnf("child actor not found: %s", childID)
- return nil, false
- }
- // 3秒定时器
- func (p *actor) every3STimer() {
- // 处理待上架的列表
- p.onPending()
- // 处理求购商品状态更新
- p.onWishItemUpState()
- // 处理出售商品状态更新
- p.onSellItemUpState()
- }
- // 15秒定时器
- func (p *actor) every15STimer() {
- // 处理求购匹配的列表
- p.onWishMatch()
- }
- // 处理待上架的列表
- func (p *actor) onPending() {
- start := ctime.Now()
- for _, item := range p.tables.PendingListed {
- if item.ItemDetail.Status != enum.ExchangeStatus_Pending {
- continue
- }
- // 更新为上架状态
- item.ItemDetail.Status = enum.ExchangeStatus_Listed
- item.Save2Queue()
- if item.ItemDetail.ActType == enum.ExchangeActType_Sell {
- // 添加到出售查询列表
- p.tables.SellQueryListed.Add(item)
- if p.tables.SellQueryListed.Count() > data.Const.ExchangeCatLimit {
- if sellItem, found := p.tables.SellQueryListed.RemoveAt(0); found {
- // 将超出限制的物品标记为隐藏
- sellItem.ItemDetail.IsFlag = true
- }
- }
- }
- }
- // 清空待上架的列表
- p.tables.PendingListed.Clear()
- cost := time.Since(start.Time)
- if cost > 20*time.Millisecond {
- clog.Warnf("onPending cost too much. cost: %v", cost)
- }
- }
- // 处理求购匹配的列表
- func (p *actor) onWishMatch() {
- start := ctime.Now()
- for _, wishItem := range p.tables.WishMap {
- // 跳过非上架状态的求购项
- if wishItem.ItemDetail.Status != enum.ExchangeStatus_Listed {
- continue
- }
- // 跳过已达上限的求购项
- if wishItem.ItemDetail.IsWishLimit {
- continue
- }
- p.handleWishMatch(wishItem)
- }
- cost := time.Since(start.Time)
- if cost > 20*time.Millisecond {
- clog.Warnf("onWishMatch cost too much. cost: %v", cost)
- }
- }
- // 单个求购匹配逻辑
- func (p *actor) handleWishMatch(wishItem *db.MapExchangeTable) {
- var (
- pageSize = data.Const.ExchangePageSize
- wishPlayerID = wishItem.ItemDetail.PlayerID
- goodsID = wishItem.ItemDetail.Asset.ID
- wishCount = wishItem.ItemDetail.Asset.Num
- wishPrice = wishItem.ItemDetail.Price
- )
- filter := db.GetWishMatchFilter(wishPlayerID, goodsID, wishPrice)
- comparator := db.GetWishMatchComparator()
- _, filterList := p.tables.SellQueryListed.Query(1, pageSize, filter, comparator)
- if len(filterList) == 0 {
- return
- }
- // 匹配的逻辑处理
- sellItem := filterList[0]
- // 交易数量
- count := min(sellItem.ItemDetail.Asset.Num, wishCount)
- // 数量扣除
- sellItem.ItemDetail.Asset.Num -= count
- //修改求购商品的状态
- wishItem.ItemDetail.Asset.Num -= count
- sellItem.Save2Queue()
- wishItem.Save2Queue()
- clog.Infof("onWishMatch match success. wish:%s, sell:%s, count:%d", wishItem.TransID, sellItem.TransID, count)
- errCode := mapCall.Exchange.ItemDeliver(wishPlayerID, sellItem.ToDetailProto(), count, wishPrice)
- if code.IsFail(errCode) {
- clog.Warnf("ItemDeliver failed. errCode: %d, wishPlayerID: %d, sellItem: %s, count: %d, wishPrice: %d", errCode, wishPlayerID, sellItem.TransID, count, wishPrice)
- }
- }
- // 处理出售商品状态更新
- func (p *actor) onSellItemUpState() {
- start := ctime.Now()
- tempFlagList := types.NewList[*db.MapExchangeTable]()
- for transID, sellItem := range p.tables.SellMap {
- // 判断商品是否已经售罄
- if sellItem.ItemDetail.Asset.Num < 1 {
- sellItem.ItemDetail.Status = enum.ExchangeStatus_Filled
- sellItem.ItemDetail.FilledTime = start.ToMillisecond()
- sellItem.IsDelete = true
- sellItem.Save2Queue()
- // 移除已经完成交易的商品
- p.tables.SellQueryListed.Remove(sellItem)
- p.tables.SellMap.Remove(transID)
- // 出售方同步删除
- mapCall.Player.ExchangeItemRemove(sellItem.ItemDetail.PlayerID, enum.ExchangeActType_Sell, sellItem.TransID)
- clog.Infof("onSellItemUpState remove sold item: %s", sellItem.TransID)
- continue
- }
- // 时间到了,处理下架
- if sellItem.ItemDetail.Status == enum.ExchangeStatus_Listed &&
- sellItem.ItemDetail.CanceledTime <= start.ToMillisecond() {
- sellItem.ItemDetail.Status = enum.ExchangeStatus_Canceled
- sellItem.Save2Queue()
- // 通知玩家下架
- mapCall.Player.ExchangeItemCanceled(sellItem.ItemDetail.PlayerID, sellItem.ToDetailProto())
- }
- // 移除已下架的商品
- if sellItem.ItemDetail.Status >= enum.ExchangeStatus_Canceled {
- // 移除可补充标记
- if sellItem.ItemDetail.IsFlag {
- sellItem.ItemDetail.IsFlag = false
- }
- p.tables.SellQueryListed.Remove(sellItem)
- if sellItem.ItemDetail.Status > enum.ExchangeStatus_Canceled {
- // 如果是取回了就标记删除
- if sellItem.ItemDetail.Status == enum.ExchangeStatus_Retrieve {
- sellItem.IsDelete = true
- sellItem.Save2Queue()
- mapCall.Player.ExchangeItemRetrieve(sellItem.ItemDetail.PlayerID, sellItem.ToDetailProto())
- }
- p.tables.SellMap.Remove(transID)
- clog.Infof("onSellItemUpState remove item from SellMap: %s", transID)
- }
- continue
- }
- if sellItem.ItemDetail.IsFlag {
- tempFlagList.Add(sellItem)
- }
- }
- // 补充的数量
- addCount := data.Const.ExchangeCatLimit - p.tables.SellQueryListed.Count()
- if !tempFlagList.IsEmpty() && addCount > 0 {
- // 排序,按时间倒序补充
- tempFlagList.Sort(db.GetListedTimeComparator(true))
- // 往查询列表添加addCount个数量
- count := min(tempFlagList.Size(), int(addCount))
- for i := range count {
- if item, found := tempFlagList.Get(i); found {
- // 移除可补充标记
- item.ItemDetail.IsFlag = false
- p.tables.SellQueryListed.Add(item)
- }
- }
- clog.Debugf("onSellItemUpState add sell item. count: %d", count)
- // 重新排序一下
- p.tables.SellQueryListed.Sort(db.GetListedTimeComparator(false))
- }
- cost := time.Since(start.Time)
- if cost > 20*time.Millisecond {
- clog.Warnf("onItemCanceled cost too much. cost: %v", cost)
- }
- }
- // 处理求购商品状态更新
- func (p *actor) onWishItemUpState() {
- start := ctime.Now()
- for index, wishItem := range p.tables.WishMap {
- if wishItem.ItemDetail.Asset.Num < 1 {
- wishItem.ItemDetail.Status = enum.ExchangeStatus_Filled
- wishItem.ItemDetail.FilledTime = start.ToMillisecond()
- wishItem.IsDelete = true
- wishItem.Save2Queue()
- // 移除求购列表
- p.tables.WishMap.Remove(index)
- // 删除玩家关联求购商品
- mapCall.Player.ExchangeItemRemove(wishItem.ItemDetail.PlayerID, enum.ExchangeActType_Wish, wishItem.TransID)
- clog.Infof("onWishItemUpState remove wish item: %s", wishItem.TransID)
- continue
- }
- // 时间到了,下架
- if wishItem.ItemDetail.Status == enum.ExchangeStatus_Listed &&
- wishItem.ItemDetail.CanceledTime <= start.ToMillisecond() {
- wishItem.ItemDetail.Status = enum.ExchangeStatus_Canceled
- wishItem.Save2Queue()
- // 通知玩家下架
- mapCall.Player.ExchangeItemCanceled(wishItem.ItemDetail.PlayerID, wishItem.ToDetailProto())
- }
- // 如果下架了
- if wishItem.ItemDetail.Status >= enum.ExchangeStatus_Canceled {
- // 如果是取回了就标记删除
- if wishItem.ItemDetail.Status == enum.ExchangeStatus_Retrieve {
- wishItem.IsDelete = true
- wishItem.Save2Queue()
- mapCall.Player.ExchangeItemRetrieve(wishItem.ItemDetail.PlayerID, wishItem.ToDetailProto())
- }
- // 只有交易成功和取回才移除
- if wishItem.ItemDetail.Status > enum.ExchangeStatus_Canceled {
- p.tables.WishMap.Remove(index)
- clog.Infof("onWishItemUpState remove item from WishMap: %s", wishItem.TransID)
- }
- }
- }
- cost := time.Since(start.Time)
- if cost > 20*time.Millisecond {
- clog.Warnf("onItemCanceled cost too much. cost: %v", cost)
- }
- }
- // 交易商品
- func (p *actor) exchangeItem(req *pb.PlayerExchangeItem) (*pb.ExchangeDetail, int32) {
- var (
- itemInfo = req.ItemInfo // 商品信息
- actType = enum.ExchangeActType(itemInfo.ActType) // 类型:1-出售,2-求购
- playerID = req.PlayerID // 玩家ID
- )
- // 上架最大毫秒数
- maxListedMilliSec := db.GetMaxListedMilliSec(actType)
- newItemDetail := dbExchange.NewItemDetail(actType, playerID, itemInfo.Asset.Id, itemInfo.Asset.Num, itemInfo.Price, maxListedMilliSec)
- isOk := newItemDetail.LoadConfig()
- if !isOk {
- return nil, code.ExchangeItemConfigError
- }
- newMapExchangeTable := db.NewMapExchangeTable(cherryNUID.Next(), newItemDetail)
- newMapExchangeTable.Save2Queue()
- p.tables.PendingListed.Add(newMapExchangeTable)
- switch actType {
- case enum.ExchangeActType_Sell:
- // 添加到出售集合
- p.tables.SellMap.Put(newMapExchangeTable.TransID, newMapExchangeTable)
- case enum.ExchangeActType_Wish:
- // 添加到求购集合
- p.tables.WishMap.Put(newMapExchangeTable.TransID, newMapExchangeTable)
- // 更新商品求购价格
- mapExchangeStatsTable, found := db.GetMapExchangeStatsTable(itemInfo.Asset.Id)
- if !found {
- mapExchangeStatsTable = db.NewMapExchangeStatsTable(itemInfo.Asset.Id)
- }
- mapExchangeStatsTable.UpdateWishPrice(itemInfo.Price)
- }
- rsp := newMapExchangeTable.ToDetailProto()
- clog.Infof("exchangeItem success. newMapExchangeTable: %+v", newMapExchangeTable)
- return rsp, code.OK
- }
- // 获取资源历史求购价格
- func (p *actor) historyPrice(req *pb.I32) (*pb.I32I32, int32) {
- var (
- itemID = req.Value // 商品ID
- rsp = &pb.I32I32{} // 响应数据
- )
- mapExchangeStatsTable, found := db.GetMapExchangeStatsTable(itemID)
- if found {
- rsp.Key = mapExchangeStatsTable.MinWishPrice
- rsp.Value = mapExchangeStatsTable.MaxWishPrice
- }
- return rsp, code.OK
- }
- // 购买商品
- func (p *actor) exchangeBuyItem(req *pb.PlayerExchangeBuyItem) (rsp *pb.ExchangeBuyResult, errCode int32) {
- var (
- playerID = req.PlayerID // 玩家ID
- gameNodeID = req.GameNodeID // 玩家游戏节点ID
- transID = req.TransID // 商品流水号ID
- count = req.Count // 购买数量
- )
- errCode = code.OK // 不管错误码都要返回
- mapExchangeTable, found := p.tables.GetExchangeTable(enum.ExchangeActType_Sell, transID)
- if !found {
- rsp = &pb.ExchangeBuyResult{
- Code: code.ExchangeItemNotFound,
- Detail: &pb.ExchangeDetail{
- TransactionID: transID,
- },
- }
- return
- }
- // 判断商品状态
- if mapExchangeTable.ItemDetail.Status > enum.ExchangeStatus_Listed {
- rsp = p.buildBuyResult(mapExchangeTable, code.ExchangeItemSoldOut)
- return
- }
- // 商品数量不足
- if count > mapExchangeTable.ItemDetail.Asset.Num {
- rsp = p.buildBuyResult(mapExchangeTable, code.ExchangeCountNotEnough)
- return
- }
- // 扣除资源
- exchangeGoodsRow, found := data.ExchangeGoods.GetByID(mapExchangeTable.ItemDetail.Asset.ID)
- if !found {
- rsp = p.buildBuyResult(mapExchangeTable, code.ConfigNotFound_ExchangeGoods)
- return
- }
- var (
- // 交易道具ID(金币)
- itemID = exchangeGoodsRow.ItemID
- // 单价
- price = mapExchangeTable.ItemDetail.Price
- // 总价
- sumPrice = int64(price) * count
- asset = types.NewAsset(itemID, sumPrice)
- assets = types.Assets{&asset}
- )
- // 扣除购买的物品
- if _, errcode := actorRemote.GamePlayer.AssetSub(gameNodeID, playerID, assets, ao.ExchangeBuyItemCost); code.IsFail(errcode) {
- rsp = p.buildBuyResult(mapExchangeTable, errcode)
- return
- }
- // 扣除数量
- mapExchangeTable.ItemDetail.Asset.Num -= count
- mapExchangeTable.Save2Queue()
- if errcode := mapCall.Exchange.ItemDeliver(playerID, mapExchangeTable.ToDetailProto(), count, 0); code.IsFail(errcode) {
- clog.Warnf("exchangeBuyItem ItemDeliver failed. errcode: %d, playerID: %d, count: %d, mapExchangeTable: %+v", errcode, playerID, count, mapExchangeTable)
- }
- rsp = p.buildBuyResult(mapExchangeTable, code.OK)
- clog.Infof("exchangeBuyItem success. mapExchangeTable: [%+v] buy count: [%d]", mapExchangeTable, count)
- return
- }
- func (p *actor) buildBuyResult(mapExchangeTable *db.MapExchangeTable, code int32) *pb.ExchangeBuyResult {
- return &pb.ExchangeBuyResult{
- Code: code,
- Detail: mapExchangeTable.ToDetailProto(),
- }
- }
- // 根据条件查询地图交易所的商品列表
- func (p *actor) queryFilter(req *pb.ExchangeQueryFilter) (*pb.ExchangeDetailList, int32) {
- var (
- pageNum = req.PageNum // 页码
- itemIDList = req.ItemIDList // 物品ID列表
- subTypeList = req.SubTypeList // 类型列表(道具表二级类型)
- qualityList = req.QualityList // 品质列表
- priceSort = enum.ExchangeSortType(req.PriceSort) // 按单价排序
- countSort = enum.ExchangeSortType(req.CountSort) // 按数量排序
- pageSize = data.Const.ExchangePageSize // 每页数量
- rsp = &pb.ExchangeDetailList{PageNum: pageNum}
- )
- filter := db.GetFilterByParams(itemIDList, subTypeList, qualityList)
- comparator := db.GetComparatorByParams(priceSort, countSort)
- total, list := p.tables.SellQueryListed.Query(pageNum, pageSize, filter, comparator)
- rsp.Total = total
- for _, mapExchangeTable := range list {
- rsp.List = append(rsp.List, mapExchangeTable.ToDetailProto())
- }
- clog.Debugf("queryFilter success. total: %d, list count: %d, pageNum: %d", total, len(list), pageNum)
- return rsp, code.OK
- }
- // 获取玩家的交易商品列表
- func (p *actor) myItems(req *pb.PlayerExchangeMyItem) (*pb.ExchangeDetailList, int32) {
- var (
- playerID = req.PlayerID
- actType = enum.ExchangeActType(req.ActType)
- transactionIDs = req.TransIDs
- )
- rsp := &pb.ExchangeDetailList{}
- if transactionIDs == nil || len(transactionIDs.List) == 0 {
- return rsp, code.OK
- }
- for _, transID := range transactionIDs.List {
- mapExchangeTable, found := p.tables.GetExchangeTable(actType, transID)
- if !found {
- clog.Warnf("myItems not found. playerID: %d, transID: %s", playerID, transID)
- continue
- }
- rsp.List = append(rsp.List, mapExchangeTable.ToDetailProto())
- }
- clog.Debugf("myItems success. playerID: %d, list count: %d", playerID, len(rsp.List))
- return rsp, code.OK
- }
- // 更新商品: 下架,重新上架,取回(如果没有下架就先下架再取回)
- func (p *actor) itemUpdate(req *pb.PlayerExchangeItemUpdate) int32 {
- var (
- playerID = req.PlayerID // 玩家ID
- transID = req.TransID // 商品流水号ID
- updateType = enum.ExchangeUpdateType(req.UpdateType) // 更新类型:1-下架,2-重新上架,3-取回
- actType = enum.ExchangeActType(req.ActType)
- nowMilliSec = ctime.Now().ToMillisecond()
- )
- mapExchangeTable, found := p.tables.GetExchangeTable(actType, transID)
- if !found {
- return code.ExchangeItemNotFound
- }
- switch updateType {
- case enum.ExchangeUpdateType_Cancel:
- {
- if mapExchangeTable.ItemDetail.Status > enum.ExchangeStatus_Listed {
- return code.ExchangeItemSoldOut
- }
- mapExchangeTable.ItemDetail.Status = enum.ExchangeStatus_Canceled
- mapExchangeTable.ItemDetail.CanceledTime = nowMilliSec
- mapExchangeTable.Save2Queue()
- // 下架商品
- p.tables.SellQueryListed.Remove(mapExchangeTable)
- clog.Infof("itemUpdate SellQueryListed remove. playerID: %d, transID: %s", playerID, transID)
- }
- case enum.ExchangeUpdateType_ReList:
- {
- if mapExchangeTable.ItemDetail.Status != enum.ExchangeStatus_Canceled {
- return code.ExchangeItemNotSoldOut
- }
- // 重新设置上架商品状态
- mapExchangeTable.Relist(nowMilliSec, req.IsWishLimit)
- // 重新上架商品
- p.tables.PendingListed.Add(mapExchangeTable)
- clog.Infof("itemUpdate PendingListed add. playerID: %d, transID: %s", playerID, transID)
- }
- case enum.ExchangeUpdateType_Retrieve:
- {
- // 取回之前如果商品没有下架,则先下架再取回
- if mapExchangeTable.ItemDetail.Status < enum.ExchangeStatus_Canceled {
- mapExchangeTable.ItemDetail.CanceledTime = nowMilliSec
- p.tables.SellQueryListed.Remove(mapExchangeTable)
- clog.Infof("itemUpdate Retrieve SellQueryListed remove. playerID: %d, transID: %s", playerID, transID)
- }
- // 标记取回状态
- mapExchangeTable.ItemDetail.Status = enum.ExchangeStatus_Retrieve
- mapExchangeTable.ItemDetail.RetrieveTime = nowMilliSec
- mapExchangeTable.Save2Queue()
- clog.Infof("itemUpdate Retrieve ok. playerID: %d, transID: %s", playerID, transID)
- }
- }
- return code.OK
- }
|