Files
bl/modules/player/service/pet_fusion_tx.go
xinian 3cfde577eb
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
test: add pet fusion transaction coverage
2026-04-16 09:21:39 +08:00

298 lines
7.1 KiB
Go

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 := syncPetSnapshotBeforeDeleteTx(tx, userID, currentInfo, masterCatchTime); err != nil {
return err
}
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 := syncPetSnapshotBeforeDeleteTx(tx, userID, currentInfo, auxCatchTime); err != nil {
return err
}
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 syncPetSnapshotBeforeDeleteTx(tx gdb.TX, userID uint32, info model.PlayerInfo, catchTime uint32) error {
pet, ok := findPetDataSnapshotInPlayerInfo(info, catchTime)
if !ok {
return nil
}
return updatePetDataTx(tx, userID, catchTime, pet)
}
func findPetDataSnapshotInPlayerInfo(info model.PlayerInfo, catchTime uint32) (model.PetInfo, bool) {
for i := range info.PetList {
if info.PetList[i].CatchTime == catchTime {
return info.PetList[i], true
}
}
for i := range info.BackupPetList {
if info.BackupPetList[i].CatchTime == catchTime {
return info.BackupPetList[i], true
}
}
return model.PetInfo{}, false
}
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
}