package service import ( "blazing/common/socket/errorcode" "blazing/cool" baseservice "blazing/modules/base/service" "blazing/modules/player/model" "context" "errors" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/frame/g" ) var ( errPetFusionInsufficientItems = errors.New("pet fusion insufficient items") errPetFusionPetNotFound = errors.New("pet fusion pet not found") errPetFusionPlayerNotFound = errors.New("pet fusion player not found") ) type PetFusionTxResult struct { NewPet *model.PetInfo CostItemUsed bool UpdatedAux *model.PetInfo } func (s *UserService) PetFusionTx( currentInfo model.PlayerInfo, masterCatchTime uint32, auxCatchTime uint32, materialCounts map[uint32]int, goldItemIDs []uint32, keepAuxItemID uint32, failureItemID uint32, cost int64, newPet *model.PetInfo, failedAux *model.PetInfo, ) (*PetFusionTxResult, errorcode.ErrorCode) { if s == nil || s.Pet == nil { return nil, errorcode.ErrorCodes.ErrSystemError } userID := s.Pet.userid nextInfo := currentInfo if nextInfo.Coins < cost { return nil, errorcode.ErrorCodes.ErrSunDouInsufficient10016 } nextInfo.Coins -= cost result := &PetFusionTxResult{} err := g.DB().Transaction(context.TODO(), func(ctx context.Context, tx gdb.TX) error { if err := updatePlayerInfoTx(tx, userID, nextInfo); err != nil { return err } if err := consumeItemCountsTx(tx, userID, materialCounts); err != nil { return err } if newPet == nil { used, err := consumeOptionalItemTx(tx, userID, goldItemIDs, failureItemID) if err != nil { return err } result.CostItemUsed = used if !used && failedAux != nil { if err := updatePetDataTx(tx, userID, auxCatchTime, *failedAux); err != nil { return err } auxCopy := *failedAux result.UpdatedAux = &auxCopy } return nil } if err := deletePetTx(tx, userID, masterCatchTime); err != nil { return err } used, err := consumeOptionalItemTx(tx, userID, goldItemIDs, keepAuxItemID) if err != nil { return err } result.CostItemUsed = used if !used { if err := deletePetTx(tx, userID, auxCatchTime); err != nil { return err } } catchTime, err := addPetTx(tx, s.Pet, newPet, 0) if err != nil { return err } petCopy := *newPet petCopy.CatchTime = catchTime result.NewPet = &petCopy return nil }) if err == nil { return result, 0 } switch { case errors.Is(err, errPetFusionInsufficientItems): return nil, errorcode.ErrorCodes.ErrInsufficientItems case errors.Is(err, errPetFusionPetNotFound): return nil, errorcode.ErrorCodes.ErrPokemonNotFusionReady2 case errors.Is(err, errPetFusionPlayerNotFound): return nil, errorcode.ErrorCodes.ErrSystemError default: cool.Logger.Error(context.TODO(), "pet fusion tx failed", userID, err) return nil, errorcode.ErrorCodes.ErrSystemError } } func updatePlayerInfoTx(tx gdb.TX, userID uint32, info model.PlayerInfo) error { res, err := tx.Model(model.NewPlayer()).Where("player_id", userID).Data("data", info).Update() if err != nil { return err } affected, err := res.RowsAffected() if err != nil { return err } if affected == 0 { return errPetFusionPlayerNotFound } return nil } func consumeItemCountsTx(tx gdb.TX, userID uint32, itemCounts map[uint32]int) error { for itemID, count := range itemCounts { if err := updateItemCountTx(tx, userID, itemID, -count); err != nil { return err } } return nil } func consumeOptionalItemTx(tx gdb.TX, userID uint32, itemIDs []uint32, target uint32) (bool, error) { for _, itemID := range itemIDs { if itemID != target { continue } if err := updateItemCountTx(tx, userID, target, -1); err != nil { return false, err } return true, nil } return false, nil } func updateItemCountTx(tx gdb.TX, userID uint32, id uint32, count int) error { if cool.Config.ServerInfo.IsVip != 0 && count < 0 { return nil } if id == 0 { return nil } baseModel := tx.Model(model.NewPlayerBag()). Where("player_id", userID). Where("is_vip", cool.Config.ServerInfo.IsVip) ok, err := baseModel.Where("item_id", id).Exist() if err != nil { return err } if ok { updateModel := tx.Model(model.NewPlayerBag()). Where("player_id", userID). Where("is_vip", cool.Config.ServerInfo.IsVip). Where("item_id", id) if count < 0 { updateModel = updateModel.Where("item_cnt + ? > 0", count) } result, err := updateModel.Increment("item_cnt", count) if err != nil { return err } affected, err := result.RowsAffected() if err != nil { return err } if affected == 0 { return errPetFusionInsufficientItems } return nil } if count <= 0 { return errPetFusionInsufficientItems } _, err = tx.Model(model.NewPlayerBag()).Data(g.Map{ "player_id": userID, "item_id": id, "item_cnt": count, "is_vip": cool.Config.ServerInfo.IsVip, }).Insert() return err } func updatePetDataTx(tx gdb.TX, userID uint32, catchTime uint32, pet model.PetInfo) error { res, err := tx.Model(model.NewPet()). Where("player_id", userID). Where("is_vip", cool.Config.ServerInfo.IsVip). Where("catch_time", catchTime). Data("data", pet). Update() if err != nil { return err } affected, err := res.RowsAffected() if err != nil { return err } if affected == 0 { return errPetFusionPetNotFound } return nil } func deletePetTx(tx gdb.TX, userID uint32, catchTime uint32) error { res, err := tx.Model(model.NewPet()). Where("player_id", userID). Where("is_vip", cool.Config.ServerInfo.IsVip). Where("catch_time", catchTime). Delete() if err != nil { return err } affected, err := res.RowsAffected() if err != nil { return err } if affected == 0 { return errPetFusionPetNotFound } return nil } func addPetTx(tx gdb.TX, petService *PetService, petInfo *model.PetInfo, saleCount uint32) (uint32, error) { if petInfo == nil { return 0, nil } catchTime, err := petService.nextCatchTime(tx.Model(baseservice.NewBaseSysUserService().Model)) if err != nil { return 0, err } petCopy := *petInfo petCopy.CatchTime = catchTime playerPet := model.Pet{ PlayerID: petService.userid, Data: petCopy, CatchTime: catchTime, Free: 0, SaleCount: saleCount, } playerPet.IsVip = cool.Config.ServerInfo.IsVip if _, err := tx.Model(model.NewPet()).Insert(playerPet); err != nil { return 0, err } return catchTime, nil }