package main import ( "errors" "fmt" "math" webapiproto "mongo.games.com/game/protocol/webapi" "mongo.games.com/game/webapi" "mongo.games.com/game/worldsrv/internal" "strconv" "time" "mongo.games.com/goserver/core/basic" "mongo.games.com/goserver/core/i18n" "mongo.games.com/goserver/core/logger" "mongo.games.com/goserver/core/module" "mongo.games.com/goserver/core/task" "mongo.games.com/game/common" "mongo.games.com/game/model" "mongo.games.com/game/proto" "mongo.games.com/game/protocol/bag" playerproto "mongo.games.com/game/protocol/player" "mongo.games.com/game/srvdata" ) const ( BagItemMax int32 = 200 ) // 道具功能 Function const ( ItemCanUse = iota //可以使用 ItemCanGive //可以赠送 ItemCanSell //可以出售 ItemCanExchange //可以兑换 ItemMax ) type Item struct { ItemId int32 // 物品ID ItemNum int64 // 物品数量 ////数据表数据 Name string // 名称 //ShowLocation []int32 // 显示位置 //Classify []int32 // 分页类型 1,道具类 2,资源类 3,兑换类 //Type int32 // 道具种类 1,宠物碎片 2,角色碎片 Effect0 []int32 // 竖版道具功能 1,使用 2,赠送 3,出售 Effect []int32 // 横版道具功能 1,使用 2,赠送 3,出售 SaleType int32 // 出售类型 SaleGold int32 // 出售金额 //Composition int32 // 能否叠加 1,能 2,不能 //CompositionMax int32 // 叠加上限 //Time int32 // 道具时效 0为永久 //Location string // 跳转页面 //Describe string // 道具描述 //数据库数据 ObtainTime int64 //获取的时间 } type BagInfo struct { SnId int32 //玩家账号直接在这里生成 Platform string //平台 BagItem map[int32]*Item //背包数据 key为itemId dirty bool } // BagMgrSingleton 背包管理器 var BagMgrSingleton = &BagMgr{ PlayerBag: make(map[int32]*BagInfo), } type BagMgr struct { PlayerBag map[int32]*BagInfo // snid:背包 } func (this *BagMgr) ModuleName() string { return "BagMgr" } func (this *BagMgr) Init() { } func (this *BagMgr) GetBagInfo(snid int32) *BagInfo { if v, exist := this.PlayerBag[snid]; exist { return v } return nil } // GetItem 获取个人的指定道具信息 func (this *BagMgr) GetItem(snid, itemId int32) *Item { p := PlayerMgrSington.GetPlayerBySnId(snid) if p == nil { return nil } item := &Item{ ItemId: itemId, } f := func() { itemX := srvdata.PBDB_GameItemMgr.GetData(itemId) if itemX != nil { item.Name = itemX.Name //item.ShowLocation = itemX.ShowLocation //item.Classify = itemX.Classify //item.Type = itemX.Type item.Effect0 = itemX.Effect item.Effect = itemX.Effect item.SaleType = itemX.SaleType item.SaleGold = itemX.SaleGold //item.Composition = itemX.Composition //item.CompositionMax = itemX.CompositionMax //item.Time = itemX.Time //item.Location = itemX.Location //item.Describe = itemX.Describe } } switch itemId { case common.ItemIDCoin: item.ItemNum = p.Coin f() case common.ItemIDDiamond: item.ItemNum = p.Diamond f() default: if bagItem, ok := this.PlayerBag[snid]; ok { if bagItem != nil { item = bagItem.BagItem[itemId] if item != nil { f() return item } } } } return item } // AddItems 给玩家背包添加道具 // add 加成数量 // gainWay 记录类型 // oper 操作人 // remark 备注 // gameId 游戏id // gameFreeId 场次id // noLog 是否不记录日志 func (this *BagMgr) AddItems(p *Player, addItems []*Item, add int64, gainWay int32, operator, remark string, gameId, gameFreeId int64, noLog bool) (*BagInfo, bag.OpResultCode, bool) { longItem := this.GetItem(p.SnId, common.ItemIDPermit) var items []*Item for _, v := range addItems { if v == nil || v.ItemNum == 0 { continue } item := srvdata.PBDB_GameItemMgr.GetData(v.ItemId) switch item.Type { case common.ItemTypeCoin: //增加金币 if item.Id == common.ItemIDCoin { p.AddCoin(v.ItemNum, add, gainWay, operator, remark) } case common.ItemTypeDiamond: //增加钻石 if item.Id == common.ItemIDDiamond { p.AddDiamond(v.ItemNum, add, gainWay, operator, remark) } case common.ItemTypeFishPower: //增加炮台 p.ItemUnPlayerPowerListEx(v.ItemId) case common.ItemTypeMoneyPond: //增加个人金币池 if v.ItemId == common.ItemIDMoneyPond { p.MoneyPond += v.ItemNum } case common.ItemTypeVipExp: //增加玩家VIP经验 if v.ItemId == common.ItemIDVipExp { p.AddMoneyPayTotal(v.ItemNum) } case common.ItemTypeShopScore: if v.ItemId == common.ItemIDPhoneScore { p.AddPhoneScore(v.ItemNum, 0, gainWay, operator, remark) } case common.ItemTypeExpireTime: p.AddItemRecExpireTime(v.ItemId, v.ItemNum, 0, gainWay, operator, remark) default: // 道具变化 items = append(items, v) } } // 添加道具到背包 var changeItems []int32 var newBagInfo *BagInfo if _, exist := this.PlayerBag[p.SnId]; !exist { newBagInfo = &BagInfo{ SnId: p.SnId, Platform: p.Platform, BagItem: make(map[int32]*Item), } } else { newBagInfo = this.PlayerBag[p.SnId] } if len(items) == 0 { return newBagInfo, bag.OpResultCode_OPRC_Sucess, true } var code = bag.OpResultCode_OPRC_Sucess //检查道具数量 for _, v := range items { if v == nil || v.ItemNum == 0 { continue } item := srvdata.PBDB_GameItemMgr.GetData(v.ItemId) if item == nil { code = bag.OpResultCode_OPRC_IdErr return newBagInfo, code, false } if itm, exist := newBagInfo.BagItem[v.ItemId]; exist { if v.ItemNum < 0 && itm.ItemNum < int64(math.Abs(float64(v.ItemNum))) { code = bag.OpResultCode_OPRC_UseUp return newBagInfo, code, false } } } for _, v := range items { if v == nil || v.ItemNum == 0 { continue } item := srvdata.PBDB_GameItemMgr.GetData(v.ItemId) if item == nil { code = bag.OpResultCode_OPRC_IdErr continue } if itm, exist := newBagInfo.BagItem[v.ItemId]; exist { if itm.ItemNum+v.ItemNum < 0 { code = bag.OpResultCode_OPRC_IdErr continue } itm.ItemNum += v.ItemNum } else { if v.ItemNum < 0 { code = bag.OpResultCode_OPRC_IdErr continue } newBagInfo.BagItem[v.ItemId] = &Item{ ItemId: item.Id, // 物品id ItemNum: v.ItemNum, // 数量 ObtainTime: time.Now().Unix(), } } changeItems = append(changeItems, v.ItemId) // 道具日志 if !noLog { num := v.ItemNum logType := ItemObtain if v.ItemNum < 0 { logType = ItemConsume num = -v.ItemNum } log := model.NewItemLogEx(model.ItemParam{ Platform: p.Platform, SnId: p.SnId, LogType: int32(logType), ItemId: v.ItemId, ItemName: item.Name, Count: num, Remark: remark, TypeId: gainWay, GameId: gameId, GameFreeId: gameFreeId, }) if log != nil { LogChannelSingleton.WriteLog(log) } } if v.ItemId == common.ItemIDWeekScore && v.ItemNum > 0 { TaskSubjectSingleton.Touch(common.TaskTypeActivityScore, &TaskData{ SnId: p.SnId, Num: v.ItemNum, }) } } if len(changeItems) > 0 { newBagInfo.dirty = true p.dirty = true this.PlayerBag[p.SnId] = newBagInfo this.SyncBagData(p.SnId, changeItems...) } for _, v := range changeItems { if v == common.ItemIDPermit { item := this.GetItem(p.SnId, v) startTs := PlatformMgrSingleton.GetConfig(p.Platform).PermitStartTs if item != nil && item.ItemNum > 0 && startTs > 0 { // 赛季积分排行榜 LogChannelSingleton.WriteLog(&model.PermitScore{ Platform: p.Platform, SnId: p.SnId, Name: p.Name, Exp: item.ItemNum, ModId: p.Roles.ModId, StartTs: startTs, Ts: time.Now().Unix(), }) // 参与赛季通行证活动 if longItem == nil || longItem.ItemNum == 0 { LogChannelSingleton.WriteLog(&model.BackendPermitJoin{ Platform: p.Platform, StartTs: PlatformMgrSingleton.GetConfig(p.Platform).PermitStartTs, SnId: p.SnId, }) } } } if v == common.ItemIDLong { p.SendDiffData() } } if code != bag.OpResultCode_OPRC_Sucess { return newBagInfo, code, false } return newBagInfo, code, true } func (this *BagMgr) AddItem(p *Player, itemId, itemNum int64, add int64, gainWay int32, operator, remark string, gameId, gameFreeId int64, noLog bool) (*BagInfo, bag.OpResultCode, bool) { return this.AddItems(p, []*Item{{ItemId: int32(itemId), ItemNum: itemNum}}, add, gainWay, operator, remark, gameId, gameFreeId, noLog) } func (this *BagMgr) AddItemsOffline(platform string, snid int32, addItems []*Item, gainWay int32, operator, remark string, gameId, gameFreeId int64, noLog bool, callback func(err error)) { var findPlayer *model.PlayerBaseInfo task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} { findPlayer = model.GetPlayerBaseInfo(platform, snid) if findPlayer == nil { return nil } newBagInfo := &model.BagInfo{ SnId: findPlayer.SnId, Platform: findPlayer.Platform, BagItem: make(map[int32]*model.Item), } for _, v := range addItems { if v == nil || v.ItemNum == 0 { continue } newBagInfo.BagItem[v.ItemId] = &model.Item{ItemId: v.ItemId, ItemNum: v.ItemNum} } return model.SaveDBBagItem(newBagInfo) }), task.CompleteNotifyWrapper(func(data interface{}, t task.Task) { logger.Logger.Tracef("AddItemsOffline failed: %v %+v", data, *findPlayer) if data == nil && findPlayer != nil { callback(nil) if noLog { return } for _, v := range addItems { itemData := srvdata.PBDB_GameItemMgr.GetData(v.ItemId) if itemData == nil { continue } num := v.ItemNum logType := ItemObtain if v.ItemNum < 0 { logType = ItemConsume num = -v.ItemNum } log := model.NewItemLogEx(model.ItemParam{ Platform: findPlayer.Platform, SnId: findPlayer.SnId, LogType: int32(logType), ItemId: v.ItemId, ItemName: itemData.Name, Count: num, Remark: remark, TypeId: gainWay, GameId: gameId, GameFreeId: gameFreeId, }) if log != nil { LogChannelSingleton.WriteLog(log) } } } else { callback(errors.New("AddItemsOffline failed")) } }), "AddItemsOffline").Start() } // AddMailByItem 赠送道具到邮件 // srcId 发送人 srcName发送人名字 // items[0]:道具id items[1]:道具数量 items[2]:道具id items[3]:道具数量 func (this *BagMgr) AddMailByItem(platform string, srcId int32, srcName string, snid int32, showId int64, items []int32) { logger.Logger.Trace("AddMailByItem:", srcId, srcName, items) var newMsg *model.Message task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} { content := i18n.Tr("languages", "GiftMail", srcName, srcName, srcName, srcName) title := i18n.Tr("languages", "GiftMailTitle") newMsg = model.NewMessageByPlayer("", 1, srcId, srcName, snid, model.MSGTYPE_ITEM, title, content, 0, 0, model.MSGSTATE_UNREAD, time.Now().Unix(), 0, "", items, platform, showId) return model.InsertMessage(platform, newMsg) }), task.CompleteNotifyWrapper(func(data interface{}, t task.Task) { if data == nil { p := PlayerMgrSington.GetPlayerBySnId(snid) if p != nil { p.AddMessage(newMsg) } } }), "AddMailByItem").Start() } // VerifyUpJybInfo 兑换礼包 func (this *BagMgr) VerifyUpJybInfo(p *Player, args *model.VerifyUpJybInfoArgs) { type VerifyInfo struct { jyb *model.JybInfo err error } pack := &playerproto.SCPlayerSetting{ OpRetCode: playerproto.OpResultCode_OPRC_Sucess, } task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} { jyb, err := model.VerifyUpJybInfo(args) info := &VerifyInfo{ jyb: jyb, err: err, } return info }), task.CompleteNotifyWrapper(func(data interface{}, t task.Task) { if data != nil && data.(*VerifyInfo) != nil { jyb := data.(*VerifyInfo).jyb err := data.(*VerifyInfo).err if jyb != nil && jyb.Award != nil { // 领取到礼包 pack.GainItem = &playerproto.JybInfoAward{} if jyb.Award.Item != nil { if len(jyb.Award.Item) > 0 { items := make([]*Item, 0) for _, v := range jyb.Award.Item { items = append(items, &Item{ ItemId: v.ItemId, // 物品id ItemNum: v.ItemNum, // 数量 ObtainTime: time.Now().Unix(), }) } if _, code, _ := this.AddItems(p, items, 0, common.GainWay_ActJybAward, "system", "礼包码兑换", 0, 0, false); code != bag.OpResultCode_OPRC_Sucess { //TODO 添加失败 要回退礼包 logger.Logger.Errorf("CSPlayerSettingHandler AddItems err", code) pack.OpRetCode = playerproto.OpResultCode_OPRC_Error proto.SetDefaults(pack) p.SendToClient(int(playerproto.PlayerPacketID_PACKET_ALL_SETTING), pack) } else { PetMgrSington.CheckShowRed(p) } p.dirty = true } } for _, v := range jyb.Award.Item { //if _, code := BagMgrSingleton.UpBagInfo(p.SnId, p.Platform, v.ItemId, v.ItemNum); code == bag.OpResultCode_OPRC_Sucess { // 需修改 pack.GainItem.ItemId = append(pack.GainItem.ItemId, &playerproto.ItemInfo{ ItemId: v.ItemId, ItemNum: v.ItemNum, }) } if jyb.Award.Coin > 0 { p.AddCoin(jyb.Award.Coin, 0, common.GainWay_ActJybAward, "system", "礼包码兑换") if !p.IsRob { LogChannelSingleton.WriteMQData(model.GenerateSystemFreeGive(p.SnId, p.Name, p.Platform, p.Channel, model.SystemFreeGive_GiveType_ActJybAward, model.SystemFreeGive_CoinType_Coin, int64(jyb.Award.Coin))) } } if jyb.Award.Diamond > 0 { p.AddDiamond(jyb.Award.Diamond, 0, common.GainWay_ActJybAward, "system", "礼包码兑换") if !p.IsRob { LogChannelSingleton.WriteMQData(model.GenerateSystemFreeGive(p.SnId, p.Name, p.Platform, p.Channel, model.SystemFreeGive_GiveType_ActJybAward, model.SystemFreeGive_CoinType_Diamond, int64(jyb.Award.Diamond))) } } p.dirty = true pack.GainItem.Coin = jyb.Award.Coin pack.GainItem.Diamond = jyb.Award.Diamond } else { switch err.Error() { case model.ErrJybISReceive.Error(): pack.OpRetCode = playerproto.OpResultCode_OPRC_Jyb_Receive case model.ErrJYBPlCode.Error(): pack.OpRetCode = playerproto.OpResultCode_OPRC_Jyb_CodeExist case model.ErrJybTsTimeErr.Error(): pack.OpRetCode = playerproto.OpResultCode_OPRC_Jyb_TimeErr default: pack.OpRetCode = playerproto.OpResultCode_OPRC_Jyb_CodeErr } } } else { proto.SetDefaults(pack) p.SendToClient(int(playerproto.PlayerPacketID_PACKET_ALL_SETTING), pack) } proto.SetDefaults(pack) p.SendToClient(int(playerproto.PlayerPacketID_PACKET_ALL_SETTING), pack) }), "VerifyUpJybInfo").Start() // 先检查玩家背包是否足够 } // SyncBagData 通知玩家背包数据变化 func (this *BagMgr) SyncBagData(snid int32, changeItemIds ...int32) { p := PlayerMgrSington.GetPlayerBySnId(snid) if p == nil || p.IsRob { return } var itemInfos []*bag.ItemInfo for _, itemId := range changeItemIds { itemInfo := this.GetItem(snid, itemId) if itemInfo != nil { itemInfos = append(itemInfos, &bag.ItemInfo{ ItemId: itemInfo.ItemId, ItemNum: itemInfo.ItemNum, //Name: itemInfo.Name, //Classify: itemInfo.Classify, //Type: itemInfo.Type, //Effect0: itemInfo.Effect0, //Effect: itemInfo.Effect, //SaleType: itemInfo.SaleType, //SaleGold: itemInfo.SaleGold, //Composition: itemInfo.Composition, //CompositionMax: itemInfo.CompositionMax, //Time: itemInfo.Time, //Location: itemInfo.Location, //Describe: itemInfo.Describe, ObtainTime: itemInfo.ObtainTime, }) if itemInfo.ItemId == VCard { FriendMgrSington.UpdateInfo(p.Platform, p.SnId) } } } p.SyncBagData(itemInfos) } // 兑换话费卡 func (this *BagMgr) ItemExchangeCard(p *Player, itemId int32, money, cardType int32) bool { // 兑换码奖品 var err error var newMsg *model.Message res := &webapiproto.SAGetMatchAwardCode{} pack := &bag.SCItemExChangeRes{RetCode: bag.OpResultCode_OPRC_Sucess} task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} { // 获取兑换码 pack := &webapiproto.ASGetMatchAwardCode{ Platform: p.Platform, Snid: p.SnId, ItemID: itemId, Money: int64(money), Tel: p.Tel, CardType: cardType, Remark: "背包内使用兑换", } logger.Logger.Trace("ItemChangeCode ", pack) var buff []byte buff, err = webapi.APIGetMatchAwardCode(common.GetAppId(), pack) if err != nil { return err } if err = proto.Unmarshal(buff, res); err != nil { return err } if res.GetCode() == "" || res.GetTag() != webapiproto.TagCode_SUCCESS { return nil } var params []string for range []string{"zh", "vi", "en", "kh"} { params = append(params, res.GetMoney(), res.GetCode()) // 金额,兑换码 } // 发送邮件 title := i18n.Tr("languages", "MatchAwardTitle") content := i18n.Tr("languages", "MatchAward", params) newMsg = model.NewMessage("", 0, "", p.SnId, model.MSGTYPE_ITEM_CHANGE, title, content, 0, 0, model.MSGSTATE_UNREAD, time.Now().Unix(), 0, "", nil, p.Platform, model.HallTienlen, nil) err := model.InsertMessage(p.Platform, newMsg) if err != nil { logger.Logger.Errorf("发送邮件失败 snid:%v itemID:%v err:%v", p.SnId, itemId, err) return err } return nil }), task.CompleteNotifyWrapper(func(i interface{}, t task.Task) { if err != nil || res.GetCode() == "" || res.GetTag() != webapiproto.TagCode_SUCCESS { //返回道具 items := make([]*Item, 0) items = append(items, &Item{ ItemId: itemId, // 物品id ItemNum: 1, // 数量 ObtainTime: time.Now().Unix(), }) this.AddItems(p, items, 0, common.GainWayItemChange, "system", "背包内使用兑换失败", 0, 0, false) logger.Logger.Errorf("获取兑换码失败 snid:%v itemID:%v res:%v err:%v", p.SnId, itemId, res, err) pack.RetCode = bag.OpResultCode_OPRC_Error p.SendToClient(int(bag.SPacketID_PACKET_SC_ITEM_EXCHANGE_RES), pack) return } p := PlayerMgrSington.GetPlayerBySnId(p.SnId) if p != nil { p.AddMessage(newMsg) } p.SendToClient(int(bag.SPacketID_PACKET_SC_ITEM_EXCHANGE_RES), pack) }), fmt.Sprintf("ItemExChange%d", p.SnId)).Start() return true } func (this *BagMgr) Update() { } func (this *BagMgr) Shutdown() { module.UnregisteModule(this) } //========================implement IPlayerLoad ============================== func (this *BagMgr) Load(platform string, snid int32, player any) *internal.PlayerLoadReplay { data, err := model.GetBagInfo(snid, platform) return &internal.PlayerLoadReplay{ Platform: platform, Snid: snid, Err: err, Data: data, } } func (this *BagMgr) Callback(player any, ret *internal.PlayerLoadReplay) { if ret == nil || ret.Data == nil { return } bagInfo, ok := ret.Data.(*model.BagInfo) if !ok || bagInfo == nil { return } //数据表 数据库数据相结合 newBagInfo := &BagInfo{ SnId: ret.Snid, Platform: ret.Platform, BagItem: make(map[int32]*Item), } for k, bi := range bagInfo.BagItem { item := srvdata.PBDB_GameItemMgr.GetData(bi.ItemId) if item != nil { if bi.ItemNum > 0 { newBagInfo.BagItem[k] = &Item{ ItemId: bi.ItemId, ItemNum: bi.ItemNum, ObtainTime: bi.ObtainTime, } } } else { logger.Logger.Error("InitBagInfo err: item is nil. ItemId:", bi.ItemId) } } this.PlayerBag[ret.Snid] = newBagInfo } func (this *BagMgr) LoadAfter(platform string, snid int32) *internal.PlayerLoadReplay { // 查询最近游戏 gameID := model.GetRecentGame(platform, snid) return &internal.PlayerLoadReplay{ Platform: platform, Snid: snid, Err: nil, Data: gameID, } } func (this *BagMgr) CallbackAfter(ret *internal.PlayerLoadReplay) { if ret == nil { return } if ret.Err != nil { logger.Logger.Error("BagMgr LoadAfter err:", ret.Err) return } p := PlayerMgrSington.GetPlayerBySnId(ret.Snid) if p != nil { p.GameID = ret.Data.([]int32) } } func (this *BagMgr) Save(platform string, snid int32, isSync, force bool) { bagInfo := this.PlayerBag[snid] logger.Logger.Trace("SaveBagData:", bagInfo) if bagInfo == nil || (!bagInfo.dirty && !force) { return } type BagInfoMap struct { SnId int32 //玩家账号直接在这里生成 Platform string //平台 BagItem []*Item //背包数据 key为itemId } // biMap 数据拷贝 var biMap = BagInfoMap{ SnId: bagInfo.SnId, Platform: bagInfo.Platform, } for _, v := range bagInfo.BagItem { biMap.BagItem = append(biMap.BagItem, &Item{ItemId: v.ItemId, ItemNum: v.ItemNum, ObtainTime: v.ObtainTime}) } var err error f := func() { newBagInfo := &model.BagInfo{ SnId: biMap.SnId, Platform: biMap.Platform, BagItem: make(map[int32]*model.Item), } for _, v := range biMap.BagItem { newBagInfo.BagItem[v.ItemId] = &model.Item{ItemId: v.ItemId, ItemNum: v.ItemNum, ObtainTime: v.ObtainTime} } err = model.UpBagItem(newBagInfo) } cf := func() { if err == nil { bagInfo.dirty = false } } if isSync { f() cf() return } task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} { f() return nil }), task.CompleteNotifyWrapper(func(i interface{}, t task.Task) { cf() }), "SaveBagData").StartByFixExecutor("SnId:" + strconv.Itoa(int(snid))) } func (this *BagMgr) Release(platform string, snid int32) { delete(this.PlayerBag, snid) } func init() { module.RegisteModule(BagMgrSingleton, time.Second, 0) internal.RegisterPlayerLoad(BagMgrSingleton) }