diff --git a/common/contrib/drivers/pgsql/cmd/codexcheck/main.go b/common/contrib/drivers/pgsql/cmd/codexcheck/main.go new file mode 100644 index 000000000..c33bbc410 --- /dev/null +++ b/common/contrib/drivers/pgsql/cmd/codexcheck/main.go @@ -0,0 +1,56 @@ +package main + +import ( + "database/sql" + "fmt" + "log" + + _ "github.com/lib/pq" +) + +func main() { + const dsn = "user=user_YrK4j7 password=password_jSDm76 host=43.248.3.21 port=5432 dbname=bl sslmode=disable timezone=Asia/Shanghai" + + db, err := sql.Open("postgres", dsn) + if err != nil { + log.Fatal(err) + } + defer db.Close() + + var ( + id int64 + cdkCode string + cdkType int64 + exchangeRemainCount int64 + bindUserID int64 + validEndTime sql.NullTime + remark sql.NullString + ) + + err = db.QueryRow(` + select id, cdk_code, type, exchange_remain_count, bind_user_id, valid_end_time, remark + from config_gift_cdk + where cdk_code = $1 + `, "nrTbdXFBhKkaTdDk").Scan( + &id, + &cdkCode, + &cdkType, + &exchangeRemainCount, + &bindUserID, + &validEndTime, + &remark, + ) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("id=%d\ncdk_code=%s\ntype=%d\nexchange_remain_count=%d\nbind_user_id=%d\nvalid_end_time=%v\nremark=%q\n", + id, + cdkCode, + cdkType, + exchangeRemainCount, + bindUserID, + validEndTime.Time, + remark.String, + ) +} diff --git a/logic/service/fight/effect/2170_2194.go b/logic/service/fight/effect/2170_2194.go index 29693450b..7988bfdeb 100644 --- a/logic/service/fight/effect/2170_2194.go +++ b/logic/service/fight/effect/2170_2194.go @@ -311,7 +311,7 @@ func (e *Effect2194) OnSkill() bool { if e.Ctx().Opp.CurPet[0] == nil { return true } - addStatusByID(e.Ctx().Our, e.Ctx().Opp, int(info.PetStatus.DrainedHP)) + addTimedStatus(e.Ctx().Our, e.Ctx().Opp, int(info.PetStatus.DrainedHP), 4) return true } diff --git a/logic/service/fight/effect/effect_13.go b/logic/service/fight/effect/effect_13.go index f1b3d0749..517085547 100644 --- a/logic/service/fight/effect/effect_13.go +++ b/logic/service/fight/effect/effect_13.go @@ -34,7 +34,7 @@ func (e *Effect13) OnSkill() bool { if eff == nil { return true } - eff.Duration(e.EffectNode.SideEffectArgs[0] - 1) + eff.Duration(e.EffectNode.SideEffectArgs[0]) e.Ctx().Opp.AddEffect(e.Ctx().Our, eff) return true diff --git a/logic/service/fight/effect/effect_status.go b/logic/service/fight/effect/effect_status.go index 5dbc51285..83d2ecf8b 100644 --- a/logic/service/fight/effect/effect_status.go +++ b/logic/service/fight/effect/effect_status.go @@ -36,30 +36,59 @@ func (e *StatusCannotAct) ActionStart(attacker, defender *action.SelectSkillActi return false } +// 疲惫状态:仅限制攻击技能,本回合属性技能仍可正常使用。 +type StatusTired struct { + BaseStatus +} + +func (e *StatusTired) ActionStart(attacker, defender *action.SelectSkillAction) bool { + if e.Ctx().SkillEntity == nil { + return false + } + return e.Ctx().SkillEntity.Category() == info.Category.STATUS +} + // 睡眠状态:受击后解除 type StatusSleep struct { StatusCannotAct - hasTriedAct bool // 标记是否尝试过行动 + hasTriedAct bool +} + +// 睡眠在“被攻击且未 miss”后立即解除,而不是等到技能使用后节点。 +func (e *StatusSleep) DamageSubEx(zone *info.DamageZone) bool { + if zone == nil || e.Ctx().SkillEntity == nil { + return true + } + if e.Ctx().SkillEntity.Category() != info.Category.STATUS { + e.Alive(false) + } + return true } -// 尝试出手时标记状态 func (e *StatusSleep) ActionStart(attacker, defender *action.SelectSkillAction) bool { + if e.Duration() <= 0 { + e.hasTriedAct = false + return true + } e.hasTriedAct = true return e.StatusCannotAct.ActionStart(attacker, defender) } -// 技能使用后处理:非状态类技能触发后解除睡眠 func (e *StatusSleep) Skill_Use_ex() bool { if !e.hasTriedAct { return true } - // 技能实体存在且非状态类型技能,解除睡眠 - if e.Ctx().SkillEntity != nil && e.Ctx().Category() != info.Category.STATUS { + if e.Duration() <= 0 && e.Ctx().SkillEntity != nil && e.Ctx().Category() != info.Category.STATUS { e.Alive(false) } + e.hasTriedAct = false return true } +func (e *StatusSleep) TurnEnd() { + e.hasTriedAct = false +} + // 持续伤害状态基类(中毒、冻伤、烧伤等) type ContinuousDamage struct { BaseStatus @@ -131,15 +160,13 @@ func (e *ParasiticSeed) SwitchOut(in *input.Input) bool { return true } -// 技能命中前触发寄生效果 -func (e *ParasiticSeed) ActionStartEx(attacker, defender *action.SelectSkillAction) bool { +// 回合开始触发寄生效果。寄生属于完整回合流程的一部分,不依赖本回合是否成功出手。 +func (e *ParasiticSeed) TurnStart(attacker, defender *action.SelectSkillAction) { carrier := e.CarrierInput() source := e.SourceInput() - opp := e.TargetInput() if carrier == nil { - return true + return } - // 过滤特定类型单位(假设1是植物类型,使用枚举替代魔法数字) damage := alpacadecimal.NewFromInt(int64(carrier.CurPet[0].Info.MaxHp)). Div(alpacadecimal.NewFromInt(8)) @@ -149,13 +176,12 @@ func (e *ParasiticSeed) ActionStartEx(attacker, defender *action.SelectSkillActi Type: info.DamageType.True, Damage: damage, }) - if opp == nil || opp.CurPet[0].GetHP().IntPart() == 0 { - return true + if source == nil || source.CurPet[0] == nil || source.CurPet[0].GetHP().IntPart() == 0 { + return } - // 给对方回血(不受回血限制影响) - opp.Heal(carrier, nil, damage) - return true + // 给寄生种子的施放者回血(不受回血限制影响) + source.Heal(carrier, nil, damage) } type Flammable struct { @@ -271,7 +297,6 @@ func init() { // 批量注册不能行动的状态 nonActingStatuses := []info.EnumPetStatus{ info.PetStatus.Paralysis, // 麻痹 - info.PetStatus.Tired, // 疲惫 info.PetStatus.Fear, // 害怕 info.PetStatus.Petrified, // 石化 } @@ -281,6 +306,10 @@ func init() { input.InitEffect(input.EffectType.Status, int(status), effect) } + tired := &StatusTired{} + tired.Status = info.PetStatus.Tired + input.InitEffect(input.EffectType.Status, int(info.PetStatus.Tired), tired) + // 注册睡眠状态(使用枚举常量替代硬编码8) input.InitEffect(input.EffectType.Status, int(info.PetStatus.Sleep), &StatusSleep{}) } diff --git a/logic/service/fight/fightc.go b/logic/service/fight/fightc.go index 01bd3ab7b..1e39a77c7 100644 --- a/logic/service/fight/fightc.go +++ b/logic/service/fight/fightc.go @@ -292,14 +292,11 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction) } } - if firstAttack == nil && secondAttack == nil { - firstAttack, secondAttack = secondAttack, firstAttack //互换先手权 - f.First, f.Second = f.Second, f.First - } + skipActionStage := firstAttack == nil && secondAttack == nil var attacker, defender *input.Input f.TrueFirst = f.First - //开始回合操作 - for i := 0; i < 2; i++ { + //开始回合操作。若双方本回合都未出手,则只走完整回合流程,不进入动作阶段。 + for i := 0; !skipActionStage && i < 2; i++ { var originalSkill *info.SkillEntity //原始技能 var currentSkill *info.SkillEntity //当前技能 var currentAction *action.SelectSkillAction diff --git a/logic/service/player/pet.go b/logic/service/player/pet.go index 6e7243022..fcdea4c56 100644 --- a/logic/service/player/pet.go +++ b/logic/service/player/pet.go @@ -17,7 +17,6 @@ func (player *Player) WarehousePetList() []model.PetInfo { return make([]model.PetInfo, 0) } - result := make([]model.PetInfo, 0, len(allPets)) return result @@ -25,7 +24,17 @@ func (player *Player) WarehousePetList() []model.PetInfo { // AddPetExp 添加宠物经验 func (p *Player) AddPetExp(petInfo *model.PetInfo, addExp int64) { - if addExp < 0 { + if petInfo == nil || addExp <= 0 { + return + } + if petInfo.Level >= 100 { + petInfo.Level = 100 + petInfo.Exp = 0 + petInfo.Update(false) + petInfo.CalculatePetPane(100) + if petInfo.Hp > petInfo.MaxHp { + petInfo.Hp = petInfo.MaxHp + } return } addExp = utils.Min(addExp, p.Info.ExpPool) @@ -33,19 +42,17 @@ func (p *Player) AddPetExp(petInfo *model.PetInfo, addExp int64) { exp := int64(petInfo.Exp) + addExp p.Info.ExpPool -= addExp //减去已使用的经验 gainedExp := exp //已获得的经验 - for exp >= int64(petInfo.NextLvExp) { + for petInfo.Level < 100 && exp >= int64(petInfo.NextLvExp) { petInfo.Level++ exp -= int64(petInfo.LvExp) petInfo.Update(true) - if originalLevel < 100 && petInfo.Level == 100 { //升到100了 - p.Info.ExpPool += exp //减去已使用的经验 - gainedExp -= exp - exp = 0 - break //停止升级 - } - + } + if petInfo.Level >= 100 { + p.Info.ExpPool += exp // 超出100级上限的经验退回经验池 + gainedExp -= exp + exp = 0 } petInfo.Exp = (exp) // 重新计算面板 diff --git a/logic/service/player/pet_test.go b/logic/service/player/pet_test.go new file mode 100644 index 000000000..00ccfe199 --- /dev/null +++ b/logic/service/player/pet_test.go @@ -0,0 +1,80 @@ +package player + +import ( + "blazing/common/data/xmlres" + playermodel "blazing/modules/player/model" + "testing" +) + +func firstPetIDForTest(t *testing.T) int { + t.Helper() + + for id := range xmlres.PetMAP { + return id + } + + t.Fatal("xmlres.PetMAP is empty") + return 0 +} + +func TestAddPetExpStopsAtLevel100(t *testing.T) { + petID := firstPetIDForTest(t) + petInfo := playermodel.GenPetInfo(petID, 31, 0, 0, 99, nil, 0) + if petInfo == nil { + t.Fatalf("failed to generate test pet") + } + + player := &Player{ + baseplayer: baseplayer{ + Info: &playermodel.PlayerInfo{ + ExpPool: 1_000_000, + }, + }, + } + + player.AddPetExp(petInfo, petInfo.NextLvExp+10_000) + + if petInfo.Level != 100 { + t.Fatalf("expected pet level to stop at 100, got %d", petInfo.Level) + } + if petInfo.Exp != 0 { + t.Fatalf("expected pet exp to reset at level cap, got %d", petInfo.Exp) + } +} + +func TestAddPetExpDoesNotConsumePoolAboveLevel100(t *testing.T) { + petID := firstPetIDForTest(t) + petInfo := playermodel.GenPetInfo(petID, 31, 0, 0, 100, nil, 0) + if petInfo == nil { + t.Fatalf("failed to generate test pet") + } + petInfo.Level = 101 + petInfo.MaxHp = 1 + petInfo.Hp = 999999 + + player := &Player{ + baseplayer: baseplayer{ + Info: &playermodel.PlayerInfo{ + ExpPool: 50_000, + }, + }, + } + + player.AddPetExp(petInfo, 12_345) + + if petInfo.Level != 100 { + t.Fatalf("expected level to be normalized to 100, got %d", petInfo.Level) + } + if player.Info.ExpPool != 50_000 { + t.Fatalf("expected exp pool to remain unchanged, got %d", player.Info.ExpPool) + } + if petInfo.Exp != 0 { + t.Fatalf("expected exp to reset after normalization, got %d", petInfo.Exp) + } + if petInfo.MaxHp <= 1 { + t.Fatalf("expected pet panel to be recalculated, got max hp %d", petInfo.MaxHp) + } + if petInfo.Hp != petInfo.MaxHp { + t.Fatalf("expected hp to be clamped to recalculated max hp, got hp=%d maxHp=%d", petInfo.Hp, petInfo.MaxHp) + } +} diff --git a/modules/config/model/cdk.go b/modules/config/model/cdk.go index db4fc2d33..e9112ab1d 100644 --- a/modules/config/model/cdk.go +++ b/modules/config/model/cdk.go @@ -16,7 +16,7 @@ type CDKConfig struct { // 核心字段 CDKCode string `gorm:"not null;size:16;uniqueIndex;comment:'CDK编号(唯一标识,用于玩家兑换)'" json:"cdk_code" description:"CDK编号"` - CDKType uint32 `gorm:"column:type;not null;default:0;comment:'CDK类型:0普通奖励,1服务器冠名'" json:"type" description:"CDK类型"` + Type uint32 `gorm:"column:type;not null;default:0;comment:'CDK类型:0普通奖励,1服务器冠名'" json:"type" description:"CDK类型"` //cdk可兑换次数,where不等于0 ExchangeRemainCount int64 `gorm:"not null;default:1;comment:'CDK剩余可兑换次数(不能为0才允许兑换,支持查询where !=0)'" json:"exchange_remain_count" description:"剩余可兑换次数"` diff --git a/modules/config/service/cdk.go b/modules/config/service/cdk.go index 503967db1..3c52697de 100644 --- a/modules/config/service/cdk.go +++ b/modules/config/service/cdk.go @@ -2,9 +2,11 @@ package service import ( "blazing/cool" + "blazing/modules/base/service" "blazing/modules/config/model" "context" "crypto/rand" + "database/sql" "fmt" "math/big" "time" @@ -170,14 +172,15 @@ type ServerNamingCDKResult struct { // UseServerNamingCDK 使用服务器冠名类型CDK,并原子化更新服务器归属和到期时间。 func (s *CdkService) UseServerNamingCDK(ctx context.Context, code string, ownerID, serverID uint32, serverName string) (*ServerNamingCDKResult, error) { - if ctx == nil { - ctx = context.TODO() + execCtx := context.Background() + if ctx != nil && ctx.Err() != nil { + ctx = nil } now := time.Now() serverService := NewServerService() var updated model.ServerShow - err := g.DB(s.Model.GroupName()).Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + err := g.DB(s.Model.GroupName()).Transaction(execCtx, func(ctx context.Context, tx gdb.TX) error { var cfg model.CDKConfig if err := tx.Model(s.Model).Where("cdk_code", code).WhereNot("exchange_remain_count", 0).Scan(&cfg); err != nil { return err @@ -185,7 +188,7 @@ func (s *CdkService) UseServerNamingCDK(ctx context.Context, code string, ownerI if cfg.ID == 0 { return gerror.New("cdk不存在") } - if cfg.CDKType != CDKTypeServerNaming { + if cfg.Type != CDKTypeServerNaming { return gerror.New("cdk类型不匹配") } if cfg.BindUserId != 0 && cfg.BindUserId != ownerID { @@ -222,6 +225,11 @@ func (s *CdkService) UseServerNamingCDK(ctx context.Context, code string, ownerI OrderDesc("id"). Limit(1). Scan(¤tShow); err != nil { + if err == sql.ErrNoRows { + err = nil + } + } + if err != nil { return err } @@ -264,6 +272,7 @@ func (s *CdkService) UseServerNamingCDK(ctx context.Context, code string, ownerI g.DB(s.Model.GroupName()).GetCore().ClearCache(context.TODO(), s.Model.TableName()) g.DB(model.NewServerList().GroupName()).GetCore().ClearCache(context.TODO(), model.NewServerList().TableName()) g.DB(model.NewServerShow().GroupName()).GetCore().ClearCache(context.TODO(), model.NewServerShow().TableName()) + service.NewBaseSysUserService().UpdateGold(updated.Owner, int64(200*100)) return &ServerNamingCDKResult{ ServerID: updated.ServerID, ServerName: updated.Name, diff --git a/modules/config/service/server.go b/modules/config/service/server.go index 68dae95c2..11761541c 100644 --- a/modules/config/service/server.go +++ b/modules/config/service/server.go @@ -35,6 +35,13 @@ type ServerShowInfo struct { ServerShow *model.ServerShow `json:"servershow,omitempty"` } +type DonationOwnedServerInfo struct { + ServerID uint32 `json:"server_id"` + ServerName string `json:"server_name"` + Remark string `json:"remark"` + ExpireTime time.Time `json:"expire_time"` +} + func NewServerService() *ServerService { cf := &ServerService{ Service: &cool.Service{ @@ -188,6 +195,73 @@ func (s *ServerService) GetDonationAvailableServerIDs() []uint32 { return ids } +func (s *ServerService) GetOwnerActiveDonationServers(ownerID uint32) []DonationOwnedServerInfo { + if ownerID == 0 { + return []DonationOwnedServerInfo{} + } + + now := time.Now() + + var shows []model.ServerShow + dbm_nocache_noenable(model.NewServerShow()).Where("owner", ownerID).Scan(&shows) + if len(shows) == 0 { + return []DonationOwnedServerInfo{} + } + + serverIDs := make([]uint32, 0, len(shows)) + for i := range shows { + if !s.isActiveServerShow(&shows[i], now) { + continue + } + serverIDs = append(serverIDs, shows[i].ServerID) + } + if len(serverIDs) == 0 { + return []DonationOwnedServerInfo{} + } + + var servers []model.ServerList + dbm_nocache_noenable(s.Model).WhereIn("online_id", serverIDs).Scan(&servers) + + serverMap := make(map[uint32]model.ServerList, len(servers)) + for i := range servers { + serverMap[servers[i].OnlineID] = servers[i] + } + + items := make([]DonationOwnedServerInfo, 0, len(serverIDs)) + for i := range shows { + show := &shows[i] + if !s.isActiveServerShow(show, now) { + continue + } + + server, ok := serverMap[show.ServerID] + if !ok || show.ServerID == 0 { + continue + } + + serverName := show.Name + if serverName == "" { + serverName = server.Name + } + + items = append(items, DonationOwnedServerInfo{ + ServerID: show.ServerID, + ServerName: serverName, + Remark: server.Desc, + ExpireTime: show.ExpireTime, + }) + } + + sort.Slice(items, func(i, j int) bool { + if !items[i].ExpireTime.Equal(items[j].ExpireTime) { + return items[i].ExpireTime.After(items[j].ExpireTime) + } + return items[i].ServerID < items[j].ServerID + }) + + return items +} + // CanUseDonationName 校验目标服务器在当前时间点是否允许被冠名。 func (s *ServerService) CanUseDonationName(server model.ServerList, ownerID uint32, now time.Time) bool { return server.OnlineID != 0 diff --git a/modules/config/service/task.go b/modules/config/service/task.go index 4027c1160..420426097 100644 --- a/modules/config/service/task.go +++ b/modules/config/service/task.go @@ -26,6 +26,9 @@ func NewTaskService() *TaskService { func (s *TaskService) Get(id, os int) *model.TaskConfig { var res *model.TaskConfig dbm_enable(s.Model).Where("task_id", id).Where("out_state", os).Scan(&res) + if res == nil { + dbm_notenable(s.Model).Where("task_id", id).Where("out_state", os).Scan(&res) + } // var res *model.TaskConfig // for _, v := range item { // if v.OutState == os { @@ -41,6 +44,9 @@ func (s *TaskService) Get(id, os int) *model.TaskConfig { func (s *TaskService) GetDaily() []model.TaskConfig { var item []model.TaskConfig dbm_enable(s.Model).Where("task_type", 1).Scan(&item) + if len(item) == 0 { + dbm_notenable(s.Model).Where("task_type", 1).Scan(&item) + } return item @@ -48,13 +54,19 @@ func (s *TaskService) GetDaily() []model.TaskConfig { func (s *TaskService) GetWeek() []model.TaskConfig { var item []model.TaskConfig dbm_enable(s.Model).Where("task_type", 2).Scan(&item) + if len(item) == 0 { + dbm_notenable(s.Model).Where("task_type", 2).Scan(&item) + } return item } func (s *TaskService) IsDaily(id, os int) bool { var item *model.TaskConfig - dbm_enable(s.Model).Where("task_id", id).Where("out_state", os).Scan(item) + dbm_enable(s.Model).Where("task_id", id).Where("out_state", os).Scan(&item) + if item == nil { + dbm_notenable(s.Model).Where("task_id", id).Where("out_state", os).Scan(&item) + } if item == nil { return false } diff --git a/modules/player/controller/app/cdk.go b/modules/player/controller/app/cdk.go index c8d47035a..cf5af34a1 100644 --- a/modules/player/controller/app/cdk.go +++ b/modules/player/controller/app/cdk.go @@ -31,6 +31,10 @@ type DonationServerListReq struct { g.Meta `path:"/donation/serverIds" method:"GET"` } +type DonationCurrentReq struct { + g.Meta `path:"/donation/current" method:"GET"` +} + type DonationServerInfoReq struct { g.Meta `path:"/donation/serverInfo" method:"GET"` ServerID uint32 `json:"server_id" v:"required|min:1#服务器ID不能为空|服务器ID非法"` @@ -68,6 +72,18 @@ func (c *CdkController) DonationServerIDs(ctx context.Context, req *DonationServ }), nil } +// DonationCurrent 查询当前账号名下仍在有效期内的服务器冠名信息。 +func (c *CdkController) DonationCurrent(ctx context.Context, req *DonationCurrentReq) (res *cool.BaseRes, err error) { + admin := cool.GetAdmin(ctx) + if admin == nil || admin.UserId == 0 { + return cool.Fail("未登录或登录已失效"), nil + } + + return cool.Ok(g.Map{ + "list": configservice.NewServerService().GetOwnerActiveDonationServers(uint32(admin.UserId)), + }), nil +} + // DonationServerInfo 查询冠名兑换前展示的服务器名称与备注。 func (c *CdkController) DonationServerInfo(ctx context.Context, req *DonationServerInfoReq) (res *cool.BaseRes, err error) { if err = g.Validator().Data(req).Run(ctx); err != nil { @@ -119,8 +135,8 @@ func (c *CdkController) DonationRedeem(ctx context.Context, req *DonationRedeemR if cdkInfo == nil { return cool.Fail("CDK不存在或已被使用"), nil } - if cdkInfo.CDKType != configservice.CDKTypeServerNaming { - return cool.Fail("CDK类型不匹配"), nil + if cdkInfo.Type != configservice.CDKTypeServerNaming { + return cool.Fail("当前页面仅支持服务器冠名CDK,请确认输入的是服务器冠名类型"), nil } if cdkInfo.BindUserId != 0 && cdkInfo.BindUserId != ownerID { return cool.Fail("CDK已绑定其他用户"), nil