Compare commits
49 Commits
061e4f0c51
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de6c700bb3 | ||
|
|
3232efd05a | ||
|
|
0c79fee8af | ||
|
|
3d77e146e9 | ||
|
|
a43a25c610 | ||
|
|
3cfde577eb | ||
|
|
85f9c02ced | ||
|
|
9f7fd83626 | ||
|
|
ee8b0a2182 | ||
|
|
6e95e014fa | ||
|
|
61a135b3a7 | ||
|
|
5a81534e84 | ||
|
|
523d835ac0 | ||
|
|
5a7e20efec | ||
|
|
5f47bf0589 | ||
|
|
a58ef20fab | ||
|
|
3999f34f77 | ||
|
|
6f51a2e349 | ||
|
|
de755f8fd0 | ||
|
|
803aa71771 | ||
|
|
4a77066d08 | ||
|
|
c9b5f8569f | ||
|
|
ddbfe91d8b | ||
|
|
74ac6ce940 | ||
|
|
43b0bc2dec | ||
|
|
b953e7831a | ||
|
|
62d93f65e7 | ||
|
|
7dfa9c297e | ||
|
|
f95fd49efd | ||
|
|
ce1a2a3588 | ||
|
|
3739c2a6f9 | ||
|
|
eca7dd86e1 | ||
|
|
e161e3626f | ||
|
|
e1a994ba11 | ||
|
|
82bb99d141 | ||
|
|
f9543a5156 | ||
|
|
174830731c | ||
|
|
3a7f593105 | ||
|
|
f6aa0c3339 | ||
|
|
ecc483a11a | ||
|
|
97c8231b44 | ||
|
|
5f5634d999 | ||
|
|
5bfdb5c32b | ||
|
|
90f1447d48 | ||
|
|
ee3f25438f | ||
|
|
2d8969bed2 | ||
|
|
fa5d50955d | ||
|
|
6574450489 | ||
|
|
0daeb70900 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -46,4 +46,5 @@ public/login-linux-amd64
|
||||
.cache/gomod/**
|
||||
public/login-login-linux-amd64
|
||||
public/logic_linux-amd64_1
|
||||
.cache/**
|
||||
.cache/**
|
||||
.agents/**
|
||||
|
||||
@@ -18,11 +18,11 @@ ENV GOMODCACHE=/workspace/.cache/gomod
|
||||
# ==========================================
|
||||
# 2. Codex 配置 (更换时修改这里,重新 build)
|
||||
# ==========================================
|
||||
ENV CODEX_BASE_URL="http://fast.jnm.lol/v1"
|
||||
ENV CODEX_BASE_URL="https://api.jucode.cn/v1"
|
||||
|
||||
ENV CODEX_MODEL="gpt-5.4"
|
||||
|
||||
ENV OPENAI_API_KEY="pk_live_d15iVqaSMxD_XLHh0nrFPJ_fzFZy8IfR4Cd62bERl8g"
|
||||
ENV OPENAI_API_KEY="sk-E0ZZIFNnD0RkhMC9pT2AGMutz9vNy2VLNrgyyobT5voa81pQ"
|
||||
|
||||
# ==========================================
|
||||
# 3. 安装系统依赖、Golang、Code-server
|
||||
|
||||
13
.ide/help.md
13
.ide/help.md
@@ -1,12 +1,7 @@
|
||||
青氧,十九禁给
|
||||
https://api.aibh.site/console 张晟 2922919493Zs.
|
||||
RUN curl -fsSL https://oss.itbzzb.cn/setup-codex.sh | \
|
||||
YES=1 bash -s -- --base-url https://api.aibh.site \
|
||||
--api-key sk-foAHgsJtmanACECtBlFYZE2z4LkwBboEOYETO3ZdWvCxdmNr \
|
||||
--mirror auto
|
||||
|
||||
|
||||
|
||||
https://api.gemai.cc/console/token 免费给部分额度 ,还有100块
|
||||
https://api.jucode.cn/
|
||||
fastai.fast 使用谷歌邮箱https://linshiguge.com/白嫖
|
||||
https://zread.ai/tawer-blog/lmarena-2api/1-overview GLM web2 pai
|
||||
|
||||
https://crazyrouter.com/console 模型最便宜,看看能不能1:10
|
||||
@@ -14,7 +9,7 @@ https://crazyrouter.com/console 模型最便宜,看看能不能1:10
|
||||
https://agentrouter.org/pricing 签到给,有175
|
||||
|
||||
|
||||
kuaipao.ai 充了十块 cjf19970621 cjf19970621
|
||||
|
||||
|
||||
充了十块
|
||||
使用网址:https://www.jnm.lol
|
||||
|
||||
@@ -144,14 +144,14 @@ steps:
|
||||
scp-exe-to-servers: # 与fetch-deploy-config同级,缩进2个空格
|
||||
image: appleboy/drone-scp:1.6.2 # 子元素,缩进4个空格
|
||||
settings: # 子元素,缩进4个空格
|
||||
host: &ssh_host 2697v22.mc5173.cn
|
||||
port: &ssh_port 16493
|
||||
host: &ssh_host 43.248.3.21
|
||||
port: &ssh_port 22
|
||||
username: &ssh_user root
|
||||
password: &ssh_pass xIy9PQcBF96C
|
||||
password: &ssh_pass KQv7yzna7BDukK
|
||||
|
||||
source:
|
||||
- blazing/build/**
|
||||
target: /opt/blazing/
|
||||
target: /ext/blazing/
|
||||
strip_components: 1 # 统一缩进6个空格
|
||||
skip_verify: true # 统一缩进6个空格
|
||||
timeout: 30s # 统一缩进6个空格
|
||||
@@ -167,7 +167,7 @@ steps:
|
||||
password: *ssh_pass
|
||||
script:
|
||||
- |
|
||||
cd /opt/blazing/build
|
||||
cd /ext/blazing/build
|
||||
ls -t login_* 2>/dev/null | head -1
|
||||
BIN_NAME=$(ls -t login_* 2>/dev/null | head -1)
|
||||
echo "BIN_NAME: $BIN_NAME"
|
||||
@@ -201,9 +201,9 @@ steps:
|
||||
# 移动logic产物到public目录
|
||||
LOGIC_BIN=$(ls -t logic_* 2>/dev/null | head -1)
|
||||
if [ -n "$LOGIC_BIN" ]; then
|
||||
mkdir -p /opt/blazing/build/public
|
||||
mv $LOGIC_BIN /opt/blazing/build/public/
|
||||
echo "✅ Logic产物已移动到 /opt/blazing/build/public/ | 文件: $(basename $LOGIC_BIN)"
|
||||
mkdir -p /ext/blazing/build/public
|
||||
mv $LOGIC_BIN /ext/blazing/build/public/
|
||||
echo "✅ Logic产物已移动到 /ext/blazing/build/public/ | 文件: $(basename $LOGIC_BIN)"
|
||||
else
|
||||
echo "⚠️ 未找到Logic产物"
|
||||
fi
|
||||
|
||||
56
common/contrib/drivers/pgsql/cmd/codexcheck/main.go
Normal file
56
common/contrib/drivers/pgsql/cmd/codexcheck/main.go
Normal file
@@ -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,
|
||||
)
|
||||
}
|
||||
@@ -45,8 +45,6 @@ type ServerList struct {
|
||||
OldScreen string `gorm:"comment:'服务器screen参数'" json:"old_screen"`
|
||||
//到期时间ServerList
|
||||
ExpireTime time.Time `gorm:"default:0;comment:'到期时间'" json:"expire_time"`
|
||||
//CDK冠名到期时间ServerList
|
||||
CDKExpireTime time.Time `gorm:"column:cdk_expire_time;default:0;comment:'CDK冠名到期时间'" json:"cdk_expire_time"`
|
||||
}
|
||||
|
||||
func (s *ServerList) GetID() string {
|
||||
|
||||
@@ -3,6 +3,7 @@ package cool
|
||||
import (
|
||||
_ "blazing/contrib/drivers/pgsql"
|
||||
"blazing/cool/cooldb"
|
||||
"sync"
|
||||
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
@@ -10,6 +11,11 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var (
|
||||
autoMigrateMu sync.Mutex
|
||||
autoMigrateModels []IModel
|
||||
)
|
||||
|
||||
// 初始化数据库连接供gorm使用
|
||||
func InitDB(group string) (*gorm.DB, error) {
|
||||
// var ctx context.Context
|
||||
@@ -54,9 +60,33 @@ func getDBbyModel(model IModel) *gorm.DB {
|
||||
|
||||
// 根据entity结构体创建表
|
||||
func CreateTable(model IModel) error {
|
||||
if Config.AutoMigrate {
|
||||
autoMigrateMu.Lock()
|
||||
autoMigrateModels = append(autoMigrateModels, model)
|
||||
autoMigrateMu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunAutoMigrate 显式执行已注册模型的建表/迁移。
|
||||
func RunAutoMigrate() error {
|
||||
if !Config.AutoMigrate {
|
||||
return nil
|
||||
}
|
||||
autoMigrateMu.Lock()
|
||||
models := append([]IModel(nil), autoMigrateModels...)
|
||||
autoMigrateMu.Unlock()
|
||||
|
||||
seen := make(map[string]struct{}, len(models))
|
||||
for _, model := range models {
|
||||
key := model.GroupName() + ":" + model.TableName()
|
||||
if _, ok := seen[key]; ok {
|
||||
continue
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
|
||||
db := getDBbyModel(model)
|
||||
return db.AutoMigrate(model)
|
||||
if err := db.AutoMigrate(model); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ type EffectArg struct {
|
||||
SideEffect []struct {
|
||||
ID int `json:"ID"`
|
||||
SideEffectArgcount int `json:"SideEffectArgcount"`
|
||||
SideEffectArg string `json:"SideEffectArg,omitempty"`
|
||||
SideEffectArg rawFlexibleString `json:"SideEffectArg,omitempty"`
|
||||
} `json:"SideEffect"`
|
||||
} `json:"SideEffects"`
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
_ "blazing/common/data/xmlres/packed"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"fmt"
|
||||
|
||||
"github.com/ECUST-XX/xml"
|
||||
"github.com/gogf/gf/v2/os/gres"
|
||||
@@ -14,22 +14,36 @@ import (
|
||||
|
||||
var path string
|
||||
|
||||
func readConfigContent(path string) []byte {
|
||||
return gres.GetContent(path)
|
||||
}
|
||||
|
||||
func getXml[T any](path string) T {
|
||||
|
||||
// 解析XML到结构体
|
||||
var xmls T
|
||||
|
||||
t1 := gres.GetContent(path)
|
||||
t1 := readConfigContent(path)
|
||||
xml.Unmarshal(t1, &xmls)
|
||||
|
||||
return xmls
|
||||
}
|
||||
func getJson[T any](path string) T {
|
||||
|
||||
// 解析XML到结构体
|
||||
// 解析JSON到结构体
|
||||
var xmls T
|
||||
t1 := gres.GetContent(path)
|
||||
json.Unmarshal(t1, &xmls)
|
||||
t1 := readConfigContent(path)
|
||||
if len(t1) == 0 {
|
||||
fmt.Printf("[xmlres] getJson empty content: path=%s\n", path)
|
||||
return xmls
|
||||
}
|
||||
if err := json.Unmarshal(t1, &xmls); err != nil {
|
||||
head := string(t1)
|
||||
if len(head) > 300 {
|
||||
head = head[:300]
|
||||
}
|
||||
fmt.Printf("[xmlres] getJson unmarshal failed: path=%s len=%d err=%v head=%q\n", path, len(t1), err, head)
|
||||
}
|
||||
|
||||
return xmls
|
||||
}
|
||||
@@ -58,8 +72,6 @@ var (
|
||||
|
||||
func Initfile() {
|
||||
//gres.Dump()
|
||||
path1, _ := os.Getwd()
|
||||
path = path1 + "/public/config/"
|
||||
path = "config/"
|
||||
MapConfig = getXml[Maps](path + "210.xml")
|
||||
|
||||
@@ -87,10 +99,10 @@ func Initfile() {
|
||||
return gconv.Int(m.ProductID)
|
||||
|
||||
})
|
||||
Skill := getXml[MovesTbl](path + "227.xml")
|
||||
skillConfig := getJson[MovesJSON](path + "moves_flash.json")
|
||||
|
||||
SkillMap = make(map[int]Move, len(Skill.Moves))
|
||||
for _, v := range Skill.Moves {
|
||||
SkillMap = make(map[int]Move, len(skillConfig.MovesTbl.Moves.Move))
|
||||
for _, v := range skillConfig.MovesTbl.Moves.Move {
|
||||
v.SideEffectS = ParseSideEffectArgs(v.SideEffect)
|
||||
v.SideEffectArgS = ParseSideEffectArgs(v.SideEffectArg)
|
||||
SkillMap[v.ID] = v
|
||||
|
||||
26
common/data/xmlres/json_compat_test.go
Normal file
26
common/data/xmlres/json_compat_test.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package xmlres
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMoveUnmarshalJSONAcceptsNumericName(t *testing.T) {
|
||||
var move Move
|
||||
if err := json.Unmarshal([]byte(`{"ID":10001,"Name":1,"Category":1,"Type":8,"Power":35,"MaxPP":35,"Accuracy":95}`), &move); err != nil {
|
||||
t.Fatalf("unmarshal move failed: %v", err)
|
||||
}
|
||||
if move.Name != "1" {
|
||||
t.Fatalf("expected numeric name to convert to string, got %q", move.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEffectArgUnmarshalJSONAcceptsNumericSideEffectArg(t *testing.T) {
|
||||
var cfg EffectArg
|
||||
if err := json.Unmarshal([]byte(`{"SideEffects":{"SideEffect":[{"ID":1,"SideEffectArgcount":1,"SideEffectArg":3}]}}`), &cfg); err != nil {
|
||||
t.Fatalf("unmarshal effect arg failed: %v", err)
|
||||
}
|
||||
if got := string(cfg.SideEffects.SideEffect[0].SideEffectArg); got != "3" {
|
||||
t.Fatalf("expected numeric side effect arg to convert to string, got %q", got)
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,6 +1,7 @@
|
||||
package xmlres
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -33,52 +34,156 @@ type MovesTbl struct {
|
||||
Moves []Move `xml:"Moves>Move"`
|
||||
EFF []SideEffect `xml:"SideEffects>SideEffect"`
|
||||
}
|
||||
|
||||
type MovesJSON struct {
|
||||
MovesTbl MovesJSONRoot `json:"MovesTbl"`
|
||||
}
|
||||
|
||||
type MovesJSONRoot struct {
|
||||
Moves struct {
|
||||
Move []Move `json:"Move"`
|
||||
} `json:"Moves"`
|
||||
SideEffects struct {
|
||||
SideEffect []SideEffect `json:"SideEffect"`
|
||||
} `json:"SideEffects"`
|
||||
}
|
||||
|
||||
type MovesMap struct {
|
||||
XMLName xml.Name `xml:"MovesTbl"`
|
||||
Moves map[int]Move
|
||||
EFF []SideEffect `xml:"SideEffects>SideEffect"`
|
||||
}
|
||||
|
||||
type rawFlexibleString string
|
||||
|
||||
func (s *rawFlexibleString) UnmarshalJSON(data []byte) error {
|
||||
text := strings.TrimSpace(string(data))
|
||||
if text == "" || text == "null" {
|
||||
*s = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(text) >= 2 && text[0] == '"' && text[len(text)-1] == '"' {
|
||||
var decoded string
|
||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||
return err
|
||||
}
|
||||
*s = rawFlexibleString(decoded)
|
||||
return nil
|
||||
}
|
||||
|
||||
*s = rawFlexibleString(text)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Move 定义单个技能的结构
|
||||
type Move struct {
|
||||
ID int `xml:"ID,attr"`
|
||||
Name string `xml:"Name,attr"`
|
||||
ID int `xml:"ID,attr" json:"ID"`
|
||||
Name string `xml:"Name,attr" json:"Name"`
|
||||
|
||||
Category int `xml:"Category,attr"` //属性
|
||||
Type int `xml:"Type,attr"` //类型
|
||||
Power int `xml:"Power,attr"` //威力
|
||||
MaxPP int `xml:"MaxPP,attr"` //最大PP
|
||||
Accuracy int `xml:"Accuracy,attr"` //命中率
|
||||
CritRate int `xml:"CritRate,attr,omitempty"` //暴击率
|
||||
Priority int `xml:"Priority,attr,omitempty"` //优先级
|
||||
MustHit int `xml:"MustHit,attr,omitempty"` //是否必中
|
||||
SwapElemType int `xml:"SwapElemType,attr,omitempty"` //技能交换属性
|
||||
CopyElemType int `xml:"CopyElemType,attr,omitempty"` // 技能复制属性
|
||||
CritAtkFirst int `xml:"CritAtkFirst,attr,omitempty"` // 先出手时必定致命一击
|
||||
CritAtkSecond int `xml:"CritAtkSecond,attr,omitempty"` //后出手时必定致命一击
|
||||
CritSelfHalfHp int `xml:"CritSelfHalfHp,attr,omitempty"` //自身体力低于一半时必定致命一击
|
||||
CritFoeHalfHp int `xml:"CritFoeHalfHp,attr,omitempty"` //对方体力低于一半时必定致命一击
|
||||
DmgBindLv int `xml:"DmgBindLv,attr,omitempty"` //使对方受到的伤害值等于自身的等级
|
||||
PwrBindDv int `xml:"PwrBindDv,attr,omitempty"` //威力(power)取决于自身的潜力(个体值)
|
||||
PwrDouble int `xml:"PwrDouble,attr,omitempty"` //攻击时,若对方处于异常状态, 则威力翻倍;
|
||||
DmgBindHpDv int `xml:"DmgBindHpDv,attr,omitempty"` //使对方受到的伤害值等于自身的体力值
|
||||
SideEffect string `xml:"SideEffect,attr,omitempty"`
|
||||
SideEffectArg string `xml:"SideEffectArg,attr,omitempty"`
|
||||
Category int `xml:"Category,attr" json:"Category"` //属性
|
||||
Type int `xml:"Type,attr" json:"Type"` //类型
|
||||
Power int `xml:"Power,attr" json:"Power"` //威力
|
||||
MaxPP int `xml:"MaxPP,attr" json:"MaxPP"` //最大PP
|
||||
Accuracy int `xml:"Accuracy,attr" json:"Accuracy"` //命中率
|
||||
CritRate int `xml:"CritRate,attr,omitempty" json:"CritRate,omitempty"` //暴击率
|
||||
Priority int `xml:"Priority,attr,omitempty" json:"Priority,omitempty"` //优先级
|
||||
MustHit int `xml:"MustHit,attr,omitempty" json:"MustHit,omitempty"` //是否必中
|
||||
SwapElemType int `xml:"SwapElemType,attr,omitempty" json:"SwapElemType,omitempty"` //技能交换属性
|
||||
CopyElemType int `xml:"CopyElemType,attr,omitempty" json:"CopyElemType,omitempty"` // 技能复制属性
|
||||
CritAtkFirst int `xml:"CritAtkFirst,attr,omitempty" json:"CritAtkFirst,omitempty"` // 先出手时必定致命一击
|
||||
CritAtkSecond int `xml:"CritAtkSecond,attr,omitempty" json:"CritAtkSecond,omitempty"` //后出手时必定致命一击
|
||||
CritSelfHalfHp int `xml:"CritSelfHalfHp,attr,omitempty" json:"CritSelfHalfHp,omitempty"` //自身体力低于一半时必定致命一击
|
||||
CritFoeHalfHp int `xml:"CritFoeHalfHp,attr,omitempty" json:"CritFoeHalfHp,omitempty"` //对方体力低于一半时必定致命一击
|
||||
DmgBindLv int `xml:"DmgBindLv,attr,omitempty" json:"DmgBindLv,omitempty"` //使对方受到的伤害值等于自身的等级
|
||||
PwrBindDv int `xml:"PwrBindDv,attr,omitempty" json:"PwrBindDv,omitempty"` //威力(power)取决于自身的潜力(个体值)
|
||||
PwrDouble int `xml:"PwrDouble,attr,omitempty" json:"PwrDouble,omitempty"` //攻击时,若对方处于异常状态, 则威力翻倍;
|
||||
DmgBindHpDv int `xml:"DmgBindHpDv,attr,omitempty" json:"DmgBindHpDv,omitempty"` //使对方受到的伤害值等于自身的体力值
|
||||
SideEffect string `xml:"SideEffect,attr,omitempty" json:"SideEffect,omitempty"`
|
||||
SideEffectArg string `xml:"SideEffectArg,attr,omitempty" json:"SideEffectArg,omitempty"`
|
||||
SideEffectS []int
|
||||
SideEffectArgS []int
|
||||
AtkNum int `xml:"AtkNum,attr,omitempty"`
|
||||
AtkType int `xml:"AtkType,attr,omitempty"` // 0:所有人 1:仅己方 2:仅对方 3:仅自己
|
||||
Url string `xml:"Url,attr,omitempty"`
|
||||
AtkNum int `xml:"AtkNum,attr,omitempty" json:"AtkNum,omitempty"`
|
||||
AtkType int `xml:"AtkType,attr,omitempty" json:"AtkType,omitempty"` // 0:所有人 1:仅己方 2:仅对方 3:仅自己
|
||||
Url string `xml:"Url,attr,omitempty" json:"Url,omitempty"`
|
||||
|
||||
Info string `xml:"info,attr,omitempty"`
|
||||
Info string `xml:"info,attr,omitempty" json:"info,omitempty"`
|
||||
|
||||
CD *int `xml:"CD,attr"`
|
||||
CD *int `xml:"CD,attr" json:"CD"`
|
||||
}
|
||||
|
||||
func (m *Move) UnmarshalJSON(data []byte) error {
|
||||
type moveAlias struct {
|
||||
ID int `json:"ID"`
|
||||
Name rawFlexibleString `json:"Name"`
|
||||
Category int `json:"Category"`
|
||||
Type int `json:"Type"`
|
||||
Power int `json:"Power"`
|
||||
MaxPP int `json:"MaxPP"`
|
||||
Accuracy int `json:"Accuracy"`
|
||||
CritRate int `json:"CritRate,omitempty"`
|
||||
Priority int `json:"Priority,omitempty"`
|
||||
MustHit int `json:"MustHit,omitempty"`
|
||||
SwapElemType int `json:"SwapElemType,omitempty"`
|
||||
CopyElemType int `json:"CopyElemType,omitempty"`
|
||||
CritAtkFirst int `json:"CritAtkFirst,omitempty"`
|
||||
CritAtkSecond int `json:"CritAtkSecond,omitempty"`
|
||||
CritSelfHalfHp int `json:"CritSelfHalfHp,omitempty"`
|
||||
CritFoeHalfHp int `json:"CritFoeHalfHp,omitempty"`
|
||||
DmgBindLv int `json:"DmgBindLv,omitempty"`
|
||||
PwrBindDv int `json:"PwrBindDv,omitempty"`
|
||||
PwrDouble int `json:"PwrDouble,omitempty"`
|
||||
DmgBindHpDv int `json:"DmgBindHpDv,omitempty"`
|
||||
SideEffect rawFlexibleString `json:"SideEffect,omitempty"`
|
||||
SideEffectArg rawFlexibleString `json:"SideEffectArg,omitempty"`
|
||||
AtkNum int `json:"AtkNum,omitempty"`
|
||||
AtkType int `json:"AtkType,omitempty"`
|
||||
Url string `json:"Url,omitempty"`
|
||||
Info string `json:"info,omitempty"`
|
||||
CD *int `json:"CD"`
|
||||
}
|
||||
|
||||
var aux moveAlias
|
||||
if err := json.Unmarshal(data, &aux); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*m = Move{
|
||||
ID: aux.ID,
|
||||
Name: string(aux.Name),
|
||||
Category: aux.Category,
|
||||
Type: aux.Type,
|
||||
Power: aux.Power,
|
||||
MaxPP: aux.MaxPP,
|
||||
Accuracy: aux.Accuracy,
|
||||
CritRate: aux.CritRate,
|
||||
Priority: aux.Priority,
|
||||
MustHit: aux.MustHit,
|
||||
SwapElemType: aux.SwapElemType,
|
||||
CopyElemType: aux.CopyElemType,
|
||||
CritAtkFirst: aux.CritAtkFirst,
|
||||
CritAtkSecond: aux.CritAtkSecond,
|
||||
CritSelfHalfHp: aux.CritSelfHalfHp,
|
||||
CritFoeHalfHp: aux.CritFoeHalfHp,
|
||||
DmgBindLv: aux.DmgBindLv,
|
||||
PwrBindDv: aux.PwrBindDv,
|
||||
PwrDouble: aux.PwrDouble,
|
||||
DmgBindHpDv: aux.DmgBindHpDv,
|
||||
SideEffect: string(aux.SideEffect),
|
||||
SideEffectArg: string(aux.SideEffectArg),
|
||||
AtkNum: aux.AtkNum,
|
||||
AtkType: aux.AtkType,
|
||||
Url: aux.Url,
|
||||
Info: aux.Info,
|
||||
CD: aux.CD,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SideEffect struct {
|
||||
ID int `xml:"ID,attr"`
|
||||
Help string `xml:"help,attr"`
|
||||
Des string `xml:"des,attr"`
|
||||
ID int `xml:"ID,attr" json:"ID"`
|
||||
Help string `xml:"help,attr" json:"help"`
|
||||
Des string `xml:"des,attr" json:"des"`
|
||||
}
|
||||
|
||||
// ReadHTTPFile 通过HTTP GET请求获取远程文件内容
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"blazing/logic/service/fight/pvp"
|
||||
"blazing/logic/service/fight/pvpwire"
|
||||
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -16,7 +15,8 @@ import (
|
||||
)
|
||||
|
||||
// ListenFunc 监听函数
|
||||
// ListenFunc 改造后的 Redis PubSub 监听函数,支持自动重连和心跳保活
|
||||
// ListenFunc 改造后的 Redis PubSub 监听函数,支持自动重连。
|
||||
// 注意:PubSub 连接只负责订阅和接收,避免在同一连接上并发 PING。
|
||||
func ListenFunc(ctx g.Ctx) {
|
||||
if !cool.IsRedisMode {
|
||||
panic(gerror.New("集群模式下, 请使用Redis作为缓存"))
|
||||
@@ -24,9 +24,8 @@ func ListenFunc(ctx g.Ctx) {
|
||||
|
||||
// 定义常量配置
|
||||
const (
|
||||
subscribeTopic = "cool:func" // 订阅的主题
|
||||
retryDelay = 10 * time.Second // 连接失败重试间隔
|
||||
heartbeatInterval = 30 * time.Second // 心跳保活间隔
|
||||
subscribeTopic = "cool:func" // 订阅的主题
|
||||
retryDelay = 10 * time.Second // 连接失败重试间隔
|
||||
)
|
||||
|
||||
// 外层循环:负责连接断开后的整体重连
|
||||
@@ -47,47 +46,25 @@ func ListenFunc(ctx g.Ctx) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 2. 启动心跳保活协程,防止连接因空闲被断开
|
||||
heartbeatCtx, heartbeatCancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
ticker := time.NewTicker(heartbeatInterval)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
heartbeatCancel()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-heartbeatCtx.Done():
|
||||
cool.Logger.Info(ctx, "心跳协程退出")
|
||||
return
|
||||
case <-ticker.C:
|
||||
// 发送 PING 心跳,保持连接活跃
|
||||
_, pingErr := conn.Do(ctx, "PING")
|
||||
if pingErr != nil {
|
||||
cool.Logger.Error(ctx, "Redis 心跳失败,触发重连", "error", pingErr)
|
||||
// 心跳失败时主动关闭连接,触发外层重连
|
||||
_ = conn.Close(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// 3. 订阅主题
|
||||
// 2. 订阅主题
|
||||
_, err = conn.Do(ctx, "subscribe", subscribeTopic)
|
||||
if err != nil {
|
||||
cool.Logger.Error(ctx, "订阅 Redis 主题失败", "topic", subscribeTopic, "error", err)
|
||||
heartbeatCancel() // 关闭心跳协程
|
||||
_ = conn.Close(ctx)
|
||||
time.Sleep(retryDelay)
|
||||
continue
|
||||
}
|
||||
cool.Logger.Info(ctx, "成功订阅 Redis 主题", "topic", subscribeTopic)
|
||||
_, err = conn.Do(ctx, "subscribe", "sun:join") //加入队列
|
||||
if err != nil {
|
||||
cool.Logger.Error(ctx, "订阅 Redis 主题失败", "topic", "sun:join", "error", err)
|
||||
_ = conn.Close(ctx)
|
||||
time.Sleep(retryDelay)
|
||||
continue
|
||||
}
|
||||
cool.Logger.Info(ctx, "成功订阅 Redis 主题", "topic", "sun:join")
|
||||
|
||||
// 4. 循环接收消息
|
||||
// 3. 循环接收消息
|
||||
connError := false
|
||||
for !connError {
|
||||
select {
|
||||
@@ -130,15 +107,15 @@ func ListenFunc(ctx g.Ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 清理资源,准备重连
|
||||
heartbeatCancel() // 关闭心跳协程
|
||||
// 4. 清理资源,准备重连
|
||||
_ = conn.Close(ctx) // 关闭当前连接
|
||||
// Logger.Warn(ctx, "Redis 连接异常,准备重连", "retry_after", retryDelay)
|
||||
cool.Logger.Info(ctx, "Redis 订阅连接异常,准备重连", "retry_after", retryDelay)
|
||||
time.Sleep(retryDelay)
|
||||
}
|
||||
}
|
||||
|
||||
// ListenFight 完全对齐 ListenFunc 写法,修复收不到消息问题
|
||||
// ListenFight 完全对齐 ListenFunc 写法,修复收不到消息问题。
|
||||
// 注意:PubSub 连接只负责订阅和接收,避免在同一连接上并发 PING。
|
||||
func ListenFight(ctx g.Ctx) {
|
||||
if !cool.IsRedisMode {
|
||||
panic(gerror.New("集群模式下, 请使用Redis作为缓存"))
|
||||
@@ -146,8 +123,7 @@ func ListenFight(ctx g.Ctx) {
|
||||
|
||||
// 定义常量配置(对齐 ListenFunc 风格)
|
||||
const (
|
||||
retryDelay = 10 * time.Second // 连接失败重试间隔
|
||||
heartbeatInterval = 30 * time.Second // 心跳保活间隔
|
||||
retryDelay = 10 * time.Second // 连接失败重试间隔
|
||||
)
|
||||
|
||||
// 提前拼接订阅主题(避免重复拼接,便于日志打印)
|
||||
@@ -176,35 +152,7 @@ func ListenFight(ctx g.Ctx) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 2. 启动心跳保活协程(完全对齐 ListenFunc 逻辑)
|
||||
heartbeatCtx, heartbeatCancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
ticker := time.NewTicker(heartbeatInterval)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
heartbeatCancel()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-heartbeatCtx.Done():
|
||||
cool.Logger.Info(ctx, "心跳协程退出")
|
||||
return
|
||||
case <-ticker.C:
|
||||
// 发送 PING 心跳,保持连接活跃
|
||||
_, pingErr := conn.Do(ctx, "PING")
|
||||
if pingErr != nil {
|
||||
cool.Logger.Error(ctx, "Redis 心跳失败,触发重连", "error", pingErr)
|
||||
// 心跳失败时主动关闭连接,触发外层重连
|
||||
_ = conn.Close(ctx)
|
||||
return
|
||||
}
|
||||
cool.Logger.Debug(ctx, "Redis 心跳发送成功,连接正常")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// 3. 订阅主题(对齐 ListenFunc 的错误处理,替换 panic 为优雅重连)
|
||||
// 2. 订阅主题(对齐 ListenFunc 的错误处理,替换 panic 为优雅重连)
|
||||
subscribeTopics := []string{startTopic, pvpServerTopic}
|
||||
if cool.Config.GameOnlineID == pvp.CoordinatorOnlineID {
|
||||
subscribeTopics = append(subscribeTopics, pvpCoordinatorTopic)
|
||||
@@ -214,7 +162,6 @@ func ListenFight(ctx g.Ctx) {
|
||||
_, err = conn.Do(ctx, "subscribe", topic)
|
||||
if err != nil {
|
||||
cool.Logger.Error(ctx, "订阅 Redis 主题失败", "topic", topic, "error", err)
|
||||
heartbeatCancel()
|
||||
_ = conn.Close(ctx)
|
||||
time.Sleep(retryDelay)
|
||||
subscribeFailed = true
|
||||
@@ -240,7 +187,7 @@ func ListenFight(ctx g.Ctx) {
|
||||
// 打印监听提示(保留原有日志)
|
||||
fmt.Println("监听战斗", startTopic)
|
||||
|
||||
// 4. 循环接收消息(完全对齐 ListenFunc 逻辑)
|
||||
// 3. 循环接收消息(完全对齐 ListenFunc 逻辑)
|
||||
connError := false
|
||||
for !connError {
|
||||
select {
|
||||
@@ -282,9 +229,9 @@ func ListenFight(ctx g.Ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 清理资源,准备重连(完全对齐 ListenFunc)
|
||||
heartbeatCancel() // 关闭心跳协程
|
||||
// 4. 清理资源,准备重连(完全对齐 ListenFunc)
|
||||
_ = conn.Close(ctx) // 关闭当前连接
|
||||
cool.Logger.Info(ctx, "Redis 战斗订阅连接异常,准备重连", "retry_after", retryDelay)
|
||||
time.Sleep(retryDelay)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +121,23 @@ func (s *Server) OnTraffic(c gnet.Conn) (action gnet.Action) {
|
||||
}
|
||||
}()
|
||||
|
||||
ws := c.Context().(*player.ClientData).Wsmsg
|
||||
client := c.Context().(*player.ClientData)
|
||||
if s.discorse && !client.IsCrossDomainChecked() {
|
||||
handled, ready, action := handle(c)
|
||||
if action != gnet.None {
|
||||
return action
|
||||
}
|
||||
if handled {
|
||||
client.MarkCrossDomainChecked()
|
||||
return gnet.None
|
||||
}
|
||||
if !ready {
|
||||
return gnet.None
|
||||
}
|
||||
client.MarkCrossDomainChecked()
|
||||
}
|
||||
|
||||
ws := client.Wsmsg
|
||||
if ws.Tcp {
|
||||
return s.handleTCP(c)
|
||||
}
|
||||
|
||||
@@ -2,33 +2,4 @@ module github.com/zmexing/go-sensitive-word
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
|
||||
github.com/orcaman/concurrent-map/v2 v2.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/cloudflare/circl v1.3.7 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.16.0 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||
github.com/quic-go/quic-go v0.40.1 // indirect
|
||||
github.com/refraction-networking/utls v1.6.3 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
)
|
||||
require github.com/orcaman/concurrent-map/v2 v2.0.1
|
||||
|
||||
@@ -1,55 +1,2 @@
|
||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q=
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/imroc/req/v3 v3.42.3 h1:ryPG2AiwouutAopwPxKpWKyxgvO8fB3hts4JXlh3PaE=
|
||||
github.com/imroc/req/v3 v3.42.3/go.mod h1:Axz9Y/a2b++w5/Jht3IhQsdBzrG1ftJd1OJhu21bB2Q=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/onsi/ginkgo/v2 v2.16.0 h1:7q1w9frJDzninhXxjZd+Y/x54XNjG/UlRLIYPZafsPM=
|
||||
github.com/onsi/ginkgo/v2 v2.16.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
|
||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||
github.com/orcaman/concurrent-map/v2 v2.0.1 h1:jOJ5Pg2w1oeB6PeDurIYf6k9PQ+aTITr/6lP/L/zp6c=
|
||||
github.com/orcaman/concurrent-map/v2 v2.0.1/go.mod h1:9Eq3TG2oBe5FirmYWQfYO5iH1q0Jv47PLaNK++uCdOM=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/quic-go/quic-go v0.40.1 h1:X3AGzUNFs0jVuO3esAGnTfvdgvL4fq655WaOi1snv1Q=
|
||||
github.com/quic-go/quic-go v0.40.1/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
|
||||
github.com/refraction-networking/utls v1.6.3 h1:MFOfRN35sSx6K5AZNIoESsBuBxS2LCgRilRIdHb6fDc=
|
||||
github.com/refraction-networking/utls v1.6.3/go.mod h1:yil9+7qSl+gBwJqztoQseO6Pr3h62pQoY1lXiNR/FPs=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
@@ -72,26 +72,32 @@ func (h Controller) DASHIbeiR(req *C2s_MASTER_REWARDSR, c *player.Player) (resul
|
||||
}
|
||||
|
||||
result.ItemList = make([]data.ItemInfo, 0, len(taskInfo.ItemList))
|
||||
c.Service.Task.Exec(masterCupTaskID, func(te *model.Task) bool {
|
||||
progress := bitset32.From(te.Data)
|
||||
if progress.Test(uint(req.ElementType)) {
|
||||
err = errorcode.ErrorCode(errorcode.ErrorCodes.ErrAwardAlreadyClaimed)
|
||||
return false
|
||||
}
|
||||
taskData, taskErr := c.Service.Task.GetTask(masterCupTaskID)
|
||||
if taskErr != nil {
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||
}
|
||||
|
||||
consumeMasterCupItems(c, requiredItems)
|
||||
progress.Set(uint(req.ElementType))
|
||||
te.Data = progress.Bytes()
|
||||
progress := bitset32.From(taskData.Data)
|
||||
if progress.Test(uint(req.ElementType)) {
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrAwardAlreadyClaimed)
|
||||
}
|
||||
|
||||
if taskInfo.Pet != nil {
|
||||
c.Service.Pet.PetAdd(taskInfo.Pet, 0)
|
||||
result.CaptureTime = taskInfo.Pet.CatchTime
|
||||
result.PetTypeId = taskInfo.Pet.ID
|
||||
}
|
||||
if err := consumeMasterCupItems(c, requiredItems); err != nil {
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrInsufficientItems)
|
||||
}
|
||||
progress.Set(uint(req.ElementType))
|
||||
taskData.Data = progress.Bytes()
|
||||
if taskErr = c.Service.Task.SetTask(taskData); taskErr != nil {
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||
}
|
||||
|
||||
appendMasterCupRewardItems(c, result, taskInfo.ItemList)
|
||||
return true
|
||||
})
|
||||
if taskInfo.Pet != nil {
|
||||
c.Service.Pet.PetAdd(taskInfo.Pet, 0)
|
||||
result.CaptureTime = taskInfo.Pet.CatchTime
|
||||
result.PetTypeId = taskInfo.Pet.ID
|
||||
}
|
||||
|
||||
appendMasterCupRewardItems(c, result, taskInfo.ItemList)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -126,10 +132,13 @@ func hasEnoughMasterCupItems(c *player.Player, requiredItems []ItemS) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func consumeMasterCupItems(c *player.Player, requiredItems []ItemS) {
|
||||
func consumeMasterCupItems(c *player.Player, requiredItems []ItemS) error {
|
||||
for _, item := range requiredItems {
|
||||
c.Service.Item.UPDATE(item.ItemId, -int(item.ItemCnt))
|
||||
if err := c.Service.Item.UPDATE(item.ItemId, -int(item.ItemCnt)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func appendMasterCupRewardItems(c *player.Player, result *S2C_MASTER_REWARDSR, itemList []data.ItemInfo) {
|
||||
|
||||
@@ -26,12 +26,11 @@ func (h Controller) EggGamePlay(data1 *C2S_EGG_GAME_PLAY, c *player.Player) (res
|
||||
if data1.EggNum > 10 || data1.EggNum <= 0 {
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||
}
|
||||
if r < 0 {
|
||||
if r <= 0 || data1.EggNum > r {
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrGachaTicketsInsufficient)
|
||||
}
|
||||
if data1.EggNum > r {
|
||||
if err := c.Service.Item.UPDATE(400501, int(-data1.EggNum)); err != nil {
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrGachaTicketsInsufficient)
|
||||
|
||||
}
|
||||
result = &S2C_EGG_GAME_PLAY{ListInfo: []data.ItemInfo{}}
|
||||
if grand.Meet(int(data1.EggNum), 100) {
|
||||
@@ -52,8 +51,6 @@ func (h Controller) EggGamePlay(data1 *C2S_EGG_GAME_PLAY, c *player.Player) (res
|
||||
for _, item := range addedItems {
|
||||
result.ListInfo = append(result.ListInfo, data.ItemInfo{ItemId: item.ItemId, ItemCnt: item.ItemCnt})
|
||||
}
|
||||
|
||||
c.Service.Item.UPDATE(400501, int(-data1.EggNum))
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ func (h Controller) GET_XUANCAI(data *C2s_GET_XUANCAI, c *player.Player) (result
|
||||
|
||||
// 检查该位是否未被选中(避免重复)
|
||||
if (result.Status & mask) == 0 {
|
||||
result.Status |= mask
|
||||
itemID := uint32(400686 + randBitIdx + 1)
|
||||
selectedItems = append(selectedItems, itemID)
|
||||
itemMask[itemID] = mask
|
||||
|
||||
@@ -43,7 +43,7 @@ func (Controller) PlayerFightBoss(req *ChallengeBossInboundInfo, p *player.Playe
|
||||
}
|
||||
|
||||
p.Fightinfo.Status = fightinfo.BattleMode.FIGHT_WITH_NPC
|
||||
p.Fightinfo.Mode = fightinfo.BattleMode.MULTI_MODE
|
||||
p.Fightinfo.Mode = resolveMapNodeFightMode(mapNode)
|
||||
|
||||
ai := player.NewAI_player(monsterInfo)
|
||||
ai.CanCapture = resolveBossCaptureRate(bossConfigs[0].IsCapture, leadMonsterID)
|
||||
@@ -72,23 +72,34 @@ func startMapBossFight(
|
||||
ai *player.AI_player,
|
||||
fn func(model.FightOverInfo),
|
||||
) (*fight.FightC, errorcode.ErrorCode) {
|
||||
ourPets := p.GetPetInfo(100)
|
||||
ourPets := p.GetPetInfo(p.CurrentMapPetLevelLimit())
|
||||
oppPets := ai.GetPetInfo(0)
|
||||
if mapNode != nil && mapNode.IsGroupBoss != 0 {
|
||||
if len(ourPets) > 0 && len(oppPets) > 0 {
|
||||
return fight.NewLegacyGroupFightSingleController(p, ai, ourPets, oppPets, groupBossSlotLimit, fn)
|
||||
slotLimit := groupBossSlotLimit
|
||||
if mapNode.PkFlag != 0 {
|
||||
slotLimit = 1
|
||||
}
|
||||
return fight.NewLegacyGroupFightSingleController(p, ai, ourPets, oppPets, slotLimit, fn)
|
||||
}
|
||||
}
|
||||
return fight.NewFight(p, ai, ourPets, oppPets, fn)
|
||||
}
|
||||
|
||||
func resolveMapNodeFightMode(mapNode *configmodel.MapNode) uint32 {
|
||||
if mapNode != nil && mapNode.PkFlag != 0 {
|
||||
return fightinfo.BattleMode.SINGLE_MODE
|
||||
}
|
||||
return fightinfo.BattleMode.MULTI_MODE
|
||||
}
|
||||
|
||||
// OnPlayerFightNpcMonster 战斗野怪
|
||||
func (Controller) OnPlayerFightNpcMonster(req *FightNpcMonsterInboundInfo, p *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if err = p.CanFight(); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
if req.Number > 9 {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemError
|
||||
if int(req.Number) >= len(p.Data) {
|
||||
return nil, errorcode.ErrorCodes.ErrPokemonNotHere
|
||||
}
|
||||
|
||||
refPet := p.Data[req.Number]
|
||||
@@ -103,7 +114,7 @@ func (Controller) OnPlayerFightNpcMonster(req *FightNpcMonsterInboundInfo, p *pl
|
||||
p.Fightinfo.Status = fightinfo.BattleMode.FIGHT_WITH_NPC
|
||||
p.Fightinfo.Mode = fightinfo.BattleMode.MULTI_MODE
|
||||
|
||||
_, err = fight.NewFight(p, ai, p.GetPetInfo(100), ai.GetPetInfo(0), func(foi model.FightOverInfo) {
|
||||
_, err = fight.NewFight(p, ai, p.GetPetInfo(p.CurrentMapPetLevelLimit()), ai.GetPetInfo(0), func(foi model.FightOverInfo) {
|
||||
handleNpcFightRewards(p, foi, monster)
|
||||
})
|
||||
if err != 0 {
|
||||
@@ -225,7 +236,7 @@ func shouldGrantBossWinBonus(fightC *fight.FightC, playerID uint32, bossConfig c
|
||||
|
||||
func buildNpcMonsterInfo(refPet player.OgrePetInfo, mapID uint32) (*model.PetInfo, *model.PlayerInfo, errorcode.ErrorCode) {
|
||||
if refPet.ID == 0 {
|
||||
return nil, nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||
return nil, nil, errorcode.ErrorCodes.ErrPokemonNotHere
|
||||
}
|
||||
|
||||
monster := model.GenPetInfo(
|
||||
|
||||
@@ -99,6 +99,12 @@ type GetPetLearnableSkillsInboundInfo struct {
|
||||
CatchTime uint32 `json:"catchTime"`
|
||||
}
|
||||
|
||||
type CommitPetSkillsInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"52313" struc:"skip"`
|
||||
CatchTime uint32 `json:"catchTime"`
|
||||
Skill [4]uint32 `json:"skill"`
|
||||
}
|
||||
|
||||
type C2S_PetFusion struct {
|
||||
Head common.TomeeHeader `cmd:"2351" struc:"skip"`
|
||||
Mcatchtime uint32 `json:"mcatchtime" msgpack:"mcatchtime"`
|
||||
|
||||
@@ -18,10 +18,13 @@ func (h Controller) ItemSale(data *C2S_ITEM_SALE, c *player.Player) (result *fig
|
||||
return nil, errorcode.ErrorCodes.ErrSystemError
|
||||
}
|
||||
|
||||
if err := c.Service.Item.UPDATE(data.ItemId, -gconv.Int(data.Amount)); err != nil {
|
||||
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
||||
}
|
||||
|
||||
itemConfig := xmlres.ItemsMAP[int(data.ItemId)]
|
||||
if itemConfig.SellPrice != 0 {
|
||||
c.Info.Coins += int64(int64(data.Amount) * int64(itemConfig.SellPrice))
|
||||
}
|
||||
c.Service.Item.UPDATE(data.ItemId, -gconv.Int(data.Amount))
|
||||
return result, 0
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ import (
|
||||
const (
|
||||
// ItemDefaultLeftTime 道具默认剩余时间(毫秒)
|
||||
ItemDefaultLeftTime = 360000
|
||||
// UniversalNatureItemID 全能性格转化剂Ω
|
||||
UniversalNatureItemID uint32 = 300136
|
||||
)
|
||||
|
||||
// GetUserItemList 获取用户道具列表
|
||||
@@ -33,11 +35,16 @@ func (h Controller) GetUserItemList(data *ItemListInboundInfo, c *player.Player)
|
||||
// c: 当前玩家对象
|
||||
// 返回: 使用后的宠物信息和错误码
|
||||
func (h Controller) UsePetItemOutOfFight(data *C2S_USE_PET_ITEM_OUT_OF_FIGHT, c *player.Player) (result *item.S2C_USE_PET_ITEM_OUT_OF_FIGHT, err errorcode.ErrorCode) {
|
||||
_, currentPet, found := c.FindPet(data.CatchTime)
|
||||
slot, found := c.FindPetBagSlot(data.CatchTime)
|
||||
if !found {
|
||||
return nil, errorcode.ErrorCodes.Err10401
|
||||
}
|
||||
|
||||
currentPet := slot.PetInfoPtr()
|
||||
if currentPet == nil {
|
||||
return nil, errorcode.ErrorCodes.Err10401
|
||||
}
|
||||
|
||||
itemID := uint32(data.ItemID)
|
||||
if c.Service.Item.CheakItem(itemID) == 0 {
|
||||
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
||||
@@ -51,7 +58,10 @@ func (h Controller) UsePetItemOutOfFight(data *C2S_USE_PET_ITEM_OUT_OF_FIGHT, c
|
||||
return nil, errcode
|
||||
}
|
||||
refreshPetPaneKeepHP(currentPet, oldHP)
|
||||
c.Service.Item.UPDATE(itemID, -1)
|
||||
if err := c.Service.Item.UPDATE(itemID, -1); err != nil {
|
||||
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
||||
}
|
||||
c.Service.Info.Save(*c.Info)
|
||||
result = &item.S2C_USE_PET_ITEM_OUT_OF_FIGHT{}
|
||||
copier.Copy(&result, currentPet)
|
||||
return result, 0
|
||||
@@ -83,7 +93,10 @@ func (h Controller) UsePetItemOutOfFight(data *C2S_USE_PET_ITEM_OUT_OF_FIGHT, c
|
||||
return nil, errcode
|
||||
}
|
||||
|
||||
c.Service.Item.UPDATE(itemID, -1)
|
||||
if err := c.Service.Item.UPDATE(itemID, -1); err != nil {
|
||||
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
||||
}
|
||||
c.Service.Info.Save(*c.Info)
|
||||
result = &item.S2C_USE_PET_ITEM_OUT_OF_FIGHT{}
|
||||
copier.Copy(&result, currentPet)
|
||||
return result, 0
|
||||
@@ -126,7 +139,9 @@ func (h Controller) handlexuancaiItem(currentPet *model.PetInfo, c *player.Playe
|
||||
return errorcode.ErrorCodes.ErrItemUnusable
|
||||
}
|
||||
|
||||
c.Service.Item.UPDATE(itemid, -100)
|
||||
if err := c.Service.Item.UPDATE(itemid, -100); err != nil {
|
||||
return errorcode.ErrorCodes.ErrInsufficientItems
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -182,11 +197,24 @@ func (h Controller) handleRegularPetItem(itemID uint32, currentPet *model.PetInf
|
||||
// c: 当前玩家对象
|
||||
// 返回: 无数据和错误码
|
||||
func (h Controller) ResetNature(data *C2S_PET_RESET_NATURE, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
_, currentPet, found := c.FindPet(data.CatchTime)
|
||||
slot, found := c.FindPetBagSlot(data.CatchTime)
|
||||
if !found {
|
||||
return nil, errorcode.ErrorCodes.Err10401
|
||||
}
|
||||
|
||||
currentPet := slot.PetInfoPtr()
|
||||
if currentPet == nil {
|
||||
return nil, errorcode.ErrorCodes.Err10401
|
||||
}
|
||||
|
||||
if data.ItemId != UniversalNatureItemID {
|
||||
return nil, errorcode.ErrorCodes.ErrItemUnusable
|
||||
}
|
||||
|
||||
if _, ok := xmlres.NatureRootMap[int(data.Nature)]; !ok {
|
||||
return nil, errorcode.ErrorCodes.ErrItemUnusable
|
||||
}
|
||||
|
||||
if c.Service.Item.CheakItem(data.ItemId) <= 0 {
|
||||
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
||||
}
|
||||
@@ -194,7 +222,10 @@ func (h Controller) ResetNature(data *C2S_PET_RESET_NATURE, c *player.Player) (r
|
||||
currentHP := currentPet.Hp
|
||||
currentPet.Nature = data.Nature
|
||||
refreshPetPaneKeepHP(currentPet, currentHP)
|
||||
c.Service.Item.UPDATE(data.ItemId, -1)
|
||||
if err := c.Service.Item.UPDATE(data.ItemId, -1); err != nil {
|
||||
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
||||
}
|
||||
c.Service.Info.Save(*c.Info)
|
||||
return result, 0
|
||||
}
|
||||
|
||||
@@ -222,29 +253,38 @@ func (h Controller) UseSpeedupItem(data *C2S_USE_SPEEDUP_ITEM, c *player.Player)
|
||||
if c.Info.TwoTimes != 0 {
|
||||
return nil, errorcode.ErrorCodes.ErrItemInUse
|
||||
}
|
||||
c.Info.TwoTimes += 50 // 玩家对象新增 TwoTimesExp 字段存储双倍剩余次数
|
||||
case 300067:
|
||||
if c.Info.TwoTimes != 0 {
|
||||
return nil, errorcode.ErrorCodes.ErrItemInUse
|
||||
}
|
||||
c.Info.TwoTimes += 25 // 玩家对象新增 TwoTimesExp 字段存储双倍剩余次数
|
||||
case 300051: // 假设1002是三倍经验加速器道具ID
|
||||
if c.Info.ThreeTimes != 0 {
|
||||
return nil, errorcode.ErrorCodes.ErrItemInUse
|
||||
}
|
||||
c.Info.ThreeTimes += 50 // 玩家对象新增 ThreeTimesExp 字段存储三倍剩余次数
|
||||
case 300115:
|
||||
if c.Info.ThreeTimes != 0 {
|
||||
return nil, errorcode.ErrorCodes.ErrItemInUse
|
||||
}
|
||||
c.Info.ThreeTimes += 30 // 玩家对象新增 ThreeTimesExp 字段存储三倍剩余次数
|
||||
|
||||
default:
|
||||
return nil, errorcode.ErrorCodes.ErrSystemError // 未知道具ID
|
||||
}
|
||||
|
||||
// 3. 扣减道具(数量-1)
|
||||
c.Service.Item.UPDATE(data.ItemID, -1)
|
||||
if err := c.Service.Item.UPDATE(data.ItemID, -1); err != nil {
|
||||
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
||||
}
|
||||
|
||||
switch data.ItemID {
|
||||
case 300027: // 假设1001是双倍经验加速器道具ID
|
||||
c.Info.TwoTimes += 50 // 玩家对象新增 TwoTimesExp 字段存储双倍剩余次数
|
||||
case 300067:
|
||||
c.Info.TwoTimes += 25 // 玩家对象新增 TwoTimesExp 字段存储双倍剩余次数
|
||||
case 300051: // 假设1002是三倍经验加速器道具ID
|
||||
c.Info.ThreeTimes += 50 // 玩家对象新增 ThreeTimesExp 字段存储三倍剩余次数
|
||||
case 300115:
|
||||
c.Info.ThreeTimes += 30 // 玩家对象新增 ThreeTimesExp 字段存储三倍剩余次数
|
||||
}
|
||||
result.ThreeTimes = uint32(c.Info.ThreeTimes) // 返回三倍经验剩余次数
|
||||
result.TwoTimes = uint32(c.Info.TwoTimes) // 返回双倍经验剩余次数
|
||||
|
||||
@@ -275,10 +315,11 @@ func (h Controller) UseEnergyXishou(data *C2S_USE_ENERGY_XISHOU, c *player.Playe
|
||||
}
|
||||
// 2. 核心业务逻辑:更新能量吸收器剩余次数
|
||||
// (注:可根据道具ID配置不同的次数加成,此处默认+1)
|
||||
if err := c.Service.Item.UPDATE(data.ItemID, -1); err != nil {
|
||||
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
||||
}
|
||||
c.Info.EnergyTime += 40 // 玩家对象新增 EnergyTimes 字段存储能量吸收剩余次数
|
||||
|
||||
// 3. 扣减道具(数量-1)
|
||||
c.Service.Item.UPDATE(data.ItemID, -1)
|
||||
result = &item.S2C_USE_ENERGY_XISHOU{
|
||||
EnergyTimes: uint32(c.Info.EnergyTime),
|
||||
}
|
||||
@@ -309,6 +350,9 @@ func (h Controller) UseAutoFightItem(data *C2S_USE_AUTO_FIGHT_ITEM, c *player.Pl
|
||||
if c.Info.AutoFightTime != 0 {
|
||||
return nil, errorcode.ErrorCodes.ErrItemInUse
|
||||
}
|
||||
if err := c.Service.Item.UPDATE(data.ItemID, -1); err != nil {
|
||||
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
||||
}
|
||||
result = &item.S2C_USE_AUTO_FIGHT_ITEM{}
|
||||
// 2. 核心业务逻辑:开启自动战斗 + 更新剩余次数
|
||||
c.Info.AutoFight = 3 // 按需求设置自动战斗flag为3(需测试)
|
||||
@@ -324,8 +368,6 @@ func (h Controller) UseAutoFightItem(data *C2S_USE_AUTO_FIGHT_ITEM, c *player.Pl
|
||||
c.Info.AutoFightTime += 50
|
||||
}
|
||||
result.AutoFightTimes = c.Info.AutoFightTime
|
||||
// 3. 扣减道具(数量-1)
|
||||
c.Service.Item.UPDATE(data.ItemID, -1)
|
||||
|
||||
return result, 0
|
||||
}
|
||||
|
||||
60
logic/controller/item_use_test.go
Normal file
60
logic/controller/item_use_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"blazing/common/data/xmlres"
|
||||
"blazing/logic/service/player"
|
||||
playermodel "blazing/modules/player/model"
|
||||
blservice "blazing/modules/player/service"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUsePetItemOutOfFightAppliesToBackupPetInMemory(t *testing.T) {
|
||||
petID := firstPetIDForControllerTest(t)
|
||||
backupPet := playermodel.GenPetInfo(petID, 31, 0, 0, 50, nil, 0)
|
||||
if backupPet == nil {
|
||||
t.Fatal("failed to generate backup pet")
|
||||
}
|
||||
if backupPet.MaxHp <= 1 {
|
||||
t.Fatalf("expected generated pet to have max hp > 1, got %d", backupPet.MaxHp)
|
||||
}
|
||||
backupPet.Hp = 1
|
||||
|
||||
testPlayer := player.NewPlayer(nil)
|
||||
testPlayer.Info = &playermodel.PlayerInfo{
|
||||
UserID: 1,
|
||||
PetList: []playermodel.PetInfo{},
|
||||
BackupPetList: []playermodel.PetInfo{*backupPet},
|
||||
}
|
||||
testPlayer.Service = blservice.NewUserService(testPlayer.Info.UserID)
|
||||
|
||||
itemID, recoverHP := firstRecoverHPItemForControllerTest(t)
|
||||
if recoverHP <= 0 {
|
||||
t.Fatalf("expected positive recover hp for item %d, got %d", itemID, recoverHP)
|
||||
}
|
||||
|
||||
_, err := (Controller{}).UsePetItemOutOfFight(&C2S_USE_PET_ITEM_OUT_OF_FIGHT{
|
||||
CatchTime: backupPet.CatchTime,
|
||||
ItemID: int32(itemID),
|
||||
}, testPlayer)
|
||||
if err != 0 {
|
||||
t.Fatalf("expected backup pet item use to succeed in-memory, got err=%d", err)
|
||||
}
|
||||
|
||||
updatedPet := testPlayer.Info.BackupPetList[0]
|
||||
if updatedPet.Hp <= 1 {
|
||||
t.Fatalf("expected backup pet hp to increase in memory, got hp=%d", updatedPet.Hp)
|
||||
}
|
||||
}
|
||||
|
||||
func firstRecoverHPItemForControllerTest(t *testing.T) (uint32, int) {
|
||||
t.Helper()
|
||||
|
||||
for id, cfg := range xmlres.ItemsMAP {
|
||||
if cfg.HP > 0 {
|
||||
return uint32(id), cfg.HP
|
||||
}
|
||||
}
|
||||
|
||||
t.Fatal("xmlres.ItemsMAP has no HP recovery item")
|
||||
return 0, 0
|
||||
}
|
||||
@@ -19,20 +19,13 @@ func (h Controller) SavePetBagOrder(
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
// PetRetrieveFromWarehouse 领回仓库精灵
|
||||
// PetRetrieveFromWarehouse 从放生仓库领回精灵
|
||||
func (h Controller) PetRetrieveFromWarehouse(
|
||||
data *PET_RETRIEVE, player *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if _, ok := player.FindPetBagSlot(data.CatchTime); ok {
|
||||
return nil, 0
|
||||
if !player.Service.Pet.UpdateFree(data.CatchTime, 1, 0) {
|
||||
return nil, errorcode.ErrorCodes.ErrPokemonIDMismatch
|
||||
}
|
||||
|
||||
petInfo := player.Service.Pet.PetInfoOneByCatchTime(data.CatchTime)
|
||||
if petInfo == nil {
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
player.AddPetToAvailableBag(petInfo.Data)
|
||||
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,9 @@ func (h Controller) PetELV(data *C2S_PET_EVOLVTION, c *player.Player) (result *f
|
||||
return nil, errorcode.ErrorCodes.ErrInsufficientItemsMulti
|
||||
}
|
||||
if branch.EvolvItem != 0 {
|
||||
c.Service.Item.UPDATE(uint32(branch.EvolvItem), -branch.EvolvItemCount)
|
||||
if err := c.Service.Item.UPDATE(uint32(branch.EvolvItem), -branch.EvolvItemCount); err != nil {
|
||||
return nil, errorcode.ErrorCodes.ErrInsufficientItemsMulti
|
||||
}
|
||||
}
|
||||
|
||||
currentPet.ID = uint32(branch.MonTo)
|
||||
|
||||
@@ -17,11 +17,16 @@ const (
|
||||
// c: 当前玩家对象
|
||||
// 返回: 分配结果和错误码
|
||||
func (h Controller) PetEVDiy(data *PetEV, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
_, currentPet, found := c.FindPet(data.CacthTime)
|
||||
slot, found := c.FindPetBagSlot(data.CacthTime)
|
||||
if !found {
|
||||
return nil, errorcode.ErrorCodes.Err10401
|
||||
}
|
||||
|
||||
currentPet := slot.PetInfoPtr()
|
||||
if currentPet == nil {
|
||||
return nil, errorcode.ErrorCodes.Err10401
|
||||
}
|
||||
|
||||
var targetTotal uint32
|
||||
var currentTotal uint32
|
||||
for i, evValue := range data.EVs {
|
||||
|
||||
45
logic/controller/pet_ev_test.go
Normal file
45
logic/controller/pet_ev_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"blazing/logic/service/player"
|
||||
playermodel "blazing/modules/player/model"
|
||||
)
|
||||
|
||||
func TestPetEVDiy_AppliesToBackupPet(t *testing.T) {
|
||||
p := player.NewPlayer(nil)
|
||||
p.Info = &playermodel.PlayerInfo{
|
||||
EVPool: 20,
|
||||
PetList: []playermodel.PetInfo{
|
||||
{CatchTime: 1},
|
||||
},
|
||||
BackupPetList: []playermodel.PetInfo{
|
||||
{
|
||||
CatchTime: 2,
|
||||
Level: 100,
|
||||
Ev: [6]uint32{0, 4, 0, 0, 0, 0},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
data := &PetEV{
|
||||
CacthTime: 2,
|
||||
EVs: [6]uint32{0, 8, 4, 0, 0, 0},
|
||||
}
|
||||
|
||||
_, err := (Controller{}).PetEVDiy(data, p)
|
||||
if err != 0 {
|
||||
t.Fatalf("PetEVDiy returned error: %v", err)
|
||||
}
|
||||
|
||||
got := p.Info.BackupPetList[0].Ev
|
||||
want := [6]uint32{0, 8, 4, 0, 0, 0}
|
||||
if got != want {
|
||||
t.Fatalf("backup pet EV mismatch, got %v want %v", got, want)
|
||||
}
|
||||
|
||||
if gotPool, wantPool := p.Info.EVPool, int64(12); gotPool != wantPool {
|
||||
t.Fatalf("EVPool mismatch, got %d want %d", gotPool, wantPool)
|
||||
}
|
||||
}
|
||||
@@ -65,16 +65,33 @@ func (h Controller) PetFusion(data *C2S_PetFusion, c *player.Player) (result *pe
|
||||
return result, errorcode.ErrorCodes.ErrSunDouInsufficient10016
|
||||
}
|
||||
|
||||
consumeItems(c, materialCounts)
|
||||
c.Info.Coins -= petFusionCost
|
||||
|
||||
if resultPetID == 0 {
|
||||
if useOptionalItem(c, data.GoldItem1[:], petFusionFailureItemID) {
|
||||
result.CostItemFlag = 1
|
||||
} else if auxPet.Level > 5 {
|
||||
auxPet.Downgrade(auxPet.Level - 5)
|
||||
failedAux := *auxPet
|
||||
if auxPet.Level > 5 {
|
||||
failedAux.Downgrade(auxPet.Level - 5)
|
||||
} else {
|
||||
auxPet.Downgrade(1)
|
||||
failedAux.Downgrade(1)
|
||||
}
|
||||
txResult, errCode := c.Service.PetFusionTx(
|
||||
*c.Info,
|
||||
data.Mcatchtime,
|
||||
data.Auxcatchtime,
|
||||
materialCounts,
|
||||
data.GoldItem1[:],
|
||||
petFusionKeepAuxItemID,
|
||||
petFusionFailureItemID,
|
||||
petFusionCost,
|
||||
nil,
|
||||
&failedAux,
|
||||
)
|
||||
if errCode != 0 {
|
||||
return result, errCode
|
||||
}
|
||||
c.Info.Coins -= petFusionCost
|
||||
if txResult.CostItemUsed {
|
||||
result.CostItemFlag = 1
|
||||
} else if txResult.UpdatedAux != nil {
|
||||
*auxPet = *txResult.UpdatedAux
|
||||
}
|
||||
return &pet.PetFusionInfo{}, 0
|
||||
}
|
||||
@@ -101,18 +118,37 @@ func (h Controller) PetFusion(data *C2S_PetFusion, c *player.Player) (result *pe
|
||||
newPet.RandomByWeightShiny()
|
||||
}
|
||||
|
||||
c.Service.Pet.PetAdd(newPet, 0)
|
||||
//println(c.Info.UserID, "进行融合", len(c.Info.PetList), masterPet.ID, auxPet.ID, newPet.ID)
|
||||
|
||||
c.PetDel(data.Mcatchtime)
|
||||
if useOptionalItem(c, data.GoldItem1[:], petFusionKeepAuxItemID) {
|
||||
result.CostItemFlag = 1
|
||||
} else {
|
||||
c.PetDel(data.Auxcatchtime)
|
||||
txResult, errCode := c.Service.PetFusionTx(
|
||||
*c.Info,
|
||||
data.Mcatchtime,
|
||||
data.Auxcatchtime,
|
||||
materialCounts,
|
||||
data.GoldItem1[:],
|
||||
petFusionKeepAuxItemID,
|
||||
petFusionFailureItemID,
|
||||
petFusionCost,
|
||||
newPet,
|
||||
nil,
|
||||
)
|
||||
if errCode != 0 {
|
||||
return result, errCode
|
||||
}
|
||||
|
||||
result.ObtainTime = newPet.CatchTime
|
||||
result.StarterCpTm = newPet.ID
|
||||
c.Info.Coins -= petFusionCost
|
||||
if txResult.CostItemUsed {
|
||||
result.CostItemFlag = 1
|
||||
} else {
|
||||
removePetFromPlayerInfo(c, data.Auxcatchtime)
|
||||
}
|
||||
removePetFromPlayerInfo(c, data.Mcatchtime)
|
||||
|
||||
if txResult.NewPet == nil {
|
||||
return result, errorcode.ErrorCodes.ErrSystemError
|
||||
}
|
||||
c.Info.PetList = append(c.Info.PetList, *txResult.NewPet)
|
||||
|
||||
result.ObtainTime = txResult.NewPet.CatchTime
|
||||
result.StarterCpTm = txResult.NewPet.ID
|
||||
return result, 0
|
||||
}
|
||||
|
||||
@@ -149,21 +185,10 @@ func hasEnoughItems(c *player.Player, itemCounts map[uint32]int) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func consumeItems(c *player.Player, itemCounts map[uint32]int) {
|
||||
for itemID, count := range itemCounts {
|
||||
_ = c.Service.Item.UPDATE(itemID, -count)
|
||||
func removePetFromPlayerInfo(c *player.Player, catchTime uint32) {
|
||||
index, _, ok := c.FindPet(catchTime)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func useOptionalItem(c *player.Player, itemIDs []uint32, target uint32) bool {
|
||||
if c.Service.Item.CheakItem(target) <= 0 {
|
||||
return false
|
||||
}
|
||||
for _, itemID := range itemIDs {
|
||||
if itemID == target {
|
||||
_ = c.Service.Item.UPDATE(target, -1)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
c.Info.PetList = append(c.Info.PetList[:index], c.Info.PetList[index+1:]...)
|
||||
}
|
||||
|
||||
@@ -4,19 +4,22 @@ import (
|
||||
"blazing/common/socket/errorcode"
|
||||
"blazing/logic/service/common"
|
||||
"blazing/logic/service/pet"
|
||||
"blazing/logic/service/player"
|
||||
playersvc "blazing/logic/service/player"
|
||||
"blazing/modules/player/model"
|
||||
)
|
||||
|
||||
// GetPetInfo 获取精灵信息
|
||||
func (h Controller) GetPetInfo(
|
||||
data *GetPetInfoInboundInfo,
|
||||
player *player.Player) (result *model.PetInfo,
|
||||
player *playersvc.Player) (result *model.PetInfo,
|
||||
err errorcode.ErrorCode) {
|
||||
_, petInfo, found := player.FindPet(data.CatchTime)
|
||||
if found {
|
||||
result = petInfo
|
||||
return result, 0
|
||||
levelLimit := player.CurrentMapPetLevelLimit()
|
||||
if slot, found := player.FindPetBagSlot(data.CatchTime); found {
|
||||
if petInfo := slot.PetInfoPtr(); petInfo != nil {
|
||||
petCopy := playersvc.ApplyPetLevelLimit(*petInfo, levelLimit)
|
||||
result = &petCopy
|
||||
return result, 0
|
||||
}
|
||||
}
|
||||
|
||||
ret := player.Service.Pet.PetInfoOneByCatchTime(data.CatchTime)
|
||||
@@ -24,16 +27,18 @@ func (h Controller) GetPetInfo(
|
||||
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||
}
|
||||
|
||||
result = &ret.Data
|
||||
petData := ret.Data
|
||||
petData = playersvc.ApplyPetLevelLimit(petData, levelLimit)
|
||||
result = &petData
|
||||
return result, 0
|
||||
}
|
||||
|
||||
// GetUserBagPetInfo 获取主背包和并列备用精灵列表
|
||||
func (h Controller) GetUserBagPetInfo(
|
||||
data *GetUserBagPetInfoInboundEmpty,
|
||||
player *player.Player) (result *pet.GetUserBagPetInfoOutboundInfo,
|
||||
player *playersvc.Player) (result *pet.GetUserBagPetInfoOutboundInfo,
|
||||
err errorcode.ErrorCode) {
|
||||
return player.GetUserBagPetInfo(), 0
|
||||
return player.GetUserBagPetInfo(player.CurrentMapPetLevelLimit()), 0
|
||||
}
|
||||
|
||||
// GetPetListInboundEmpty 定义请求或响应数据结构。
|
||||
@@ -44,7 +49,7 @@ type GetPetListInboundEmpty struct {
|
||||
// GetPetList 获取当前主背包列表
|
||||
func (h Controller) GetPetList(
|
||||
data *GetPetListInboundEmpty,
|
||||
player *player.Player) (result *pet.GetPetListOutboundInfo,
|
||||
player *playersvc.Player) (result *pet.GetPetListOutboundInfo,
|
||||
err errorcode.ErrorCode) {
|
||||
return buildPetListOutboundInfo(player.Info.PetList), 0
|
||||
}
|
||||
@@ -57,7 +62,7 @@ type GetPetListFreeInboundEmpty struct {
|
||||
// GetPetReleaseList 获取仓库可放生列表
|
||||
func (h Controller) GetPetReleaseList(
|
||||
data *GetPetListFreeInboundEmpty,
|
||||
player *player.Player) (result *pet.GetPetListOutboundInfo,
|
||||
player *playersvc.Player) (result *pet.GetPetListOutboundInfo,
|
||||
err errorcode.ErrorCode) {
|
||||
|
||||
return buildPetListOutboundInfo(player.WarehousePetList()), 0
|
||||
@@ -66,14 +71,13 @@ func (h Controller) GetPetReleaseList(
|
||||
// PlayerShowPet 精灵展示
|
||||
func (h Controller) PlayerShowPet(
|
||||
data *PetShowInboundInfo,
|
||||
player *player.Player) (result *pet.PetShowOutboundInfo, err errorcode.ErrorCode) {
|
||||
player *playersvc.Player) (result *pet.PetShowOutboundInfo, err errorcode.ErrorCode) {
|
||||
result = &pet.PetShowOutboundInfo{
|
||||
UserID: data.Head.UserID,
|
||||
CatchTime: data.CatchTime,
|
||||
Flag: data.Flag,
|
||||
}
|
||||
|
||||
_, currentPet, ok := player.FindPet(data.CatchTime)
|
||||
if data.Flag == 0 {
|
||||
player.SetPetDisplay(0, nil)
|
||||
player.GetSpace().RefreshUserInfo(player)
|
||||
@@ -81,10 +85,16 @@ func (h Controller) PlayerShowPet(
|
||||
return
|
||||
}
|
||||
|
||||
slot, ok := player.FindPetBagSlot(data.CatchTime)
|
||||
if !ok {
|
||||
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||
}
|
||||
|
||||
currentPet := slot.PetInfoPtr()
|
||||
if currentPet == nil {
|
||||
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||
}
|
||||
|
||||
player.SetPetDisplay(data.Flag, currentPet)
|
||||
player.GetSpace().RefreshUserInfo(player)
|
||||
result = buildPetShowOutboundInfo(data.Head.UserID, data.Flag, currentPet)
|
||||
|
||||
@@ -6,8 +6,39 @@ import (
|
||||
"blazing/logic/service/fight"
|
||||
"blazing/logic/service/pet"
|
||||
"blazing/logic/service/player"
|
||||
playermodel "blazing/modules/player/model"
|
||||
)
|
||||
|
||||
func petSetExpLimit(currentPet *playermodel.PetInfo) int64 {
|
||||
if currentPet == nil || currentPet.Level >= 100 {
|
||||
return 0
|
||||
}
|
||||
|
||||
simulatedPet := *currentPet
|
||||
allowedExp := simulatedPet.NextLvExp - simulatedPet.Exp
|
||||
if allowedExp < 0 {
|
||||
allowedExp = 0
|
||||
}
|
||||
|
||||
for simulatedPet.Level < 100 && simulatedPet.NextLvExp > 0 {
|
||||
simulatedPet.Level++
|
||||
simulatedPet.Update(true)
|
||||
if simulatedPet.Level >= 100 {
|
||||
break
|
||||
}
|
||||
allowedExp += simulatedPet.NextLvExp
|
||||
}
|
||||
|
||||
return allowedExp
|
||||
}
|
||||
|
||||
func minInt64(a, b int64) int64 {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// PetReleaseToWarehouse 将精灵从仓库包中放生
|
||||
func (h Controller) PetReleaseToWarehouse(
|
||||
data *PET_ROWEI, player *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
@@ -17,9 +48,8 @@ func (h Controller) PetReleaseToWarehouse(
|
||||
if inBag || inBackup || freeForbidden == 1 {
|
||||
return nil, errorcode.ErrorCodes.ErrCannotReleaseNonWarehouse
|
||||
}
|
||||
|
||||
if !player.Service.Pet.UpdateFree(data.CatchTime, 1) {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemError
|
||||
if !player.Service.Pet.UpdateFree(data.CatchTime, 0, 1) {
|
||||
return nil, errorcode.ErrorCodes.ErrCannotReleaseNonWarehouse
|
||||
}
|
||||
|
||||
return nil, 0
|
||||
@@ -32,9 +62,11 @@ func (h Controller) PetOneCure(
|
||||
return result, errorcode.ErrorCodes.ErrChampionCannotHeal
|
||||
}
|
||||
|
||||
_, currentPet, ok := player.FindPet(data.CatchTime)
|
||||
if ok {
|
||||
defer currentPet.Cure()
|
||||
if slot, ok := player.FindPetBagSlot(data.CatchTime); ok {
|
||||
currentPet := slot.PetInfoPtr()
|
||||
if currentPet != nil {
|
||||
defer currentPet.Cure()
|
||||
}
|
||||
}
|
||||
|
||||
return &pet.PetOneCureOutboundInfo{
|
||||
@@ -63,11 +95,17 @@ func (h Controller) PetFirst(
|
||||
func (h Controller) SetPetExp(
|
||||
data *PetSetExpInboundInfo,
|
||||
player *player.Player) (result *pet.PetSetExpOutboundInfo, err errorcode.ErrorCode) {
|
||||
_, currentPet, found := player.FindPet(data.CatchTime)
|
||||
if !found || currentPet.Level >= 100 {
|
||||
slot, found := player.FindPetBagSlot(data.CatchTime)
|
||||
currentPet := slot.PetInfoPtr()
|
||||
if !found || currentPet == nil || currentPet.Level >= 100 {
|
||||
return &pet.PetSetExpOutboundInfo{Exp: player.Info.ExpPool}, errorcode.ErrorCodes.ErrSystemError
|
||||
}
|
||||
|
||||
player.AddPetExp(currentPet, data.Exp)
|
||||
allowedExp := petSetExpLimit(currentPet)
|
||||
if allowedExp <= 0 {
|
||||
return &pet.PetSetExpOutboundInfo{Exp: player.Info.ExpPool}, errorcode.ErrorCodes.ErrSystemError
|
||||
}
|
||||
|
||||
player.AddPetExp(currentPet, minInt64(data.Exp, allowedExp))
|
||||
return &pet.PetSetExpOutboundInfo{Exp: player.Info.ExpPool}, 0
|
||||
}
|
||||
|
||||
75
logic/controller/pet_manage_test.go
Normal file
75
logic/controller/pet_manage_test.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"blazing/common/data/xmlres"
|
||||
"blazing/common/socket/errorcode"
|
||||
"blazing/logic/service/player"
|
||||
playermodel "blazing/modules/player/model"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func firstPetIDForControllerTest(t *testing.T) int {
|
||||
t.Helper()
|
||||
|
||||
for id := range xmlres.PetMAP {
|
||||
return id
|
||||
}
|
||||
|
||||
t.Fatal("xmlres.PetMAP is empty")
|
||||
return 0
|
||||
}
|
||||
|
||||
func TestSetPetExpCapsLevelAt100(t *testing.T) {
|
||||
petID := firstPetIDForControllerTest(t)
|
||||
petInfo := playermodel.GenPetInfo(petID, 31, 0, 0, 99, nil, 0)
|
||||
if petInfo == nil {
|
||||
t.Fatal("failed to generate test pet")
|
||||
}
|
||||
|
||||
expPool := petInfo.NextLvExp + 10_000
|
||||
testPlayer := player.NewPlayer(nil)
|
||||
testPlayer.Info = &playermodel.PlayerInfo{
|
||||
ExpPool: expPool,
|
||||
PetList: []playermodel.PetInfo{*petInfo},
|
||||
}
|
||||
|
||||
currentPet := &testPlayer.Info.PetList[0]
|
||||
result, err := (Controller{}).SetPetExp(&PetSetExpInboundInfo{
|
||||
CatchTime: currentPet.CatchTime,
|
||||
Exp: expPool,
|
||||
}, testPlayer)
|
||||
if err != 0 {
|
||||
t.Fatalf("expected SetPetExp to succeed, got err=%d", err)
|
||||
}
|
||||
if currentPet.Level != 100 {
|
||||
t.Fatalf("expected pet level to stop at 100, got %d", currentPet.Level)
|
||||
}
|
||||
if result.Exp != 10_000 {
|
||||
t.Fatalf("expected overflow exp to remain in pool, got %d", result.Exp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetPetExpRejectsPetAtLevel100(t *testing.T) {
|
||||
petID := firstPetIDForControllerTest(t)
|
||||
petInfo := playermodel.GenPetInfo(petID, 31, 0, 0, 100, nil, 0)
|
||||
if petInfo == nil {
|
||||
t.Fatal("failed to generate test pet")
|
||||
}
|
||||
|
||||
testPlayer := player.NewPlayer(nil)
|
||||
testPlayer.Info = &playermodel.PlayerInfo{
|
||||
ExpPool: 50_000,
|
||||
PetList: []playermodel.PetInfo{*petInfo},
|
||||
}
|
||||
|
||||
result, err := (Controller{}).SetPetExp(&PetSetExpInboundInfo{
|
||||
CatchTime: petInfo.CatchTime,
|
||||
Exp: 12_345,
|
||||
}, testPlayer)
|
||||
if err != errorcode.ErrorCodes.ErrSystemError {
|
||||
t.Fatalf("expected level-100 pet to be rejected, got err=%d", err)
|
||||
}
|
||||
if result.Exp != 50_000 {
|
||||
t.Fatalf("expected exp pool to remain unchanged, got %d", result.Exp)
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,18 @@ type GetPetLearnableSkillsOutboundInfo struct {
|
||||
SkillList []uint32 `json:"skillList"`
|
||||
}
|
||||
|
||||
func isSameUint32Slice(a []uint32, b []uint32) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for index := range a {
|
||||
if a[index] != b[index] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func collectPetLearnableSkillList(currentPet *model.PetInfo) []uint32 {
|
||||
skillSet := make(map[uint32]struct{})
|
||||
skills := make([]uint32, 0)
|
||||
@@ -55,8 +67,9 @@ func (h Controller) GetPetLearnableSkills(
|
||||
data *GetPetLearnableSkillsInboundInfo,
|
||||
c *player.Player,
|
||||
) (result *GetPetLearnableSkillsOutboundInfo, err errorcode.ErrorCode) {
|
||||
_, currentPet, ok := c.FindPet(data.CatchTime)
|
||||
if !ok {
|
||||
slot, ok := c.FindPetBagSlot(data.CatchTime)
|
||||
currentPet := slot.PetInfoPtr()
|
||||
if !ok || currentPet == nil {
|
||||
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||
}
|
||||
|
||||
@@ -69,8 +82,9 @@ func (h Controller) GetPetLearnableSkills(
|
||||
func (h Controller) SetPetSkill(data *ChangeSkillInfo, c *player.Player) (result *pet.ChangeSkillOutInfo, err errorcode.ErrorCode) {
|
||||
const setSkillCost = 50
|
||||
|
||||
_, currentPet, ok := c.FindPet(data.CatchTime)
|
||||
if !ok {
|
||||
slot, ok := c.FindPetBagSlot(data.CatchTime)
|
||||
currentPet := slot.PetInfoPtr()
|
||||
if !ok || currentPet == nil {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemBusy
|
||||
}
|
||||
|
||||
@@ -135,8 +149,9 @@ func (h Controller) SetPetSkill(data *ChangeSkillInfo, c *player.Player) (result
|
||||
func (h Controller) SortPetSkills(data *C2S_Skill_Sort, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
const skillSortCost = 50
|
||||
|
||||
_, currentPet, ok := c.FindPet(data.CapTm)
|
||||
if !ok {
|
||||
slot, ok := c.FindPetBagSlot(data.CapTm)
|
||||
currentPet := slot.PetInfoPtr()
|
||||
if !ok || currentPet == nil {
|
||||
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||
}
|
||||
|
||||
@@ -184,3 +199,89 @@ func (h Controller) SortPetSkills(data *C2S_Skill_Sort, c *player.Player) (resul
|
||||
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
// CommitPetSkills 按最终技能列表一次性提交学习/替换/排序结果。
|
||||
func (h Controller) CommitPetSkills(
|
||||
data *CommitPetSkillsInboundInfo,
|
||||
c *player.Player,
|
||||
) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
const setSkillCost = 50
|
||||
const skillSortCost = 50
|
||||
|
||||
slot, ok := c.FindPetBagSlot(data.CatchTime)
|
||||
currentPet := slot.PetInfoPtr()
|
||||
if !ok || currentPet == nil {
|
||||
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||
}
|
||||
|
||||
currentSkillSet := make(map[uint32]model.SkillInfo, len(currentPet.SkillList))
|
||||
currentSkillOrder := make([]uint32, 0, len(currentPet.SkillList))
|
||||
for _, skill := range currentPet.SkillList {
|
||||
if skill.ID == 0 {
|
||||
continue
|
||||
}
|
||||
currentSkillSet[skill.ID] = skill
|
||||
currentSkillOrder = append(currentSkillOrder, skill.ID)
|
||||
}
|
||||
|
||||
finalSkillIDs := make([]uint32, 0, 4)
|
||||
usedSkillSet := make(map[uint32]struct{}, 4)
|
||||
for _, skillID := range data.Skill {
|
||||
if skillID == 0 {
|
||||
continue
|
||||
}
|
||||
if _, exists := usedSkillSet[skillID]; exists {
|
||||
continue
|
||||
}
|
||||
usedSkillSet[skillID] = struct{}{}
|
||||
finalSkillIDs = append(finalSkillIDs, skillID)
|
||||
}
|
||||
|
||||
if len(finalSkillIDs) == 0 {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemBusy
|
||||
}
|
||||
if len(finalSkillIDs) > 4 {
|
||||
finalSkillIDs = finalSkillIDs[:4]
|
||||
}
|
||||
if isSameUint32Slice(currentSkillOrder, finalSkillIDs) {
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
learnableSkillSet := make(map[uint32]struct{})
|
||||
for _, skillID := range collectPetLearnableSkillList(currentPet) {
|
||||
learnableSkillSet[skillID] = struct{}{}
|
||||
}
|
||||
|
||||
newSkillCount := 0
|
||||
finalSkillList := make([]model.SkillInfo, 0, len(finalSkillIDs))
|
||||
for _, skillID := range finalSkillIDs {
|
||||
if skill, exists := currentSkillSet[skillID]; exists {
|
||||
finalSkillList = append(finalSkillList, skill)
|
||||
continue
|
||||
}
|
||||
if _, exists := learnableSkillSet[skillID]; !exists {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemBusy
|
||||
}
|
||||
skillInfo, exists := xmlres.SkillMap[int(skillID)]
|
||||
if !exists {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemBusy
|
||||
}
|
||||
newSkillCount++
|
||||
finalSkillList = append(finalSkillList, model.SkillInfo{
|
||||
ID: skillID,
|
||||
PP: uint32(skillInfo.MaxPP),
|
||||
})
|
||||
}
|
||||
|
||||
totalCost := int64(newSkillCount * setSkillCost)
|
||||
if newSkillCount == 0 {
|
||||
totalCost += int64(skillSortCost)
|
||||
}
|
||||
if totalCost > 0 && !c.GetCoins(totalCost) {
|
||||
return nil, errorcode.ErrorCodes.ErrSunDouInsufficient10016
|
||||
}
|
||||
c.Info.Coins -= totalCost
|
||||
currentPet.SkillList = finalSkillList
|
||||
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
@@ -17,15 +17,13 @@ func (h Controller) IsCollect(
|
||||
ID: data.Type,
|
||||
}
|
||||
|
||||
c.Service.Task.Exec(uint32(1335), func(te *model.Task) bool {
|
||||
|
||||
r := bitset32.From(te.Data)
|
||||
// 分支未完成时,标记完成并发放奖励
|
||||
taskData, taskErr := c.Service.Task.GetTask(uint32(1335))
|
||||
if taskErr == nil {
|
||||
r := bitset32.From(taskData.Data)
|
||||
if r.Test(uint(data.Type)) {
|
||||
result.IsCom = 1
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
_, ok := lo.Find([]uint32{1, 2, 3, 4, 301}, func(item uint32) bool {
|
||||
return data.Type == item
|
||||
@@ -59,14 +57,17 @@ func (h Controller) Collect(
|
||||
return data.Type == item
|
||||
})
|
||||
if res == model.Completed && ok { //这块是为了兼容旧版本
|
||||
c.Service.Task.Exec(uint32(1335), func(te *model.Task) bool {
|
||||
taskData, taskErr := c.Service.Task.GetTask(uint32(1335))
|
||||
if taskErr != nil {
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||
}
|
||||
|
||||
r := bitset32.From(te.Data)
|
||||
|
||||
r.Set(uint(data.Type))
|
||||
te.Data = r.Bytes()
|
||||
return true
|
||||
})
|
||||
r := bitset32.From(taskData.Data)
|
||||
r.Set(uint(data.Type))
|
||||
taskData.Data = r.Bytes()
|
||||
if taskErr = c.Service.Task.SetTask(taskData); taskErr != nil {
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||
}
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||
}
|
||||
|
||||
@@ -80,21 +81,22 @@ func (h Controller) Collect(
|
||||
if !lo.Contains(validIDs, data.ID) {
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||
}
|
||||
c.Service.Task.Exec(uint32(1335), func(te *model.Task) bool {
|
||||
taskData, taskErr := c.Service.Task.GetTask(uint32(1335))
|
||||
if taskErr != nil {
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||
}
|
||||
|
||||
r := bitset32.From(te.Data)
|
||||
// 分支未完成时,标记完成并发放奖励
|
||||
if !r.Test(uint(data.Type)) {
|
||||
r.Set(uint(data.Type))
|
||||
te.Data = r.Bytes()
|
||||
r := model.GenPetInfo(int(data.ID), -1, -1, 0, 1, nil, 0)
|
||||
c.Service.Pet.PetAdd(r, 0)
|
||||
result.CatchTime = r.CatchTime
|
||||
|
||||
return true
|
||||
r := bitset32.From(taskData.Data)
|
||||
if !r.Test(uint(data.Type)) {
|
||||
r.Set(uint(data.Type))
|
||||
taskData.Data = r.Bytes()
|
||||
if taskErr = c.Service.Task.SetTask(taskData); taskErr != nil {
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||
}
|
||||
return false
|
||||
})
|
||||
petInfo := model.GenPetInfo(int(data.ID), -1, -1, 0, 1, nil, 0)
|
||||
c.Service.Pet.PetAdd(petInfo, 0)
|
||||
result.CatchTime = petInfo.CatchTime
|
||||
}
|
||||
|
||||
if result.CatchTime == 0 {
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrAwardAlreadyClaimed)
|
||||
|
||||
@@ -77,31 +77,16 @@ func (h Controller) CompleteTask(data1 *CompleteTaskInboundInfo, c *player.Playe
|
||||
// if service.NewTaskService().IsAcceptable(data1.TaskId) == nil {
|
||||
// return nil, errorcode.ErrorCodes.ErrSystemError
|
||||
// }
|
||||
c.Info.SetTask(int(data1.TaskId), model.Completed)
|
||||
|
||||
result = &task.CompleteTaskOutboundInfo{
|
||||
TaskId: data1.TaskId,
|
||||
ItemList: make([]data.ItemInfo, 0),
|
||||
}
|
||||
|
||||
taskInfo := task.GetTaskInfo(int(data1.TaskId), int(data1.OutState))
|
||||
if taskInfo == nil {
|
||||
return nil, errorcode.ErrorCodes.ErrNeedCompleteTaskForPrize
|
||||
if _, err = c.ApplyTaskCompletion(data1.TaskId, int(data1.OutState), result); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if taskInfo.Pet != nil {
|
||||
|
||||
c.Service.Pet.PetAdd(taskInfo.Pet, 0)
|
||||
result.CaptureTime = taskInfo.Pet.CatchTime
|
||||
result.PetTypeId = taskInfo.Pet.ID
|
||||
}
|
||||
|
||||
for _, item := range taskInfo.ItemList {
|
||||
success := c.ItemAdd(item.ItemId, item.ItemCnt)
|
||||
if success {
|
||||
result.ItemList = append(result.ItemList, item)
|
||||
}
|
||||
|
||||
if taskErr := c.Info.SetTask(int(data1.TaskId), model.Completed); taskErr != nil {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemError
|
||||
}
|
||||
|
||||
return result, 0 //通过PUB/SUB回包
|
||||
|
||||
@@ -52,7 +52,7 @@ func PprofWeb() {
|
||||
}
|
||||
|
||||
// 所有端口都失败时的兜底
|
||||
errMsg := fmt.Sprintf("[FATAL] 端口9909/9910均监听失败,pprof服务启动失败")
|
||||
errMsg := "[FATAL] 端口9909/9910均监听失败,pprof服务启动失败"
|
||||
fmt.Println(errMsg)
|
||||
// 可选:根据业务需求决定是否panic
|
||||
// panic(errMsg)
|
||||
@@ -148,7 +148,7 @@ func monitorMemAndQuit() {
|
||||
|
||||
// 4. 超70%阈值,执行优雅退出
|
||||
if usedRatio >= memThresholdRatio {
|
||||
log.Fatalf("内存占比达%.1f%%,超过90%阈值,程序开始退出", usedRatio*100)
|
||||
log.Fatalf("内存占比达%.1f%%,超过90%%阈值,程序开始退出", usedRatio*100)
|
||||
// ########## 可选:这里添加你的优雅清理逻辑 ##########
|
||||
// 如:关闭数据库连接、释放文件句柄、保存业务状态、推送退出告警等
|
||||
cleanup()
|
||||
|
||||
@@ -44,6 +44,7 @@ func (f *FightC) openActionWindow() {
|
||||
f.actionMu.Lock()
|
||||
f.acceptActions = true
|
||||
f.pendingActions = f.pendingActions[:0]
|
||||
f.pendingHead = 0
|
||||
f.actionRound.Store(uint32(f.Round))
|
||||
f.actionMu.Unlock()
|
||||
}
|
||||
@@ -52,6 +53,7 @@ func (f *FightC) closeActionWindow() {
|
||||
f.actionMu.Lock()
|
||||
f.acceptActions = false
|
||||
f.pendingActions = f.pendingActions[:0]
|
||||
f.pendingHead = 0
|
||||
f.actionRound.Store(0)
|
||||
f.actionMu.Unlock()
|
||||
|
||||
@@ -73,8 +75,10 @@ func (f *FightC) submitAction(act action.BattleActionI) {
|
||||
f.actionMu.Unlock()
|
||||
return
|
||||
}
|
||||
f.compactPendingActionsLocked()
|
||||
replaceIndex := -1
|
||||
for i, pending := range f.pendingActions {
|
||||
for i := f.pendingHead; i < len(f.pendingActions); i++ {
|
||||
pending := f.pendingActions[i]
|
||||
if pending == nil || actionSlotKeyFromAction(pending) != actionSlotKeyFromAction(act) {
|
||||
continue
|
||||
}
|
||||
@@ -105,15 +109,23 @@ func (f *FightC) submitAction(act action.BattleActionI) {
|
||||
|
||||
func (f *FightC) nextAction() action.BattleActionI {
|
||||
f.actionMu.Lock()
|
||||
if len(f.pendingActions) == 0 {
|
||||
if f.pendingHead >= len(f.pendingActions) {
|
||||
f.pendingActions = f.pendingActions[:0]
|
||||
f.pendingHead = 0
|
||||
f.actionMu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
act := f.pendingActions[0]
|
||||
copy(f.pendingActions, f.pendingActions[1:])
|
||||
f.pendingActions = f.pendingActions[:len(f.pendingActions)-1]
|
||||
hasMore := len(f.pendingActions) > 0
|
||||
act := f.pendingActions[f.pendingHead]
|
||||
f.pendingActions[f.pendingHead] = nil
|
||||
f.pendingHead++
|
||||
hasMore := f.pendingHead < len(f.pendingActions)
|
||||
if !hasMore {
|
||||
f.pendingActions = f.pendingActions[:0]
|
||||
f.pendingHead = 0
|
||||
} else {
|
||||
f.compactPendingActionsLocked()
|
||||
}
|
||||
notify := f.actionNotify
|
||||
f.actionMu.Unlock()
|
||||
|
||||
@@ -127,6 +139,22 @@ func (f *FightC) nextAction() action.BattleActionI {
|
||||
return act
|
||||
}
|
||||
|
||||
func (f *FightC) compactPendingActionsLocked() {
|
||||
if f.pendingHead == 0 {
|
||||
return
|
||||
}
|
||||
if f.pendingHead < len(f.pendingActions)/2 && len(f.pendingActions) < cap(f.pendingActions) {
|
||||
return
|
||||
}
|
||||
remaining := len(f.pendingActions) - f.pendingHead
|
||||
copy(f.pendingActions, f.pendingActions[f.pendingHead:])
|
||||
for i := remaining; i < len(f.pendingActions); i++ {
|
||||
f.pendingActions[i] = nil
|
||||
}
|
||||
f.pendingActions = f.pendingActions[:remaining]
|
||||
f.pendingHead = 0
|
||||
}
|
||||
|
||||
// 玩家逃跑/无响应/掉线
|
||||
func (f *FightC) Over(c common.PlayerI, res model.EnumBattleOverReason) {
|
||||
if f.closefight {
|
||||
@@ -147,7 +175,7 @@ func (f *FightC) Over(c common.PlayerI, res model.EnumBattleOverReason) {
|
||||
// }
|
||||
|
||||
f.overl.Do(func() {
|
||||
f.Reason = res
|
||||
f.Reason = normalizeFightOverReason(res)
|
||||
if f.GetInputByPlayer(c, true) != nil {
|
||||
f.WinnerId = f.GetInputByPlayer(c, true).UserID
|
||||
}
|
||||
@@ -342,7 +370,7 @@ func (f *FightC) collectFightPetInfos(inputs []*input.Input) []info.FightPetInfo
|
||||
Hp: currentPet.Info.Hp,
|
||||
MaxHp: currentPet.Info.MaxHp,
|
||||
Level: currentPet.Info.Level,
|
||||
Catchable: uint32(fighter.CanCapture),
|
||||
Catchable: fightPetCatchableFlag(fighter.CanCapture),
|
||||
}
|
||||
if fighter.AttackValue != nil {
|
||||
fightInfo.Prop = fighter.AttackValue.Prop
|
||||
@@ -352,6 +380,13 @@ func (f *FightC) collectFightPetInfos(inputs []*input.Input) []info.FightPetInfo
|
||||
return infos
|
||||
}
|
||||
|
||||
func fightPetCatchableFlag(catchRate int) uint32 {
|
||||
if catchRate > 0 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// checkBothPlayersReady 检查PVP战斗中双方是否都已准备完成
|
||||
// 参数c为当前准备的玩家,返回true表示双方均准备完成
|
||||
func (f *FightC) checkBothPlayersReady(currentPlayer common.PlayerI) bool {
|
||||
|
||||
@@ -140,7 +140,7 @@ func (e *Effect1181) OnSkill() bool {
|
||||
type Effect1182 struct{ node.EffectNode }
|
||||
|
||||
func (e *Effect1182) Skill_Use() bool {
|
||||
if len(e.Args()) < 2 || e.Ctx().Our == nil || e.Ctx().Our.CurPet[0] == nil || e.Ctx().Opp == nil || e.Ctx().Opp.CurPet[0] == nil {
|
||||
if len(e.Args()) < 2 || e.Ctx().Our == nil || e.Ctx().Our.CurPet[0] == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -153,9 +153,15 @@ func (e *Effect1182) Skill_Use() bool {
|
||||
if targetHP.Cmp(alpacadecimal.Zero) < 0 {
|
||||
targetHP = alpacadecimal.Zero
|
||||
}
|
||||
if e.Ctx().Opp.CurPet[0].GetHP().Cmp(targetHP) > 0 {
|
||||
e.Ctx().Opp.CurPet[0].Info.Hp = uint32(targetHP.IntPart())
|
||||
}
|
||||
forEachEnemyTargetBySkill(e.Ctx().Our, e.Ctx().Opp, e.Ctx().SkillEntity, func(target *input.Input) bool {
|
||||
if target == nil || target.CurrentPet() == nil {
|
||||
return true
|
||||
}
|
||||
if target.CurrentPet().GetHP().Cmp(targetHP) > 0 {
|
||||
target.CurrentPet().Info.Hp = uint32(targetHP.IntPart())
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
sub := e.Ctx().Our.InitEffect(input.EffectType.Sub, 1182, int(e.Args()[1].IntPart()))
|
||||
if sub != nil {
|
||||
|
||||
@@ -10,20 +10,23 @@ type Effect169 struct {
|
||||
}
|
||||
|
||||
func (e *Effect169) OnSkill() bool {
|
||||
|
||||
chance := e.Args()[1].IntPart()
|
||||
success, _, _ := e.Input.Player.Roll(int(chance), 100)
|
||||
if success {
|
||||
// 添加异常状态
|
||||
statusEffect := e.CarrierInput().InitEffect(input.EffectType.Status, int(e.Args()[2].IntPart())) // 以麻痹为例
|
||||
if statusEffect != nil {
|
||||
e.TargetInput().AddEffect(e.CarrierInput(), statusEffect)
|
||||
}
|
||||
e.ForEachOpponentSlot(func(target *input.Input) bool {
|
||||
if target == nil {
|
||||
return true
|
||||
}
|
||||
statusEffect := e.CarrierInput().InitEffect(input.EffectType.Status, int(e.Args()[2].IntPart()))
|
||||
if statusEffect != nil {
|
||||
target.AddEffect(e.CarrierInput(), statusEffect)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func init() {
|
||||
input.InitEffect(input.EffectType.Skill, 169, &Effect169{})
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package effect
|
||||
|
||||
import (
|
||||
"blazing/logic/service/fight/input"
|
||||
"blazing/logic/service/fight/node"
|
||||
)
|
||||
|
||||
@@ -41,14 +42,16 @@ type Effect5 struct {
|
||||
// 技能触发时调用
|
||||
// -----------------------------------------------------------
|
||||
func (e *Effect5) Skill_Use() bool {
|
||||
|
||||
// 概率判定
|
||||
ok, _, _ := e.Input.Player.Roll(e.SideEffectArgs[1], 100)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
e.Ctx().Opp.SetProp(e.Ctx().Our, int8(e.SideEffectArgs[0]), int8(e.SideEffectArgs[2]))
|
||||
forEachEnemyTargetBySkill(e.Ctx().Our, e.Ctx().Opp, e.Ctx().SkillEntity, func(target *input.Input) bool {
|
||||
target.SetProp(e.Ctx().Our, int8(e.SideEffectArgs[0]), int8(e.SideEffectArgs[2]))
|
||||
return true
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -22,15 +22,19 @@ type Effect76 struct {
|
||||
}
|
||||
|
||||
func (e *Effect76) OnSkill() bool {
|
||||
|
||||
// 概率判定
|
||||
ok, _, _ := e.Input.Player.Roll(int(e.Args()[0].IntPart()), 100)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{
|
||||
Type: info.DamageType.Fixed,
|
||||
Damage: alpacadecimal.NewFromInt(int64(e.SideEffectArgs[2])),
|
||||
|
||||
damage := alpacadecimal.NewFromInt(int64(e.SideEffectArgs[2]))
|
||||
forEachEnemyTargetBySkill(e.Ctx().Our, e.Ctx().Opp, e.Ctx().SkillEntity, func(target *input.Input) bool {
|
||||
target.Damage(e.Ctx().Our, &info.DamageZone{
|
||||
Type: info.DamageType.Fixed,
|
||||
Damage: damage,
|
||||
})
|
||||
return true
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ var effectInfoByID = map[int]string{
|
||||
29: "额外附加{0}点固定伤害",
|
||||
31: "",
|
||||
32: "使用后{0}回合攻击击中对象要害概率增加1/16",
|
||||
33: "消除对手能力提升状态",
|
||||
33: "消除敌方阵营所有强化",
|
||||
34: "将所受的伤害{0}倍反馈给对手",
|
||||
35: "惩罚,对方能力等级越高,此技能威力越大",
|
||||
36: "命中时{0}%的概率秒杀对方",
|
||||
@@ -120,7 +120,7 @@ var effectInfoByID = map[int]string{
|
||||
164: "{0}回合内若受到攻击则有{1}%概率令对手{2}",
|
||||
165: "{0}回合内每回合防御和特防等级+{1}",
|
||||
166: "{0}回合内若对手使用属性攻击则{2}%对手{1}等级{3}",
|
||||
169: "{0}回合内每回合额外附加{1}%概率令对手{2}",
|
||||
169: "{0}回合内每回合额外附加{1}%概率令对方阵营全体{2}",
|
||||
170: "若先出手,则免疫当回合伤害并回复1/{0}的最大体力值",
|
||||
171: "{0}回合内自身使用属性技能时能较快出手",
|
||||
172: "若后出手,则给予对方损伤的1/{0}会回复自己的体力",
|
||||
|
||||
@@ -27,7 +27,7 @@ func (e *Effect3) Skill_Use() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Effect 33: 消除对手能力提升状态
|
||||
// Effect 33: 消除敌方阵营所有强化
|
||||
type Effect33 struct {
|
||||
node.EffectNode
|
||||
Reverse bool
|
||||
@@ -38,13 +38,17 @@ type Effect33 struct {
|
||||
// 执行时逻辑
|
||||
// ----------------------
|
||||
func (e *Effect33) Skill_Use() bool {
|
||||
|
||||
for i, v := range e.Ctx().Opp.Prop[:] {
|
||||
if v > 0 {
|
||||
e.Ctx().Opp.SetProp(e.Ctx().Our, int8(i), 0)
|
||||
e.ForEachOpponentSlot(func(target *input.Input) bool {
|
||||
if target == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
for i, v := range target.Prop[:] {
|
||||
if v > 0 {
|
||||
target.SetProp(e.Ctx().Our, int8(i), 0)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -54,8 +58,8 @@ func (e *Effect33) Skill_Use() bool {
|
||||
// ----------------------
|
||||
func init() {
|
||||
// {3, false, 0}, // 解除自身能力下降状态
|
||||
// {33, true, 0}, // 消除对手能力提升状态{3, false, 0}, // 解除自身能力下降状态
|
||||
// {33, true, 0}, // 消除对手能力提升状态
|
||||
// {33, true, 0}, // 消除敌方阵营所有强化{3, false, 0}, // 解除自身能力下降状态
|
||||
// {33, true, 0}, // 消除敌方阵营所有强化
|
||||
input.InitEffect(input.EffectType.Skill, 3, &Effect3{})
|
||||
input.InitEffect(input.EffectType.Skill, 33, &Effect33{})
|
||||
}
|
||||
|
||||
@@ -36,43 +36,73 @@ 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
|
||||
e.StatusCannotAct.TurnEnd()
|
||||
}
|
||||
|
||||
// 持续伤害状态基类(中毒、冻伤、烧伤等)
|
||||
type ContinuousDamage struct {
|
||||
BaseStatus
|
||||
isheal bool //是否回血
|
||||
}
|
||||
|
||||
// 技能命中前触发伤害(1/8最大生命值真实伤害)
|
||||
func (e *ContinuousDamage) ActionStart(attacker, defender *action.SelectSkillAction) bool {
|
||||
// 回合开始触发持续伤害,保证吃药/空过回合时也会正常结算。
|
||||
func (e *ContinuousDamage) TurnStart(attacker, defender *action.SelectSkillAction) {
|
||||
carrier := e.CarrierInput()
|
||||
source := e.SourceInput()
|
||||
opp := e.TargetInput()
|
||||
if carrier == nil {
|
||||
return true
|
||||
return
|
||||
}
|
||||
damage := e.calculateDamage()
|
||||
|
||||
@@ -81,7 +111,7 @@ func (e *ContinuousDamage) ActionStart(attacker, defender *action.SelectSkillAct
|
||||
Damage: damage,
|
||||
})
|
||||
if len(e.SideEffectArgs) == 0 {
|
||||
return true
|
||||
return
|
||||
}
|
||||
// 额外效果
|
||||
carrier.Damage(source, &info.DamageZone{
|
||||
@@ -89,12 +119,11 @@ func (e *ContinuousDamage) ActionStart(attacker, defender *action.SelectSkillAct
|
||||
Damage: damage,
|
||||
})
|
||||
if opp == nil || opp.CurPet[0].GetHP().IntPart() == 0 {
|
||||
return true
|
||||
return
|
||||
}
|
||||
|
||||
// 给对方回血(不受回血限制影响)
|
||||
opp.Heal(carrier, nil, damage)
|
||||
return true
|
||||
}
|
||||
|
||||
// 计算伤害:最大生命值的1/8
|
||||
@@ -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{})
|
||||
}
|
||||
|
||||
@@ -83,6 +83,10 @@ func (e *Effect201) OnSkill() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
if !carrier.IsMultiInputBattle() {
|
||||
return true
|
||||
}
|
||||
|
||||
divisorIndex := len(args) - 1
|
||||
if len(args) > 1 {
|
||||
divisorIndex = 1
|
||||
|
||||
91
logic/service/fight/effect/none_test.go
Normal file
91
logic/service/fight/effect/none_test.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package effect
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
fightinfo "blazing/logic/service/fight/info"
|
||||
"blazing/logic/service/fight/input"
|
||||
"blazing/modules/player/model"
|
||||
)
|
||||
|
||||
func newEffect201TestInput(hp, maxHP uint32) *input.Input {
|
||||
in := &input.Input{
|
||||
CurPet: []*fightinfo.BattlePetEntity{{
|
||||
Info: model.PetInfo{
|
||||
Hp: hp,
|
||||
MaxHp: maxHP,
|
||||
},
|
||||
}},
|
||||
}
|
||||
in.AttackValue = fightinfo.NewAttackValue(0)
|
||||
return in
|
||||
}
|
||||
|
||||
func TestEffect201HealAllIgnoredInSingleInputBattle(t *testing.T) {
|
||||
carrier := newEffect201TestInput(40, 100)
|
||||
opponent := newEffect201TestInput(60, 100)
|
||||
carrier.Team = []*input.Input{carrier}
|
||||
carrier.OppTeam = []*input.Input{opponent}
|
||||
|
||||
eff := &Effect201{}
|
||||
eff.SetArgs(carrier, 1, 2)
|
||||
eff.EffectNode.EffectContextHolder.Ctx = input.Ctx{
|
||||
LegacySides: input.LegacySides{Our: carrier, Opp: opponent},
|
||||
EffectBinding: input.EffectBinding{Carrier: carrier, Source: carrier},
|
||||
}
|
||||
|
||||
if !eff.OnSkill() {
|
||||
t.Fatalf("expected effect to finish successfully")
|
||||
}
|
||||
if got := carrier.CurrentPet().Info.Hp; got != 40 {
|
||||
t.Fatalf("expected single-input full-team heal to be ignored, got hp %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEffect201SingleTargetIgnoredInSingleInputBattle(t *testing.T) {
|
||||
carrier := newEffect201TestInput(40, 100)
|
||||
opponent := newEffect201TestInput(60, 100)
|
||||
carrier.Team = []*input.Input{carrier}
|
||||
carrier.OppTeam = []*input.Input{opponent}
|
||||
|
||||
eff := &Effect201{}
|
||||
eff.SetArgs(carrier, 2)
|
||||
eff.EffectNode.EffectContextHolder.Ctx = input.Ctx{
|
||||
LegacySides: input.LegacySides{Our: carrier, Opp: opponent},
|
||||
EffectBinding: input.EffectBinding{Carrier: carrier, Source: carrier},
|
||||
}
|
||||
|
||||
if !eff.OnSkill() {
|
||||
t.Fatalf("expected effect to finish successfully")
|
||||
}
|
||||
if got := carrier.CurrentPet().Info.Hp; got != 40 {
|
||||
t.Fatalf("expected single-input single-target heal to be ignored, got hp %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEffect201HealAllWorksInMultiInputBattle(t *testing.T) {
|
||||
carrier := newEffect201TestInput(40, 100)
|
||||
ally := newEffect201TestInput(10, 80)
|
||||
opponent := newEffect201TestInput(60, 100)
|
||||
carrier.Team = []*input.Input{carrier, ally}
|
||||
carrier.OppTeam = []*input.Input{opponent}
|
||||
ally.Team = carrier.Team
|
||||
ally.OppTeam = carrier.OppTeam
|
||||
|
||||
eff := &Effect201{}
|
||||
eff.SetArgs(carrier, 1, 2)
|
||||
eff.EffectNode.EffectContextHolder.Ctx = input.Ctx{
|
||||
LegacySides: input.LegacySides{Our: carrier, Opp: opponent},
|
||||
EffectBinding: input.EffectBinding{Carrier: carrier, Source: carrier},
|
||||
}
|
||||
|
||||
if !eff.OnSkill() {
|
||||
t.Fatalf("expected effect to finish successfully")
|
||||
}
|
||||
if got := carrier.CurrentPet().Info.Hp; got != 90 {
|
||||
t.Fatalf("expected carrier hp 90 after full-team heal, got %d", got)
|
||||
}
|
||||
if got := ally.CurrentPet().Info.Hp; got != 50 {
|
||||
t.Fatalf("expected ally hp 50 after full-team heal, got %d", got)
|
||||
}
|
||||
}
|
||||
@@ -5,3 +5,7 @@ import "blazing/logic/service/fight/input"
|
||||
func initskill(id int, e input.Effect) {
|
||||
input.InitEffect(input.EffectType.Skill, id, e)
|
||||
}
|
||||
|
||||
func initskillFactory(id int, factory func() input.Effect) {
|
||||
input.InitEffectFactory(input.EffectType.Skill, id, factory)
|
||||
}
|
||||
|
||||
@@ -158,7 +158,10 @@ func registerSelfDamageSkillHitEffects() {
|
||||
}
|
||||
|
||||
for effectID, handler := range handlers {
|
||||
initskill(effectID, newSkillHitRegistrarEffect(handler))
|
||||
currentHandler := handler
|
||||
initskillFactory(effectID, func() input.Effect {
|
||||
return newSkillHitRegistrarEffect(currentHandler)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,9 +207,15 @@ func registerSelfDamageOnSkillEffects() {
|
||||
})
|
||||
}
|
||||
|
||||
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{
|
||||
Type: info.DamageType.Fixed,
|
||||
Damage: opponentDamage,
|
||||
forEachEnemyTargetBySkill(e.Ctx().Our, e.Ctx().Opp, e.Ctx().SkillEntity, func(target *input.Input) bool {
|
||||
if target == nil || target.CurrentPet() == nil {
|
||||
return true
|
||||
}
|
||||
target.Damage(e.Ctx().Our, &info.DamageZone{
|
||||
Type: info.DamageType.Fixed,
|
||||
Damage: opponentDamage,
|
||||
})
|
||||
return true
|
||||
})
|
||||
return true
|
||||
},
|
||||
@@ -223,7 +232,10 @@ func registerSelfDamageOnSkillEffects() {
|
||||
}
|
||||
|
||||
for effectID, handler := range handlers {
|
||||
initskill(effectID, newOnSkillRegistrarEffect(handler))
|
||||
currentHandler := handler
|
||||
initskillFactory(effectID, func() input.Effect {
|
||||
return newOnSkillRegistrarEffect(currentHandler)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,9 +247,15 @@ func registerSelfDamageSkillUseEffects() {
|
||||
Type: info.DamageType.Fixed,
|
||||
Damage: damage,
|
||||
})
|
||||
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{
|
||||
Type: info.DamageType.Fixed,
|
||||
Damage: damage,
|
||||
forEachEnemyTargetBySkill(e.Ctx().Our, e.Ctx().Opp, e.Ctx().SkillEntity, func(target *input.Input) bool {
|
||||
if target == nil || target.CurrentPet() == nil {
|
||||
return true
|
||||
}
|
||||
target.Damage(e.Ctx().Our, &info.DamageZone{
|
||||
Type: info.DamageType.Fixed,
|
||||
Damage: damage,
|
||||
})
|
||||
return true
|
||||
})
|
||||
return true
|
||||
},
|
||||
@@ -247,9 +265,23 @@ func registerSelfDamageSkillUseEffects() {
|
||||
Damage: alpacadecimal.NewFromInt(int64(e.Ctx().Our.CurPet[0].Info.MaxHp)),
|
||||
})
|
||||
damage := int64(grand.N(250, 300))
|
||||
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{
|
||||
Type: info.DamageType.Fixed,
|
||||
Damage: alpacadecimal.Min(alpacadecimal.NewFromInt(damage), e.Ctx().Opp.CurPet[0].GetHP().Sub(alpacadecimal.NewFromInt(1))),
|
||||
forEachEnemyTargetBySkill(e.Ctx().Our, e.Ctx().Opp, e.Ctx().SkillEntity, func(target *input.Input) bool {
|
||||
if target == nil {
|
||||
return true
|
||||
}
|
||||
targetPet := target.CurrentPet()
|
||||
if targetPet == nil {
|
||||
return true
|
||||
}
|
||||
remainHP := targetPet.GetHP().Sub(alpacadecimal.NewFromInt(1))
|
||||
if remainHP.Cmp(alpacadecimal.Zero) <= 0 {
|
||||
return true
|
||||
}
|
||||
target.Damage(e.Ctx().Our, &info.DamageZone{
|
||||
Type: info.DamageType.Fixed,
|
||||
Damage: alpacadecimal.Min(alpacadecimal.NewFromInt(damage), remainHP),
|
||||
})
|
||||
return true
|
||||
})
|
||||
return true
|
||||
},
|
||||
@@ -274,15 +306,25 @@ func registerSelfDamageSkillUseEffects() {
|
||||
randomDamage = grand.N(minDamage, maxDamage)
|
||||
}
|
||||
|
||||
remainHP := e.Ctx().Opp.CurPet[0].GetHP().Sub(alpacadecimal.NewFromInt(1))
|
||||
if remainHP.Cmp(alpacadecimal.Zero) <= 0 {
|
||||
return true
|
||||
}
|
||||
forEachEnemyTargetBySkill(e.Ctx().Our, e.Ctx().Opp, e.Ctx().SkillEntity, func(target *input.Input) bool {
|
||||
if target == nil {
|
||||
return true
|
||||
}
|
||||
targetPet := target.CurrentPet()
|
||||
if targetPet == nil {
|
||||
return true
|
||||
}
|
||||
remainHP := targetPet.GetHP().Sub(alpacadecimal.NewFromInt(1))
|
||||
if remainHP.Cmp(alpacadecimal.Zero) <= 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
damage := alpacadecimal.Min(alpacadecimal.NewFromInt(int64(randomDamage)), remainHP)
|
||||
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{
|
||||
Type: info.DamageType.Fixed,
|
||||
Damage: damage,
|
||||
damage := alpacadecimal.Min(alpacadecimal.NewFromInt(int64(randomDamage)), remainHP)
|
||||
target.Damage(e.Ctx().Our, &info.DamageZone{
|
||||
Type: info.DamageType.Fixed,
|
||||
Damage: damage,
|
||||
})
|
||||
return true
|
||||
})
|
||||
return true
|
||||
},
|
||||
@@ -291,11 +333,17 @@ func registerSelfDamageSkillUseEffects() {
|
||||
return true
|
||||
}
|
||||
|
||||
applyAllPropDown(e.Ctx().Our, e.Ctx().Opp, int8(e.Args()[0].IntPart()))
|
||||
sub := e.Ctx().Our.InitEffect(input.EffectType.Sub, 1380, int(e.Args()[1].IntPart()), int(e.Args()[2].IntPart()))
|
||||
if sub != nil {
|
||||
e.Ctx().Opp.AddEffect(e.Ctx().Our, sub)
|
||||
}
|
||||
forEachEnemyTargetBySkill(e.Ctx().Our, e.Ctx().Opp, e.Ctx().SkillEntity, func(target *input.Input) bool {
|
||||
if target == nil || target.CurrentPet() == nil {
|
||||
return true
|
||||
}
|
||||
applyAllPropDown(e.Ctx().Our, target, int8(e.Args()[0].IntPart()))
|
||||
sub := e.Ctx().Our.InitEffect(input.EffectType.Sub, 1380, int(e.Args()[1].IntPart()), int(e.Args()[2].IntPart()))
|
||||
if sub != nil {
|
||||
target.AddEffect(e.Ctx().Our, sub)
|
||||
}
|
||||
return true
|
||||
})
|
||||
e.Ctx().Our.Damage(e.Ctx().Our, &info.DamageZone{
|
||||
Type: info.DamageType.Fixed,
|
||||
Damage: e.Ctx().Our.CurPet[0].GetHP(),
|
||||
@@ -305,7 +353,10 @@ func registerSelfDamageSkillUseEffects() {
|
||||
}
|
||||
|
||||
for effectID, handler := range handlers {
|
||||
initskill(effectID, newSkillUseRegistrarEffect(handler))
|
||||
currentHandler := handler
|
||||
initskillFactory(effectID, func() input.Effect {
|
||||
return newSkillUseRegistrarEffect(currentHandler)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,7 +390,10 @@ func registerSelfDamageComparePreOnSkillEffects() {
|
||||
}
|
||||
|
||||
for effectID, effect := range effects {
|
||||
initskill(effectID, effect)
|
||||
currentEffect := effect
|
||||
initskillFactory(effectID, func() input.Effect {
|
||||
return newComparePreOnSkillRegistrarEffect(currentEffect.comparePreHandler, currentEffect.onSkillHandler)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
34
logic/service/fight/effect/skill_target_helper.go
Normal file
34
logic/service/fight/effect/skill_target_helper.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package effect
|
||||
|
||||
import (
|
||||
"blazing/logic/service/fight/info"
|
||||
"blazing/logic/service/fight/input"
|
||||
)
|
||||
|
||||
// forEachEnemyTargetBySkill 在普通情况下对单个目标生效;
|
||||
// 当技能为 AtkType=3(仅自己)且当前目标仍在己方时,改为遍历敌方全部站位。
|
||||
func forEachEnemyTargetBySkill(carrier, target *input.Input, skill *info.SkillEntity, fn func(*input.Input) bool) {
|
||||
if fn == nil {
|
||||
return
|
||||
}
|
||||
if carrier == nil {
|
||||
if target != nil {
|
||||
fn(target)
|
||||
}
|
||||
return
|
||||
}
|
||||
if skill == nil || skill.XML.AtkType != 3 || !isSameSideTarget(carrier, target) {
|
||||
if target != nil {
|
||||
fn(target)
|
||||
}
|
||||
return
|
||||
}
|
||||
for _, opponent := range carrier.OpponentSlots() {
|
||||
if opponent == nil {
|
||||
continue
|
||||
}
|
||||
if !fn(opponent) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
40
logic/service/fight/fight_over_payload.go
Normal file
40
logic/service/fight/fight_over_payload.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package fight
|
||||
|
||||
import "blazing/modules/player/model"
|
||||
|
||||
// buildFightOverPayload builds the legacy 2506 payload expected by the flash client.
|
||||
// Regular fight-over packets use a different reason mapping than group fight 7560:
|
||||
// 0=normal end 1=player lost/offline 2=overtime 3=draw 4=system error 5=npc escape.
|
||||
func buildFightOverPayload(over model.FightOverInfo) *model.FightOverInfo {
|
||||
payload := over
|
||||
payload.Reason = model.EnumBattleOverReason(mapUnifiedFightOverReason(over.Reason))
|
||||
return &payload
|
||||
}
|
||||
|
||||
func normalizeFightOverReason(reason model.EnumBattleOverReason) model.EnumBattleOverReason {
|
||||
if reason == model.BattleOverReason.DefaultEnd {
|
||||
return 0
|
||||
}
|
||||
return reason
|
||||
}
|
||||
|
||||
func mapUnifiedFightOverReason(reason model.EnumBattleOverReason) uint32 {
|
||||
switch normalizeFightOverReason(reason) {
|
||||
case 0, model.BattleOverReason.Cacthok:
|
||||
return 0
|
||||
case model.BattleOverReason.PlayerOffline:
|
||||
return 1
|
||||
case model.BattleOverReason.PlayerOVerTime:
|
||||
return 2
|
||||
case model.BattleOverReason.NOTwind:
|
||||
return 3
|
||||
case model.BattleOverReason.PlayerEscape:
|
||||
return 5
|
||||
default:
|
||||
return 4
|
||||
}
|
||||
}
|
||||
|
||||
func mapFightOverReasonFor2506(reason model.EnumBattleOverReason) model.EnumBattleOverReason {
|
||||
return model.EnumBattleOverReason(mapUnifiedFightOverReason(reason))
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"blazing/logic/service/fight/action"
|
||||
"blazing/logic/service/fight/info"
|
||||
"blazing/logic/service/fight/input"
|
||||
_ "blazing/logic/service/fight/itemover"
|
||||
_ "blazing/logic/service/fight/rule"
|
||||
"blazing/modules/player/model"
|
||||
"reflect"
|
||||
|
||||
@@ -133,7 +135,20 @@ func (f *FightC) getSkillParticipants(skillAction *action.SelectSkillAction) (*i
|
||||
if skillAction == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return f.GetInputByAction(skillAction, false), f.GetInputByAction(skillAction, true)
|
||||
attacker := f.GetInputByAction(skillAction, false)
|
||||
defender := f.GetInputByAction(skillAction, true)
|
||||
if attacker != nil && defender == attacker && shouldResolveOpponentAsTarget(skillAction.SkillEntity) {
|
||||
if opponent, _ := attacker.OpponentSlotAtOrNextLiving(0); opponent != nil {
|
||||
defender = opponent
|
||||
} else if opponent := f.roundOpponentInput(attacker); opponent != nil {
|
||||
defender = opponent
|
||||
}
|
||||
}
|
||||
return attacker, defender
|
||||
}
|
||||
|
||||
func shouldResolveOpponentAsTarget(skill *info.SkillEntity) bool {
|
||||
return skill != nil && skill.XML.AtkType == 3
|
||||
}
|
||||
|
||||
// setEffectSkillContext 统一设置技能阶段 effect 上下文。
|
||||
@@ -184,20 +199,63 @@ func (f *FightC) collectAttackValues(inputs []*input.Input) []model.AttackValue
|
||||
continue
|
||||
}
|
||||
attackValue := *fighter.AttackValue
|
||||
if attackValue.SkillID == 0 {
|
||||
continue
|
||||
}
|
||||
attackValue.ActorIndex = uint32(actorIndex)
|
||||
values = append(values, attackValue)
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func (f *FightC) buildAttackValueForBroadcast(fighter *input.Input, fallbackActorIndex int) model.AttackValue {
|
||||
if fighter == nil {
|
||||
return model.AttackValue{}
|
||||
}
|
||||
if fighter.AttackValue == nil {
|
||||
empty := info.NewAttackValue(fighter.UserID)
|
||||
fighter.AttackValue = empty
|
||||
}
|
||||
attackValue := *fighter.AttackValue
|
||||
attackValue.ActorIndex = uint32(fallbackActorIndex)
|
||||
if attackValue.UserID == 0 && fighter.Player != nil && fighter.Player.GetInfo() != nil {
|
||||
attackValue.UserID = fighter.Player.GetInfo().UserID
|
||||
}
|
||||
return attackValue
|
||||
}
|
||||
|
||||
func (f *FightC) buildNoteUseSkillOutboundInfo() info.NoteUseSkillOutboundInfo {
|
||||
result := info.NoteUseSkillOutboundInfo{}
|
||||
result.FirstAttackInfo = append(result.FirstAttackInfo, f.collectAttackValues(f.Our)...)
|
||||
result.SecondAttackInfo = append(result.SecondAttackInfo, f.collectAttackValues(f.Opp)...)
|
||||
|
||||
if f.First != nil {
|
||||
result.FirstAttackInfo = f.buildAttackValueForBroadcast(f.First, f.First.TeamSlotIndex())
|
||||
}
|
||||
if f.Second != nil {
|
||||
result.SecondAttackInfo = f.buildAttackValueForBroadcast(f.Second, f.Second.TeamSlotIndex())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (f *FightC) roundOpponentInput(attacker *input.Input) *input.Input {
|
||||
if attacker == nil {
|
||||
return nil
|
||||
}
|
||||
for _, opponent := range attacker.OpponentSlots() {
|
||||
if opponent != nil {
|
||||
return opponent
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func shouldSkipSecondAction(first, second *input.Input) bool {
|
||||
if first == nil || second == nil {
|
||||
return false
|
||||
}
|
||||
firstPet := first.CurrentPet()
|
||||
secondPet := second.CurrentPet()
|
||||
return firstPet == nil || firstPet.Info.Hp <= 0 || secondPet == nil || secondPet.Info.Hp <= 0
|
||||
}
|
||||
|
||||
// enterturn 处理战斗回合逻辑
|
||||
// 回合有先手方和后手方,同时有攻击方和被攻击方
|
||||
func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction) {
|
||||
@@ -245,9 +303,11 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction)
|
||||
f.First, _ = f.getSkillParticipants(firstAttack)
|
||||
f.Second, _ = f.getSkillParticipants(secondAttack)
|
||||
case firstAttack != nil:
|
||||
f.First, f.Second = f.getSkillParticipants(firstAttack)
|
||||
f.First, _ = f.getSkillParticipants(firstAttack)
|
||||
f.Second = f.roundOpponentInput(f.First)
|
||||
case secondAttack != nil:
|
||||
f.First, f.Second = f.getSkillParticipants(secondAttack)
|
||||
f.First, _ = f.getSkillParticipants(secondAttack)
|
||||
f.Second = f.roundOpponentInput(f.First)
|
||||
}
|
||||
if f.First == nil {
|
||||
f.First = f.primaryOur()
|
||||
@@ -275,25 +335,32 @@ 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
|
||||
if i == 0 {
|
||||
currentAction = firstAttack
|
||||
if currentAction == nil {
|
||||
continue
|
||||
}
|
||||
attacker, defender = f.getSkillParticipants(firstAttack)
|
||||
originalSkill = f.copySkill(firstAttack)
|
||||
//先手阶段,先修复后手效果
|
||||
f.Second.RecoverEffect()
|
||||
} else {
|
||||
currentAction = secondAttack
|
||||
if currentAction == nil {
|
||||
continue
|
||||
}
|
||||
if shouldSkipSecondAction(f.First, f.Second) {
|
||||
secondAttack = nil
|
||||
continue
|
||||
}
|
||||
attacker, defender = f.getSkillParticipants(secondAttack)
|
||||
originalSkill = f.copySkill(secondAttack)
|
||||
//取消后手历史效果
|
||||
@@ -332,7 +399,6 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction)
|
||||
|
||||
}
|
||||
//先手权不一定出手
|
||||
|
||||
} else {
|
||||
f.setActionAttackValue(currentAction)
|
||||
|
||||
@@ -449,11 +515,13 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction)
|
||||
// })
|
||||
return
|
||||
}
|
||||
f.BroadcastPlayers(func(p common.PlayerI) {
|
||||
if !f.LegacyGroupProtocol {
|
||||
f.sendFightPacket(p, fightPacketSkillResult, &attackValueResult)
|
||||
}
|
||||
})
|
||||
if attackValueResult.FirstAttackInfo.UserID != 0 || attackValueResult.SecondAttackInfo.UserID != 0 {
|
||||
f.BroadcastPlayers(func(p common.PlayerI) {
|
||||
if !f.LegacyGroupProtocol {
|
||||
f.sendFightPacket(p, fightPacketSkillResult, &attackValueResult)
|
||||
}
|
||||
})
|
||||
}
|
||||
f.Broadcast(func(fighter *input.Input) {
|
||||
fighter.CanChange = 0
|
||||
})
|
||||
@@ -467,12 +535,7 @@ func (f *FightC) TURNOVER(cur *input.Input) {
|
||||
if cur == nil {
|
||||
return
|
||||
}
|
||||
for _, pet := range cur.BenchPets() {
|
||||
if pet != nil && pet.Info.Hp > 0 {
|
||||
_hasBackup = true
|
||||
break
|
||||
}
|
||||
}
|
||||
_hasBackup = cur.HasLivingBench()
|
||||
f.sendLegacySpriteDie(cur, _hasBackup)
|
||||
|
||||
f.Broadcast(func(ff *input.Input) {
|
||||
@@ -487,9 +550,9 @@ func (f *FightC) TURNOVER(cur *input.Input) {
|
||||
if f.IsWin(f.GetInputByPlayer(cur.Player, true)) { //然后检查是否战斗结束
|
||||
|
||||
f.FightOverInfo.WinnerId = f.GetInputByPlayer(cur.Player, true).UserID
|
||||
f.FightOverInfo.Reason = model.BattleOverReason.DefaultEnd
|
||||
f.FightOverInfo.Reason = normalizeFightOverReason(model.BattleOverReason.DefaultEnd)
|
||||
f.WinnerId = f.FightOverInfo.WinnerId
|
||||
f.Reason = model.BattleOverReason.DefaultEnd
|
||||
f.Reason = f.FightOverInfo.Reason
|
||||
|
||||
f.closefight = true
|
||||
// break
|
||||
|
||||
@@ -8,6 +8,11 @@ import (
|
||||
"blazing/modules/player/model"
|
||||
)
|
||||
|
||||
// <!--
|
||||
// GBTL:
|
||||
// 1. AtkNum:本技能同时攻击数量, 默认:1(不能为0)
|
||||
// 2. AtkType:攻击类型: 0:所有人, 1:仅己方, 2:仅对方, 3:仅自己, 默认:2
|
||||
// -->
|
||||
const (
|
||||
groupCmdReadyToFight uint32 = 7555
|
||||
groupCmdReadyFightFinish uint32 = 7556
|
||||
@@ -426,38 +431,15 @@ func (f *FightC) buildLegacyGroupOverInfo(over *model.FightOverInfo) *legacyGrou
|
||||
}
|
||||
|
||||
func mapLegacyGroupFightOverReason(reason model.EnumBattleOverReason) uint32 {
|
||||
switch reason {
|
||||
case model.BattleOverReason.PlayerOffline:
|
||||
return 2
|
||||
case model.BattleOverReason.PlayerOVerTime:
|
||||
return 3
|
||||
case model.BattleOverReason.NOTwind:
|
||||
return 4
|
||||
case model.BattleOverReason.DefaultEnd:
|
||||
return 1
|
||||
case model.BattleOverReason.PlayerEscape:
|
||||
return 6
|
||||
default:
|
||||
return 5
|
||||
}
|
||||
return mapUnifiedFightOverReason(reason)
|
||||
}
|
||||
|
||||
func resolveLegacyGroupFightOverReason(over *model.FightOverInfo) uint32 {
|
||||
if over == nil {
|
||||
return 5
|
||||
}
|
||||
switch over.Reason {
|
||||
case model.BattleOverReason.PlayerOffline:
|
||||
return 2
|
||||
case model.BattleOverReason.PlayerOVerTime:
|
||||
return 3
|
||||
case model.BattleOverReason.PlayerEscape:
|
||||
return 6
|
||||
case model.BattleOverReason.NOTwind:
|
||||
return 4
|
||||
return mapUnifiedFightOverReason(0)
|
||||
}
|
||||
if over.WinnerId != 0 {
|
||||
return 1
|
||||
return mapUnifiedFightOverReason(0)
|
||||
}
|
||||
return mapLegacyGroupFightOverReason(over.Reason)
|
||||
}
|
||||
@@ -515,15 +497,23 @@ func (f *FightC) sendLegacyRoundBroadcast(firstAttack, secondAttack *action.Sele
|
||||
if f == nil || !f.LegacyGroupProtocol {
|
||||
return
|
||||
}
|
||||
if firstAttack != nil {
|
||||
if f.legacySkillExecuted(firstAttack) {
|
||||
f.sendLegacyGroupSkillHurt(firstAttack)
|
||||
}
|
||||
if secondAttack != nil {
|
||||
if f.legacySkillExecuted(secondAttack) {
|
||||
f.sendLegacyGroupSkillHurt(secondAttack)
|
||||
}
|
||||
f.sendLegacyGroupBoutDone()
|
||||
}
|
||||
|
||||
func (f *FightC) legacySkillExecuted(skillAction *action.SelectSkillAction) bool {
|
||||
if f == nil || skillAction == nil {
|
||||
return false
|
||||
}
|
||||
attacker := f.GetInputByAction(skillAction, false)
|
||||
return attacker != nil && attacker.AttackValue != nil && attacker.AttackValue.SkillID != 0
|
||||
}
|
||||
|
||||
func (f *FightC) sendLegacyGroupSkillHurt(skillAction *action.SelectSkillAction) {
|
||||
if f == nil || !f.LegacyGroupProtocol || skillAction == nil {
|
||||
return
|
||||
@@ -603,10 +593,10 @@ func (f *FightC) buildLegacyGroupSkillAttackInfo(skillAction *action.SelectSkill
|
||||
if attackValue == nil {
|
||||
attackValue = info.NewAttackValue(self.UserID)
|
||||
}
|
||||
if skillAction != nil && skillAction.SkillEntity != nil {
|
||||
result.MoveID = uint32(skillAction.SkillEntity.XML.ID)
|
||||
} else {
|
||||
if attackValue.SkillID != 0 {
|
||||
result.MoveID = attackValue.SkillID
|
||||
} else if skillAction != nil && skillAction.SkillEntity != nil {
|
||||
result.MoveID = uint32(skillAction.SkillEntity.XML.ID)
|
||||
}
|
||||
result.IsCrit = attackValue.IsCritical
|
||||
result.EffectName = attackValue.State
|
||||
|
||||
61
logic/service/fight/group_legacy_test.go
Normal file
61
logic/service/fight/group_legacy_test.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package fight
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"blazing/logic/service/fight/action"
|
||||
fightinfo "blazing/logic/service/fight/info"
|
||||
"blazing/logic/service/fight/input"
|
||||
"blazing/modules/player/model"
|
||||
)
|
||||
|
||||
func TestSendLegacyRoundBroadcastSkipsUnexecutedAction(t *testing.T) {
|
||||
ourPlayer := &stubPlayer{info: model.PlayerInfo{UserID: 1001}}
|
||||
oppPlayer := &stubPlayer{info: model.PlayerInfo{UserID: 2002}}
|
||||
|
||||
our := input.NewInput(nil, ourPlayer)
|
||||
our.InitAttackValue()
|
||||
our.AttackValue.SkillID = 111
|
||||
our.SetCurPetAt(0, fightinfo.CreateBattlePetEntity(model.PetInfo{
|
||||
ID: 11,
|
||||
Hp: 80,
|
||||
MaxHp: 100,
|
||||
CatchTime: 101,
|
||||
}))
|
||||
|
||||
opp := input.NewInput(nil, oppPlayer)
|
||||
opp.InitAttackValue()
|
||||
opp.SetCurPetAt(0, fightinfo.CreateBattlePetEntity(model.PetInfo{
|
||||
ID: 22,
|
||||
Hp: 0,
|
||||
MaxHp: 100,
|
||||
CatchTime: 202,
|
||||
}))
|
||||
|
||||
fc := &FightC{
|
||||
Our: []*input.Input{our},
|
||||
Opp: []*input.Input{opp},
|
||||
LegacyGroupProtocol: true,
|
||||
}
|
||||
|
||||
firstAttack := &action.SelectSkillAction{
|
||||
BaseAction: action.BaseAction{PlayerID: ourPlayer.info.UserID, ActorIndex: 0, TargetIndex: 0},
|
||||
}
|
||||
secondAttack := &action.SelectSkillAction{
|
||||
BaseAction: action.BaseAction{PlayerID: oppPlayer.info.UserID, ActorIndex: 0, TargetIndex: 0},
|
||||
}
|
||||
|
||||
fc.sendLegacyRoundBroadcast(firstAttack, secondAttack)
|
||||
|
||||
for _, player := range []*stubPlayer{ourPlayer, oppPlayer} {
|
||||
if len(player.sentCmds) != 2 {
|
||||
t.Fatalf("expected one skill packet plus bout done, got %v", player.sentCmds)
|
||||
}
|
||||
if player.sentCmds[0] != groupCmdSkillHurt {
|
||||
t.Fatalf("expected first packet to be skill hurt, got %d", player.sentCmds[0])
|
||||
}
|
||||
if player.sentCmds[1] != groupCmdBoutDone {
|
||||
t.Fatalf("expected second packet to be bout done, got %d", player.sentCmds[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,10 +196,8 @@ type PropDict struct {
|
||||
|
||||
// NoteUseSkillOutboundInfo 战斗技能使用通知的出站信息结构体
|
||||
type NoteUseSkillOutboundInfo struct {
|
||||
FirstAttackInfoLen uint32 `struc:"sizeof=FirstAttackInfo"`
|
||||
FirstAttackInfo []model.AttackValue // 本轮先手方精灵在释放技能结束后的状态
|
||||
SecondAttackInfoLen uint32 `struc:"sizeof=SecondAttackInfo"`
|
||||
SecondAttackInfo []model.AttackValue // 本轮后手方精灵在释放技能结束后的状态
|
||||
FirstAttackInfo model.AttackValue // 本轮先手方精灵在释放技能结束后的状态
|
||||
SecondAttackInfo model.AttackValue // 本轮后手方精灵在释放技能结束后的状态
|
||||
}
|
||||
|
||||
type FightStartOutboundInfo struct {
|
||||
|
||||
@@ -39,6 +39,7 @@ type FightC struct {
|
||||
actionNotify chan struct{}
|
||||
acceptActions bool
|
||||
pendingActions []action.BattleActionI // 待处理动作队列,同一战斗位最多保留一个动作
|
||||
pendingHead int
|
||||
actionRound atomic.Uint32
|
||||
|
||||
quit chan struct{}
|
||||
@@ -250,20 +251,13 @@ func (f *FightC) sideHasActionableSlots(side int) bool {
|
||||
}
|
||||
|
||||
func (f *FightC) slotNeedsAction(in *input.Input) bool {
|
||||
var bench []*info.BattlePetEntity
|
||||
if in == nil {
|
||||
return false
|
||||
}
|
||||
if current := in.CurrentPet(); current != nil && current.Info.Hp > 0 {
|
||||
return true
|
||||
}
|
||||
bench = in.BenchPets()
|
||||
for _, pet := range bench {
|
||||
if pet != nil && pet.Info.Hp > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return in.HasLivingBench()
|
||||
}
|
||||
|
||||
func (f *FightC) setActionAttackValue(act action.BattleActionI) {
|
||||
|
||||
@@ -32,6 +32,7 @@ var EffectType = enum.New[struct {
|
||||
}]()
|
||||
|
||||
var NodeM = make(map[int64]Effect, 0)
|
||||
var NodeFactoryM = make(map[int64]func() Effect, 0)
|
||||
|
||||
func InitEffect(etype EnumEffectType, id int, t Effect) {
|
||||
pr := EffectIDCombiner{}
|
||||
@@ -41,6 +42,13 @@ func InitEffect(etype EnumEffectType, id int, t Effect) {
|
||||
|
||||
NodeM[pr.EffectID()] = t
|
||||
}
|
||||
|
||||
func InitEffectFactory(etype EnumEffectType, id int, factory func() Effect) {
|
||||
pr := EffectIDCombiner{}
|
||||
pr.Combine(etype, 0, gconv.Uint16(id))
|
||||
|
||||
NodeFactoryM[pr.EffectID()] = factory
|
||||
}
|
||||
func GeteffectIDs(etype EnumEffectType) []uint32 {
|
||||
|
||||
var ret []uint32 = make([]uint32, 0)
|
||||
@@ -60,6 +68,19 @@ func geteffect[T int | byte | uint16](etype EnumEffectType, id T) Effect {
|
||||
pr := EffectIDCombiner{}
|
||||
pr.Combine(etype, 0, gconv.Uint16(id))
|
||||
|
||||
if factory, ok := NodeFactoryM[pr.EffectID()]; ok {
|
||||
eff := factory()
|
||||
if eff == nil {
|
||||
return nil
|
||||
}
|
||||
eff.ID(pr)
|
||||
if etype == EffectType.Status {
|
||||
eff.CanStack(true)
|
||||
eff.Duration(grand.N(1, 2))
|
||||
}
|
||||
return eff
|
||||
}
|
||||
|
||||
//todo 获取前GetEffect
|
||||
ret, ok := NodeM[pr.EffectID()]
|
||||
if ok {
|
||||
@@ -107,18 +128,14 @@ func (our *Input) GetProp(id int) alpacadecimal.Decimal {
|
||||
}
|
||||
|
||||
func (our *Input) GetEffect(etype EnumEffectType, id int) Effect {
|
||||
var ret []Effect
|
||||
pr := EffectIDCombiner{}
|
||||
pr.Combine(etype, 0, gconv.Uint16(id))
|
||||
|
||||
for _, v := range our.Effects {
|
||||
if v.ID().Base == pr.Base && v.Alive() {
|
||||
ret = append(ret, v)
|
||||
our.ensureEffectIndex()
|
||||
bucket := our.effectsByBase[pr.Base]
|
||||
for i := len(bucket) - 1; i >= 0; i-- {
|
||||
if bucket[i] != nil && bucket[i].Alive() {
|
||||
return bucket[i]
|
||||
}
|
||||
|
||||
}
|
||||
if len(ret) > 0 {
|
||||
return ret[len(ret)-1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -178,6 +195,7 @@ func (our *Input) AddEffect(in *Input, e Effect) Effect {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
our.ensureEffectIndex()
|
||||
ctx := e.Ctx()
|
||||
if ctx != nil {
|
||||
if ctx.Source == nil {
|
||||
@@ -204,7 +222,7 @@ func (our *Input) AddEffect(in *Input, e Effect) Effect {
|
||||
//TODO 先激活
|
||||
//fmt.Println("产生回合数", e.ID(), e.Duration())
|
||||
// 如果已有同 ID 的效果,尝试叠加
|
||||
for _, v := range our.Effects {
|
||||
for _, v := range our.effectsByBase[e.ID().Base] {
|
||||
if v == e {
|
||||
return nil //完全相同,跳过执行
|
||||
}
|
||||
@@ -219,7 +237,7 @@ func (our *Input) AddEffect(in *Input, e Effect) Effect {
|
||||
if !v.CanStack() { //说明进行了替换
|
||||
v.Alive(false) //不允许叠层,取消效果
|
||||
e.Duration(utils.Max(e.Duration(), v.Duration()))
|
||||
our.Effects = append(our.Effects, e)
|
||||
our.appendEffect(e)
|
||||
return v //这里把V替换掉了
|
||||
} else {
|
||||
//默认给叠一层
|
||||
@@ -237,7 +255,7 @@ func (our *Input) AddEffect(in *Input, e Effect) Effect {
|
||||
}
|
||||
//无限叠加,比如能力提升类buff
|
||||
// 如果没有同 ID 的效果,直接添加
|
||||
our.Effects = append(our.Effects, e)
|
||||
our.appendEffect(e)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -309,6 +327,34 @@ func (our *Input) CancelTurn(in *Input) {
|
||||
|
||||
}
|
||||
|
||||
func (our *Input) ensureEffectIndex() {
|
||||
if our == nil {
|
||||
return
|
||||
}
|
||||
if our.effectsByBase == nil {
|
||||
our.effectsByBase = make(map[int64][]Effect, len(our.Effects))
|
||||
}
|
||||
if our.indexedEffects > len(our.Effects) {
|
||||
our.effectsByBase = make(map[int64][]Effect, len(our.Effects))
|
||||
our.indexedEffects = 0
|
||||
}
|
||||
for our.indexedEffects < len(our.Effects) {
|
||||
effect := our.Effects[our.indexedEffects]
|
||||
if effect != nil {
|
||||
our.effectsByBase[effect.ID().Base] = append(our.effectsByBase[effect.ID().Base], effect)
|
||||
}
|
||||
our.indexedEffects++
|
||||
}
|
||||
}
|
||||
|
||||
func (our *Input) appendEffect(effect Effect) {
|
||||
if our == nil || effect == nil {
|
||||
return
|
||||
}
|
||||
our.Effects = append(our.Effects, effect)
|
||||
our.ensureEffectIndex()
|
||||
}
|
||||
|
||||
// // 消除全部 断回合效果,但是我放下场的时候应该断掉所有的回合类效果
|
||||
// func (our *Input) CancelAll() {
|
||||
// our.Effects = make([]Effect, 0)
|
||||
|
||||
@@ -15,6 +15,13 @@ import (
|
||||
"github.com/jinzhu/copier"
|
||||
)
|
||||
|
||||
var statusBonuses = map[info.EnumPetStatus]float64{
|
||||
info.PetStatus.Paralysis: 1.5,
|
||||
info.PetStatus.Poisoned: 1.5,
|
||||
info.PetStatus.Sleep: 2.0,
|
||||
// /info.BattleStatus.Frozen: 2.0,
|
||||
}
|
||||
|
||||
type Input struct {
|
||||
CanChange uint32 //是否可以死亡切换CanChange
|
||||
// CanAction bool //是否可以行动
|
||||
@@ -27,9 +34,11 @@ type Input struct {
|
||||
CanCapture int
|
||||
Finished bool //是否加载完成
|
||||
// info.BattleActionI
|
||||
Effects []Effect //effects 实际上全局就是effect无限回合 //effects容器 技能的
|
||||
EffectCache []Effect //这里是命中前执行的容器,也就是命中前执行的所有逻辑相关,理论上一个effect被激活,就应该同时将其他的effect取消激活
|
||||
EffectLost []Effect
|
||||
Effects []Effect //effects 实际上全局就是effect无限回合 //effects容器 技能的
|
||||
EffectCache []Effect //这里是命中前执行的容器,也就是命中前执行的所有逻辑相关,理论上一个effect被激活,就应该同时将其他的effect取消激活
|
||||
EffectLost []Effect
|
||||
effectsByBase map[int64][]Effect
|
||||
indexedEffects int
|
||||
// 删掉伤害记录,可以在回调中记录,而不是每次调用记录
|
||||
*model.AttackValue
|
||||
FightC common.FightI
|
||||
@@ -57,6 +66,7 @@ func NewInput(c common.FightI, p common.PlayerI) *Input {
|
||||
ret := &Input{FightC: c, Player: p}
|
||||
ret.Effects = make([]Effect, 0)
|
||||
ret.CurPet = make([]*info.BattlePetEntity, 0)
|
||||
ret.effectsByBase = make(map[int64][]Effect)
|
||||
|
||||
// t := Geteffect(EffectType.Damage, 0)
|
||||
// t.Effect.SetArgs(ret)
|
||||
@@ -115,6 +125,27 @@ func (our *Input) BenchPets() []*info.BattlePetEntity {
|
||||
return bench
|
||||
}
|
||||
|
||||
func (our *Input) HasLivingBench() bool {
|
||||
if our == nil {
|
||||
return false
|
||||
}
|
||||
current := our.CurrentPet()
|
||||
currentCatchTime := uint32(0)
|
||||
if current != nil {
|
||||
currentCatchTime = current.Info.CatchTime
|
||||
}
|
||||
for _, pet := range our.AllPet {
|
||||
if pet == nil || pet.Info.Hp == 0 {
|
||||
continue
|
||||
}
|
||||
if current != nil && pet.Info.CatchTime == currentCatchTime {
|
||||
continue
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (our *Input) OpponentSlots() []*Input {
|
||||
if our == nil {
|
||||
return nil
|
||||
@@ -299,20 +330,12 @@ func (our *Input) GetPet(id uint32) (ii *info.BattlePetEntity, Reason info.Chang
|
||||
// GetStatusBonus 获取最高的状态倍率
|
||||
// 遍历状态数组,返回存在的状态中最高的倍率(无状态则返回1.0)
|
||||
func (our *Input) GetStatusBonus() float64 {
|
||||
// 异常状态倍率映射表(状态索引 -> 倍率)
|
||||
var statusBonuses = map[info.EnumPetStatus]float64{
|
||||
info.PetStatus.Paralysis: 1.5,
|
||||
info.PetStatus.Poisoned: 1.5,
|
||||
info.PetStatus.Sleep: 2.0,
|
||||
// /info.BattleStatus.Frozen: 2.0,
|
||||
}
|
||||
maxBonus := 1.0 // 默认无状态倍率
|
||||
|
||||
for statusIdx := 0; statusIdx < 20; statusIdx++ {
|
||||
t := our.InitEffect(EffectType.Status, statusIdx)
|
||||
t := our.GetEffect(EffectType.Status, statusIdx)
|
||||
|
||||
// 检查状态是否存在(数组中值为1表示存在该状态)
|
||||
if t != nil && t.Stack() > 0 {
|
||||
if t != nil && t.Alive() {
|
||||
if bonus, exists := statusBonuses[info.EnumPetStatus(statusIdx)]; exists && bonus > maxBonus {
|
||||
maxBonus = bonus
|
||||
}
|
||||
|
||||
@@ -2,6 +2,40 @@ package input
|
||||
|
||||
import "github.com/gogf/gf/v2/util/grand"
|
||||
|
||||
func compactSlots(slots []*Input) []*Input {
|
||||
if len(slots) == 0 {
|
||||
return nil
|
||||
}
|
||||
ret := make([]*Input, 0, len(slots))
|
||||
for _, slot := range slots {
|
||||
if slot == nil {
|
||||
continue
|
||||
}
|
||||
ret = append(ret, slot)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func uniqueControllerCount(slots []*Input) int {
|
||||
if len(slots) == 0 {
|
||||
return 0
|
||||
}
|
||||
seen := make(map[uint32]struct{}, len(slots))
|
||||
count := 0
|
||||
for _, slot := range slots {
|
||||
if slot == nil || slot.Player == nil {
|
||||
continue
|
||||
}
|
||||
userID := slot.Player.GetInfo().UserID
|
||||
if _, ok := seen[userID]; ok {
|
||||
continue
|
||||
}
|
||||
seen[userID] = struct{}{}
|
||||
count++
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// TeamSlots 返回当前输入所在阵营的全部有效战斗位视图。
|
||||
func (our *Input) TeamSlots() []*Input {
|
||||
if our == nil {
|
||||
@@ -10,14 +44,7 @@ func (our *Input) TeamSlots() []*Input {
|
||||
if len(our.Team) == 0 {
|
||||
return []*Input{our}
|
||||
}
|
||||
slots := make([]*Input, 0, len(our.Team))
|
||||
for _, teammate := range our.Team {
|
||||
if teammate == nil {
|
||||
continue
|
||||
}
|
||||
slots = append(slots, teammate)
|
||||
}
|
||||
return slots
|
||||
return compactSlots(our.Team)
|
||||
}
|
||||
|
||||
// TeamSlotIndex 返回当前输入在本阵营中的原始站位下标。
|
||||
@@ -69,6 +96,51 @@ func (our *Input) HasLivingTeammate() bool {
|
||||
return len(our.LivingTeammates()) > 0
|
||||
}
|
||||
|
||||
// TeamInputCount 返回当前阵营有效 input 数量。
|
||||
func (our *Input) TeamInputCount() int {
|
||||
return len(our.TeamSlots())
|
||||
}
|
||||
|
||||
// OpponentInputCount 返回敌方阵营有效 input 数量。
|
||||
func (our *Input) OpponentInputCount() int {
|
||||
if our == nil {
|
||||
return 0
|
||||
}
|
||||
if len(our.OppTeam) == 0 {
|
||||
if our.Opp != nil {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return len(compactSlots(our.OppTeam))
|
||||
}
|
||||
|
||||
// IsMultiInputSide 判断当前阵营是否为多 input。
|
||||
func (our *Input) IsMultiInputSide() bool {
|
||||
return our.TeamInputCount() > 1
|
||||
}
|
||||
|
||||
// IsMultiInputBattle 判断当前战斗是否包含多 input 站位。
|
||||
func (our *Input) IsMultiInputBattle() bool {
|
||||
if our == nil {
|
||||
return false
|
||||
}
|
||||
return our.TeamInputCount() > 1 || our.OpponentInputCount() > 1
|
||||
}
|
||||
|
||||
// TeamControllerCount 返回当前阵营实际操作者数量。
|
||||
func (our *Input) TeamControllerCount() int {
|
||||
if our == nil {
|
||||
return 0
|
||||
}
|
||||
return uniqueControllerCount(our.TeamSlots())
|
||||
}
|
||||
|
||||
// IsSingleControllerMultiInputSide 判断当前阵营是否为“单人控制的多 input”。
|
||||
func (our *Input) IsSingleControllerMultiInputSide() bool {
|
||||
return our.IsMultiInputSide() && our.TeamControllerCount() <= 1
|
||||
}
|
||||
|
||||
// TeamSlotAt 返回指定己方站位。
|
||||
func (our *Input) TeamSlotAt(index int) *Input {
|
||||
if our == nil {
|
||||
|
||||
@@ -3,10 +3,32 @@ package input
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"blazing/common/socket/errorcode"
|
||||
"blazing/logic/service/common"
|
||||
fightinfo "blazing/logic/service/fight/info"
|
||||
spaceinfo "blazing/logic/service/space/info"
|
||||
"blazing/modules/player/model"
|
||||
)
|
||||
|
||||
type teamTestPlayer struct {
|
||||
info model.PlayerInfo
|
||||
fightInfo fightinfo.Fightinfo
|
||||
}
|
||||
|
||||
func (p *teamTestPlayer) ApplyPetDisplayInfo(*spaceinfo.SimpleInfo) {}
|
||||
func (p *teamTestPlayer) GetPlayerCaptureContext() *fightinfo.PlayerCaptureContext { return nil }
|
||||
func (p *teamTestPlayer) Roll(int, int) (bool, float64, float64) { return false, 0, 0 }
|
||||
func (p *teamTestPlayer) Getfightinfo() fightinfo.Fightinfo { return p.fightInfo }
|
||||
func (p *teamTestPlayer) ItemAdd(int64, int64) bool { return false }
|
||||
func (p *teamTestPlayer) GetInfo() *model.PlayerInfo { return &p.info }
|
||||
func (p *teamTestPlayer) InvitePlayer(common.PlayerI) {}
|
||||
func (p *teamTestPlayer) SetFightC(common.FightI) {}
|
||||
func (p *teamTestPlayer) QuitFight() {}
|
||||
func (p *teamTestPlayer) MessWin(bool) {}
|
||||
func (p *teamTestPlayer) CanFight() errorcode.ErrorCode { return 0 }
|
||||
func (p *teamTestPlayer) SendPackCmd(uint32, any) {}
|
||||
func (p *teamTestPlayer) GetPetInfo(uint32) []model.PetInfo { return nil }
|
||||
|
||||
func TestLivingTeammatesFiltersSelfAndDeadSlots(t *testing.T) {
|
||||
owner := &Input{CurPet: []*fightinfo.BattlePetEntity{{Info: model.PetInfo{Hp: 10}}}}
|
||||
aliveMate := &Input{CurPet: []*fightinfo.BattlePetEntity{{Info: model.PetInfo{Hp: 5}}}}
|
||||
@@ -58,3 +80,38 @@ func TestRandomOpponentSlotIndexPrefersLivingTarget(t *testing.T) {
|
||||
t.Fatalf("expected opponent slot 1 to return live opponent")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputModeHelpers(t *testing.T) {
|
||||
playerA := &teamTestPlayer{info: model.PlayerInfo{UserID: 1001}}
|
||||
playerB := &teamTestPlayer{info: model.PlayerInfo{UserID: 1002}}
|
||||
|
||||
solo := &Input{}
|
||||
soloMate := &Input{}
|
||||
groupMate := &Input{}
|
||||
|
||||
solo.Player = playerA
|
||||
soloMate.Player = playerA
|
||||
groupMate.Player = playerB
|
||||
|
||||
solo.Team = []*Input{solo}
|
||||
solo.OppTeam = []*Input{{}}
|
||||
if solo.IsMultiInputBattle() {
|
||||
t.Fatalf("expected single-input battle to be false")
|
||||
}
|
||||
|
||||
solo.Team = []*Input{solo, soloMate}
|
||||
if !solo.IsMultiInputBattle() {
|
||||
t.Fatalf("expected multi-input battle to be true")
|
||||
}
|
||||
if !solo.IsSingleControllerMultiInputSide() {
|
||||
t.Fatalf("expected same-controller double side to be single-controller multi-input")
|
||||
}
|
||||
|
||||
solo.Team = []*Input{solo, groupMate}
|
||||
if solo.TeamControllerCount() != 2 {
|
||||
t.Fatalf("expected group side controller count 2, got %d", solo.TeamControllerCount())
|
||||
}
|
||||
if solo.IsSingleControllerMultiInputSide() {
|
||||
t.Fatalf("expected grouped side not to be single-controller multi-input")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ func (f *FightC) battleLoop() {
|
||||
if player := f.primaryOppPlayer(); player != nil {
|
||||
f.WinnerId = player.GetInfo().UserID
|
||||
}
|
||||
f.Reason = model.BattleOverReason.DefaultEnd
|
||||
f.Reason = normalizeFightOverReason(model.BattleOverReason.DefaultEnd)
|
||||
f.FightOverInfo.WinnerId = f.WinnerId
|
||||
f.FightOverInfo.Reason = f.Reason
|
||||
f.closefight = true
|
||||
@@ -86,7 +86,7 @@ func (f *FightC) battleLoop() {
|
||||
if player := f.primaryOurPlayer(); player != nil {
|
||||
f.WinnerId = player.GetInfo().UserID
|
||||
}
|
||||
f.Reason = model.BattleOverReason.DefaultEnd
|
||||
f.Reason = normalizeFightOverReason(model.BattleOverReason.DefaultEnd)
|
||||
f.FightOverInfo.WinnerId = f.WinnerId
|
||||
f.FightOverInfo.Reason = f.Reason
|
||||
f.closefight = true
|
||||
@@ -177,7 +177,7 @@ func (f *FightC) battleLoop() {
|
||||
if f.LegacyGroupProtocol {
|
||||
f.sendLegacyGroupOver(p, &f.FightOverInfo)
|
||||
} else {
|
||||
f.sendFightPacket(p, fightPacketOver, &f.FightOverInfo)
|
||||
f.sendFightPacket(p, fightPacketOver, buildFightOverPayload(f.FightOverInfo))
|
||||
}
|
||||
|
||||
p.QuitFight()
|
||||
|
||||
@@ -297,9 +297,6 @@ func buildFight(opts *fightBuildOptions) (*FightC, errorcode.ErrorCode) {
|
||||
f.bindInputFightContext(f.Our, f.Opp)
|
||||
f.linkTeamViews()
|
||||
|
||||
f.ReadyInfo.OurInfo, f.ReadyInfo.OurPetList = initfightready(f.primaryOur())
|
||||
f.ReadyInfo.OpponentInfo, f.ReadyInfo.OpponentPetList = initfightready(f.primaryOpp())
|
||||
|
||||
loadtime := 120 * time.Second
|
||||
if f.Info.Status == info.BattleMode.FIGHT_WITH_NPC {
|
||||
if opp := f.primaryOpp(); opp != nil {
|
||||
@@ -313,6 +310,9 @@ func buildFight(opts *fightBuildOptions) (*FightC, errorcode.ErrorCode) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f.ReadyInfo.OurInfo, f.ReadyInfo.OurPetList = initfightready(f.primaryOur())
|
||||
f.ReadyInfo.OpponentInfo, f.ReadyInfo.OpponentPetList = initfightready(f.primaryOpp())
|
||||
f.FightStartOutboundInfo = f.buildFightStartInfo()
|
||||
|
||||
f.BroadcastPlayers(func(p common.PlayerI) {
|
||||
@@ -342,7 +342,7 @@ func buildFight(opts *fightBuildOptions) (*FightC, errorcode.ErrorCode) {
|
||||
if f.LegacyGroupProtocol {
|
||||
f.sendLegacyGroupOver(p, &f.FightOverInfo)
|
||||
} else {
|
||||
f.sendFightPacket(p, fightPacketOver, &f.FightOverInfo)
|
||||
f.sendFightPacket(p, fightPacketOver, buildFightOverPayload(f.FightOverInfo))
|
||||
}
|
||||
p.QuitFight()
|
||||
})
|
||||
|
||||
@@ -24,6 +24,7 @@ type EffectNode struct {
|
||||
canStack bool // 最大叠加层数 ,正常都是不允许叠加的,除了衰弱特殊效果 ,异常和能力的叠层
|
||||
isFirst bool
|
||||
SideEffectArgs []int // 附加效果参数
|
||||
cachedArgs []alpacadecimal.Decimal
|
||||
// owner bool //是否作用自身
|
||||
Success bool // 是否执行成功 成功XXX,失败XXX
|
||||
arget bool // 传出作用对象,默认0是自身,1是作用于对面
|
||||
@@ -240,18 +241,22 @@ func (e *EffectNode) SetArgs(t *input.Input, a ...int) {
|
||||
e.Input = t
|
||||
if len(a) > 0 {
|
||||
e.SideEffectArgs = a
|
||||
e.cachedArgs = e.cachedArgs[:0]
|
||||
for _, v := range a {
|
||||
e.cachedArgs = append(e.cachedArgs, alpacadecimal.NewFromInt(int64(v)))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
func (e *EffectNode) Args() []alpacadecimal.Decimal {
|
||||
var ret []alpacadecimal.Decimal
|
||||
|
||||
for _, v := range e.SideEffectArgs {
|
||||
ret = append(ret, alpacadecimal.NewFromInt(int64(v)))
|
||||
|
||||
if len(e.cachedArgs) == len(e.SideEffectArgs) {
|
||||
return e.cachedArgs
|
||||
}
|
||||
|
||||
return ret
|
||||
e.cachedArgs = e.cachedArgs[:0]
|
||||
for _, v := range e.SideEffectArgs {
|
||||
e.cachedArgs = append(e.cachedArgs, alpacadecimal.NewFromInt(int64(v)))
|
||||
}
|
||||
return e.cachedArgs
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ import (
|
||||
)
|
||||
|
||||
type stubPlayer struct {
|
||||
info model.PlayerInfo
|
||||
info model.PlayerInfo
|
||||
sentCmds []uint32
|
||||
}
|
||||
|
||||
func (*stubPlayer) ApplyPetDisplayInfo(*spaceinfo.SimpleInfo) {}
|
||||
@@ -26,7 +27,7 @@ func (*stubPlayer) SetFightC(common.FightI) {}
|
||||
func (*stubPlayer) QuitFight() {}
|
||||
func (*stubPlayer) MessWin(bool) {}
|
||||
func (*stubPlayer) CanFight() errorcode.ErrorCode { return 0 }
|
||||
func (*stubPlayer) SendPackCmd(uint32, any) {}
|
||||
func (p *stubPlayer) SendPackCmd(cmd uint32, _ any) { p.sentCmds = append(p.sentCmds, cmd) }
|
||||
func (*stubPlayer) GetPetInfo(uint32) []model.PetInfo { return nil }
|
||||
|
||||
func TestFightActionEnvelopeEncodedTargetIndex(t *testing.T) {
|
||||
@@ -111,3 +112,36 @@ func TestBuildFightStateStartEnvelope(t *testing.T) {
|
||||
t.Fatalf("unexpected right fighter snapshot: %+v", envelope.Right[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildNoteUseSkillOutboundInfoUsesActionOrder(t *testing.T) {
|
||||
ourPlayer := &stubPlayer{info: model.PlayerInfo{UserID: 1001}}
|
||||
oppPlayer := &stubPlayer{info: model.PlayerInfo{UserID: 2002}}
|
||||
|
||||
our := input.NewInput(nil, ourPlayer)
|
||||
our.InitAttackValue()
|
||||
our.AttackValue.SkillID = 111
|
||||
our.AttackValue.RemainHp = 80
|
||||
our.AttackValue.MaxHp = 100
|
||||
|
||||
opp := input.NewInput(nil, oppPlayer)
|
||||
opp.InitAttackValue()
|
||||
opp.AttackValue.SkillID = 222
|
||||
opp.AttackValue.RemainHp = 70
|
||||
opp.AttackValue.MaxHp = 100
|
||||
|
||||
fc := &FightC{
|
||||
Our: []*input.Input{our},
|
||||
Opp: []*input.Input{opp},
|
||||
First: opp,
|
||||
Second: our,
|
||||
}
|
||||
|
||||
result := fc.buildNoteUseSkillOutboundInfo()
|
||||
|
||||
if result.FirstAttackInfo.UserID != 2002 || result.FirstAttackInfo.SkillID != 222 {
|
||||
t.Fatalf("expected first attack info to belong to acting opponent, got %+v", result.FirstAttackInfo)
|
||||
}
|
||||
if result.SecondAttackInfo.UserID != 1001 || result.SecondAttackInfo.SkillID != 111 {
|
||||
t.Fatalf("expected second attack info to keep the idle side placeholder, got %+v", result.SecondAttackInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,3 +23,9 @@ type C2S_Skill_Sort struct {
|
||||
Skill [4]uint32 `json:"skill_1"` // 技能1(对应C# uint skill_1)
|
||||
|
||||
}
|
||||
|
||||
type CommitPetSkillsInfo struct {
|
||||
Head common.TomeeHeader `cmd:"52313" struc:"skip"`
|
||||
CatchTime uint32 `json:"catchTime"`
|
||||
Skill [4]uint32 `json:"skill"`
|
||||
}
|
||||
|
||||
@@ -51,6 +51,9 @@ func (p *Player) IsMatch(t configmodel.Event) bool {
|
||||
if len(p.Info.PetList) == 0 {
|
||||
return false
|
||||
}
|
||||
if p.Info.PetList[0].Hp == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
firstPetID := int32(p.Info.PetList[0].ID)
|
||||
_, ok := lo.Find(t.FirstSprites, func(item int32) bool {
|
||||
|
||||
49
logic/service/player/Monster_test.go
Normal file
49
logic/service/player/Monster_test.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package player
|
||||
|
||||
import (
|
||||
configmodel "blazing/modules/config/model"
|
||||
playermodel "blazing/modules/player/model"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIsMatchFirstSpritesRequiresLivingLeadPet(t *testing.T) {
|
||||
player := &Player{
|
||||
baseplayer: baseplayer{
|
||||
Info: &playermodel.PlayerInfo{
|
||||
PetList: []playermodel.PetInfo{
|
||||
{ID: 1001, Hp: 0},
|
||||
{ID: 2002, Hp: 100},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
event := configmodel.Event{
|
||||
FirstSprites: []int32{1001},
|
||||
}
|
||||
|
||||
if player.IsMatch(event) {
|
||||
t.Fatalf("expected dead lead pet to fail FirstSprites match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMatchFirstSpritesAcceptsLivingLeadPet(t *testing.T) {
|
||||
player := &Player{
|
||||
baseplayer: baseplayer{
|
||||
Info: &playermodel.PlayerInfo{
|
||||
PetList: []playermodel.PetInfo{
|
||||
{ID: 1001, Hp: 100},
|
||||
{ID: 2002, Hp: 100},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
event := configmodel.Event{
|
||||
FirstSprites: []int32{1001},
|
||||
}
|
||||
|
||||
if !player.IsMatch(event) {
|
||||
t.Fatalf("expected living lead pet to pass FirstSprites match")
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,16 @@
|
||||
package player
|
||||
|
||||
import "blazing/modules/player/model"
|
||||
|
||||
type AI_player struct {
|
||||
baseplayer
|
||||
|
||||
CanCapture int
|
||||
BossScript string
|
||||
}
|
||||
|
||||
func (p *AI_player) GetPetInfo(_ uint32) []model.PetInfo {
|
||||
ret := make([]model.PetInfo, 0, len(p.Info.PetList))
|
||||
ret = append(ret, p.Info.PetList...)
|
||||
return ret
|
||||
}
|
||||
|
||||
@@ -32,17 +32,19 @@ func (p *baseplayer) GetInfo() *model.PlayerInfo {
|
||||
return p.Info
|
||||
}
|
||||
|
||||
func ApplyPetLevelLimit(pet model.PetInfo, limitlevel uint32) model.PetInfo {
|
||||
originalHP := pet.Hp
|
||||
pet.CalculatePetPane(limitlevel)
|
||||
pet.Hp = utils.Min(originalHP, pet.MaxHp)
|
||||
return pet
|
||||
}
|
||||
|
||||
func (p *baseplayer) GetPetInfo(limitlevel uint32) []model.PetInfo {
|
||||
|
||||
var ret []model.PetInfo
|
||||
ret := make([]model.PetInfo, 0, len(p.Info.PetList))
|
||||
|
||||
for _, pet := range p.Info.PetList {
|
||||
if limitlevel > 0 {
|
||||
pet.Level = utils.Min(pet.Level, limitlevel)
|
||||
|
||||
}
|
||||
|
||||
ret = append(ret, pet)
|
||||
ret = append(ret, ApplyPetLevelLimit(pet, limitlevel))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
@@ -1,23 +1,11 @@
|
||||
package player
|
||||
|
||||
import (
|
||||
"blazing/logic/service/fight/info"
|
||||
"blazing/logic/service/task"
|
||||
"blazing/modules/player/model"
|
||||
|
||||
"github.com/pointernil/bitset32"
|
||||
)
|
||||
|
||||
// 辅助函数:获取任务奖励,封装逻辑便于复用和统一检查
|
||||
// 返回nil表示无奖励
|
||||
func (p *Player) getTaskGift(taskID int, ot int) *task.TaskResult {
|
||||
// 防御性检查:taskID非法时直接返回nil
|
||||
if taskID <= 0 {
|
||||
return nil
|
||||
}
|
||||
return task.GetTaskInfo(taskID, ot)
|
||||
}
|
||||
|
||||
// SptCompletedTask 完成任务(单分支)
|
||||
// 优化点:仅当奖励存在时,才完成任务并发放奖励
|
||||
func (p *Player) SptCompletedTask(taskID int, ot int) {
|
||||
@@ -29,15 +17,17 @@ func (p *Player) SptCompletedTask(taskID int, ot int) {
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 核心逻辑:先检查奖励是否存在,无奖励则直接返回(不完成任务)
|
||||
gift := p.getTaskGift(taskID, ot)
|
||||
if gift == nil {
|
||||
if !p.canCompleteTaskReward(taskID, ot) {
|
||||
return
|
||||
}
|
||||
|
||||
granted, err := p.ApplyTaskCompletion(uint32(taskID), ot, nil)
|
||||
if err != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// 3. 奖励存在时,才标记任务完成 + 发放奖励
|
||||
p.Info.SetTask(taskID, model.Completed)
|
||||
p.bossgive(taskID, ot)
|
||||
p.SendTaskCompletionBonus(uint32(taskID), granted)
|
||||
}
|
||||
|
||||
// TawerCompletedTask 完成塔类任务(多分支)
|
||||
@@ -48,71 +38,34 @@ func (p *Player) TawerCompletedTask(taskID int, ot int) {
|
||||
}
|
||||
// 处理默认分支(ot=-1):仅奖励存在时才完成主任务
|
||||
if p.Info.GetTask(taskID) != model.Completed {
|
||||
defaultGift := p.getTaskGift(taskID, -1)
|
||||
if defaultGift != nil { // 奖励存在才标记主任务完成
|
||||
p.Info.SetTask(taskID, model.Completed)
|
||||
p.bossgive(taskID, -1)
|
||||
if p.canCompleteTaskReward(taskID, -1) {
|
||||
granted, err := p.ApplyTaskCompletion(uint32(taskID), -1, nil)
|
||||
if err == 0 {
|
||||
p.Info.SetTask(taskID, model.Completed)
|
||||
p.SendTaskCompletionBonus(uint32(taskID), granted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理指定分支(ot):仅奖励存在时才标记分支完成并发奖
|
||||
p.Service.Task.Exec(uint32(taskID), func(te *model.Task) bool {
|
||||
|
||||
// 核心检查:指定分支的奖励是否存在
|
||||
branchGift := p.getTaskGift(taskID, ot)
|
||||
if branchGift == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 初始化分支数据
|
||||
if te.Data == nil {
|
||||
te.Data = []uint32{}
|
||||
}
|
||||
|
||||
r := bitset32.From(te.Data)
|
||||
// 分支未完成时,标记完成并发放奖励
|
||||
if !r.Test(uint(ot)) {
|
||||
r.Set(uint(ot))
|
||||
p.bossgive(taskID, ot)
|
||||
te.Data = r.Bytes()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
// bossgive 发放任务奖励(逻辑保持不变,仅补充注释)
|
||||
func (p *Player) bossgive(taskID int, ot int) {
|
||||
gift := p.getTaskGift(taskID, ot)
|
||||
if gift == nil {
|
||||
taskData, err := p.Service.Task.GetTask(uint32(taskID))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res := &info.S2C_GET_BOSS_MONSTER{
|
||||
BonusID: uint32(taskID),
|
||||
if !p.canCompleteTaskReward(taskID, ot) {
|
||||
return
|
||||
}
|
||||
|
||||
// 发放宠物奖励
|
||||
if gift.Pet != nil {
|
||||
p.Service.Pet.PetAdd(gift.Pet, 0)
|
||||
res.PetID = gift.Pet.ID
|
||||
res.CaptureTm = gift.Pet.CatchTime
|
||||
}
|
||||
|
||||
// 发放道具奖励(仅成功添加的道具才返回给前端)
|
||||
for _, item := range gift.ItemList {
|
||||
if success := p.ItemAdd(item.ItemId, item.ItemCnt); success {
|
||||
res.AddItemInfo(item)
|
||||
r := bitset32.From(taskData.Data)
|
||||
if !r.Test(uint(ot)) {
|
||||
r.Set(uint(ot))
|
||||
granted, rewardErr := p.ApplyTaskCompletion(uint32(taskID), ot, nil)
|
||||
if rewardErr != 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 发放称号奖励
|
||||
if gift.Title != 0 {
|
||||
p.GiveTitle(gift.Title)
|
||||
}
|
||||
|
||||
// 发送奖励通知给前端
|
||||
if res.HasReward() {
|
||||
p.SendPackCmd(8004, res)
|
||||
p.SendTaskCompletionBonus(uint32(taskID), granted)
|
||||
taskData.Data = r.Bytes()
|
||||
_ = p.Service.Task.SetTask(taskData)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,32 +24,35 @@ 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
|
||||
}
|
||||
addExp = utils.Min(addExp, p.Info.ExpPool)
|
||||
originalLevel := petInfo.Level
|
||||
exp := int64(petInfo.Exp) + addExp
|
||||
p.Info.ExpPool -= addExp //减去已使用的经验
|
||||
gainedExp := exp //已获得的经验
|
||||
for 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 //停止升级
|
||||
}
|
||||
|
||||
panelLimit := p.CurrentMapPetLevelLimit()
|
||||
if petInfo.Level > 100 {
|
||||
currentHP := petInfo.Hp
|
||||
petInfo.Update(false)
|
||||
petInfo.CalculatePetPane(panelLimit)
|
||||
petInfo.Hp = utils.Min(currentHP, petInfo.MaxHp)
|
||||
}
|
||||
petInfo.Exp = (exp)
|
||||
addExp = utils.Min(addExp, p.Info.ExpPool)
|
||||
if addExp <= 0 {
|
||||
return
|
||||
}
|
||||
originalLevel := petInfo.Level
|
||||
allocatedExp := addExp
|
||||
p.Info.ExpPool -= addExp //减去已使用的经验
|
||||
|
||||
currentExp := petInfo.Exp + addExp
|
||||
for currentExp >= petInfo.NextLvExp && petInfo.NextLvExp > 0 {
|
||||
petInfo.Level++
|
||||
petInfo.Update(true)
|
||||
currentExp -= petInfo.LvExp
|
||||
}
|
||||
petInfo.Exp = currentExp
|
||||
|
||||
// 重新计算面板
|
||||
if originalLevel != petInfo.Level {
|
||||
petInfo.CalculatePetPane(100)
|
||||
petInfo.CalculatePetPane(panelLimit)
|
||||
|
||||
petInfo.Cure()
|
||||
p.Info.PetMaxLevel = utils.Max(petInfo.Level, p.Info.PetMaxLevel)
|
||||
@@ -82,7 +84,7 @@ func (p *Player) AddPetExp(petInfo *model.PetInfo, addExp int64) {
|
||||
var petUpdateInfo info.UpdatePropInfo
|
||||
|
||||
copier.Copy(&petUpdateInfo, petInfo)
|
||||
petUpdateInfo.Exp = uint32(gainedExp)
|
||||
petUpdateInfo.Exp = uint32(allocatedExp)
|
||||
updateOutbound.Data = append(updateOutbound.Data, petUpdateInfo)
|
||||
p.SendPack(header.Pack(updateOutbound)) //准备包由各自发,因为协议不一样
|
||||
|
||||
|
||||
@@ -25,6 +25,13 @@ func (slot PetBagSlot) PetInfo() model.PetInfo {
|
||||
return slot.info
|
||||
}
|
||||
|
||||
func (slot PetBagSlot) PetInfoPtr() *model.PetInfo {
|
||||
if !slot.IsValid() {
|
||||
return nil
|
||||
}
|
||||
return &(*slot.list)[slot.index]
|
||||
}
|
||||
|
||||
func (slot PetBagSlot) IsMainBag() bool {
|
||||
return slot.main
|
||||
}
|
||||
@@ -99,12 +106,20 @@ func validatePetBagOrder(
|
||||
return true
|
||||
}
|
||||
|
||||
func buildLimitedPetList(petList []model.PetInfo, limitlevel uint32) []model.PetInfo {
|
||||
result := make([]model.PetInfo, 0, len(petList))
|
||||
for _, petInfo := range petList {
|
||||
result = append(result, ApplyPetLevelLimit(petInfo, limitlevel))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetUserBagPetInfo 返回主背包和并列备用精灵列表。
|
||||
func (p *Player) GetUserBagPetInfo() *pet.GetUserBagPetInfoOutboundInfo {
|
||||
func (p *Player) GetUserBagPetInfo(limitlevel uint32) *pet.GetUserBagPetInfoOutboundInfo {
|
||||
|
||||
result := &pet.GetUserBagPetInfoOutboundInfo{
|
||||
PetList: p.Info.PetList,
|
||||
BackupPetList: p.Info.BackupPetList,
|
||||
PetList: buildLimitedPetList(p.Info.PetList, limitlevel),
|
||||
BackupPetList: buildLimitedPetList(p.Info.BackupPetList, limitlevel),
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
157
logic/service/player/pet_test.go
Normal file
157
logic/service/player/pet_test.go
Normal file
@@ -0,0 +1,157 @@
|
||||
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 TestAddPetExpAllowsLevelBeyond100WhilePanelStaysCapped(t *testing.T) {
|
||||
petID := firstPetIDForTest(t)
|
||||
petInfo := playermodel.GenPetInfo(petID, 31, 0, 0, 100, nil, 0)
|
||||
expectedPanel := playermodel.GenPetInfo(petID, 31, 0, 0, 100, nil, 0)
|
||||
if petInfo == nil {
|
||||
t.Fatalf("failed to generate test pet")
|
||||
}
|
||||
if expectedPanel == nil {
|
||||
t.Fatalf("failed to generate expected 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 continue beyond 100, got %d", petInfo.Level)
|
||||
}
|
||||
if petInfo.MaxHp != expectedPanel.MaxHp {
|
||||
t.Fatalf("expected max hp to stay capped at 100-level panel, got %d want %d", petInfo.MaxHp, expectedPanel.MaxHp)
|
||||
}
|
||||
if petInfo.Prop != expectedPanel.Prop {
|
||||
t.Fatalf("expected props to stay capped at 100-level panel, got %+v want %+v", petInfo.Prop, expectedPanel.Prop)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddPetExpRecalculatesPanelForLevelAbove100(t *testing.T) {
|
||||
petID := firstPetIDForTest(t)
|
||||
petInfo := playermodel.GenPetInfo(petID, 31, 0, 0, 100, nil, 0)
|
||||
expectedPanel := playermodel.GenPetInfo(petID, 31, 0, 0, 100, nil, 0)
|
||||
if petInfo == nil {
|
||||
t.Fatalf("failed to generate test pet")
|
||||
}
|
||||
if expectedPanel == nil {
|
||||
t.Fatalf("failed to generate expected test pet")
|
||||
}
|
||||
petInfo.Level = 101
|
||||
petInfo.Exp = 7
|
||||
petInfo.MaxHp = 1
|
||||
petInfo.Hp = 999999
|
||||
|
||||
player := &Player{
|
||||
baseplayer: baseplayer{
|
||||
Info: &playermodel.PlayerInfo{
|
||||
ExpPool: 50_000,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
player.AddPetExp(petInfo, 12_345)
|
||||
|
||||
if petInfo.Level < 101 {
|
||||
t.Fatalf("expected level above 100 to be preserved, got %d", petInfo.Level)
|
||||
}
|
||||
if petInfo.MaxHp != expectedPanel.MaxHp {
|
||||
t.Fatalf("expected max hp to be recalculated using level 100 cap, got %d want %d", petInfo.MaxHp, expectedPanel.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)
|
||||
}
|
||||
if player.Info.ExpPool != 50_000-12_345 {
|
||||
t.Fatalf("expected exp pool to be consumed normally, got %d", player.Info.ExpPool)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddPetExpSmallRewardDoesNotJumpToMaxLevel(t *testing.T) {
|
||||
petID := firstPetIDForTest(t)
|
||||
petInfo := playermodel.GenPetInfo(petID, 31, 0, 0, 20, nil, 0)
|
||||
if petInfo == nil {
|
||||
t.Fatalf("failed to generate test pet")
|
||||
}
|
||||
|
||||
player := &Player{
|
||||
baseplayer: baseplayer{
|
||||
Info: &playermodel.PlayerInfo{
|
||||
ExpPool: 1_000_000,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
addExp := int64(100)
|
||||
originalLevel := petInfo.Level
|
||||
nextLevelNeed := petInfo.NextLvExp - petInfo.Exp
|
||||
if addExp >= nextLevelNeed {
|
||||
t.Fatalf("test setup invalid: addExp=%d should be smaller than next level need=%d", addExp, nextLevelNeed)
|
||||
}
|
||||
|
||||
player.AddPetExp(petInfo, addExp)
|
||||
|
||||
if petInfo.Level != originalLevel {
|
||||
t.Fatalf("expected level to stay at %d, got %d", originalLevel, petInfo.Level)
|
||||
}
|
||||
if petInfo.Exp != addExp {
|
||||
t.Fatalf("expected current exp to increase by %d, got %d", addExp, petInfo.Exp)
|
||||
}
|
||||
if player.Info.ExpPool != 1_000_000-addExp {
|
||||
t.Fatalf("expected exp pool to decrease by %d, got %d", addExp, player.Info.ExpPool)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddPetExpUsesDynamicPerLevelRequirement(t *testing.T) {
|
||||
petID := firstPetIDForTest(t)
|
||||
petInfo := playermodel.GenPetInfo(petID, 31, 0, 0, 20, nil, 0)
|
||||
if petInfo == nil {
|
||||
t.Fatalf("failed to generate test pet")
|
||||
}
|
||||
|
||||
initialNeed := petInfo.NextLvExp
|
||||
if initialNeed <= 0 {
|
||||
t.Fatalf("expected positive exp requirement, got %d", initialNeed)
|
||||
}
|
||||
|
||||
player := &Player{
|
||||
baseplayer: baseplayer{
|
||||
Info: &playermodel.PlayerInfo{
|
||||
ExpPool: 1_000_000,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
player.AddPetExp(petInfo, initialNeed)
|
||||
|
||||
if petInfo.Level != 21 {
|
||||
t.Fatalf("expected pet level to become 21, got %d", petInfo.Level)
|
||||
}
|
||||
if petInfo.Exp != 0 {
|
||||
t.Fatalf("expected level-up to reset current level exp, got %d", petInfo.Exp)
|
||||
}
|
||||
if petInfo.NextLvExp == initialNeed {
|
||||
t.Fatalf("expected next level exp to change after leveling, still %d", petInfo.NextLvExp)
|
||||
}
|
||||
}
|
||||
@@ -228,6 +228,21 @@ func (p *Player) GetSpace() *space.Space {
|
||||
return space.GetSpace(p.Info.MapID)
|
||||
}
|
||||
|
||||
func (p *Player) CurrentMapPetLevelLimit() uint32 {
|
||||
if p == nil {
|
||||
return 100
|
||||
}
|
||||
currentSpace := p.GetSpace()
|
||||
if currentSpace != nil && currentSpace.IsLevelBreakMap {
|
||||
return 0
|
||||
}
|
||||
return 100
|
||||
}
|
||||
|
||||
func (p *Player) IsCurrentMapLevelBreak() bool {
|
||||
return p != nil && p.CurrentMapPetLevelLimit() == 0
|
||||
}
|
||||
|
||||
// CanFight 检查玩家是否可以进行战斗
|
||||
// 0无战斗,1PVP,2,BOOS,3PVE
|
||||
func (p *Player) CanFight() errorcode.ErrorCode {
|
||||
@@ -419,7 +434,15 @@ func (p *Player) ItemAdd(ItemId, ItemCnt int64) (result bool) {
|
||||
p.SendPack(t1.Pack(nil)) //准备包由各自发,因为协议不一样
|
||||
return false
|
||||
}
|
||||
p.Service.Item.UPDATE(uint32(ItemId), gconv.Int(ItemCnt))
|
||||
if err := p.Service.Item.UPDATE(uint32(ItemId), gconv.Int(ItemCnt)); err != nil {
|
||||
cool.Logger.Error(context.TODO(), "item add update failed", p.Info.UserID, ItemId, ItemCnt, err)
|
||||
|
||||
t1 := common.NewTomeeHeader(2601, p.Info.UserID)
|
||||
t1.Result = uint32(errorcode.ErrorCodes.ErrSystemError200007)
|
||||
|
||||
p.SendPack(t1.Pack(nil)) //准备包由各自发,因为协议不一样
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
222
logic/service/player/task_completion.go
Normal file
222
logic/service/player/task_completion.go
Normal file
@@ -0,0 +1,222 @@
|
||||
package player
|
||||
|
||||
import (
|
||||
"blazing/common/data"
|
||||
"blazing/common/socket/errorcode"
|
||||
fightinfo "blazing/logic/service/fight/info"
|
||||
tasklogic "blazing/logic/service/task"
|
||||
configmodel "blazing/modules/config/model"
|
||||
configservice "blazing/modules/config/service"
|
||||
playermodel "blazing/modules/player/model"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// TaskCompletionContext 封装任务完成时的上下文。
|
||||
// 这里除了保留任务配置和默认奖励,也给自定义任务完成逻辑暴露了返回包与开关位,
|
||||
// 用来兼容“固定发物品/精灵”之外的奖励场景。
|
||||
// 这套扩展最初是为任务发放特训技能、皮肤而补上的:
|
||||
// 特训奖励不能完全按静态表直发,需要结合额外条件做特判,
|
||||
// 例如通过挖矿/对话进度限制特训次数,满足条件后再允许完成任务。
|
||||
type TaskCompletionContext struct {
|
||||
TaskID uint32
|
||||
OutState int
|
||||
Config *configmodel.TaskConfig
|
||||
Reward *tasklogic.TaskResult
|
||||
Result *tasklogic.CompleteTaskOutboundInfo
|
||||
|
||||
SkipDefaultReward bool
|
||||
}
|
||||
|
||||
// TaskCompletionHandler 定义任务完成前的自定义处理器。
|
||||
// 处理器可用于补充校验、写入额外奖励,或在任务完全走自定义发奖时跳过默认奖励流程。
|
||||
type TaskCompletionHandler func(*Player, *TaskCompletionContext) errorcode.ErrorCode
|
||||
|
||||
// taskCompletionRegistry 按任务 ID 维护自定义完成处理器。
|
||||
// 默认任务仍然走 task 配表里的固定奖励;只有存在特判需求的任务才在这里注册。
|
||||
var taskCompletionRegistry = struct {
|
||||
sync.RWMutex
|
||||
handlers map[uint32]TaskCompletionHandler
|
||||
}{
|
||||
handlers: make(map[uint32]TaskCompletionHandler),
|
||||
}
|
||||
|
||||
// taskRewardGrantResult 汇总本次任务实际发放的奖励,
|
||||
// 便于后续统一推送给前端展示。
|
||||
type taskRewardGrantResult struct {
|
||||
Pet *playermodel.PetInfo
|
||||
Items []data.ItemInfo
|
||||
}
|
||||
|
||||
// RegisterTaskCompletionHandler 注册任务完成时的自定义处理器。
|
||||
// 用于覆盖“任务奖励固定为物品和精灵”的旧模型,让指定任务在完成前后插入额外逻辑。
|
||||
// 当前这套机制主要服务于特训技能、皮肤等特殊奖励,以及需要额外次数/进度校验的任务。
|
||||
func RegisterTaskCompletionHandler(taskID uint32, handler TaskCompletionHandler) {
|
||||
if taskID == 0 || handler == nil {
|
||||
return
|
||||
}
|
||||
|
||||
taskCompletionRegistry.Lock()
|
||||
taskCompletionRegistry.handlers[taskID] = handler
|
||||
taskCompletionRegistry.Unlock()
|
||||
}
|
||||
|
||||
// RegisterTaskTalkLimitHandler 注册一个基于挖矿/采集对话进度的完成限制。
|
||||
// 历史上特训任务需要通过挖矿次数限制可领取次数,因此复用了 Talk 进度作为准入条件。
|
||||
// 当指定 talkID 的进度不足 needCount 时,任务不能完成也不能领奖。
|
||||
func RegisterTaskTalkLimitHandler(taskID, talkID, needCount uint32) {
|
||||
RegisterTaskCompletionHandler(taskID, func(p *Player, _ *TaskCompletionContext) errorcode.ErrorCode {
|
||||
if p == nil || p.Service == nil || p.Service.Talk == nil {
|
||||
return errorcode.ErrorCodes.ErrSystemError
|
||||
}
|
||||
|
||||
currentCount, ok := p.Service.Talk.Progress(int(talkID))
|
||||
if !ok || currentCount < needCount {
|
||||
return errorcode.ErrorCodes.ErrNeedCompleteTaskForPrize
|
||||
}
|
||||
|
||||
return 0
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Player) getTaskGift(taskID int, outState int) *tasklogic.TaskResult {
|
||||
if taskID <= 0 {
|
||||
return nil
|
||||
}
|
||||
return tasklogic.GetTaskInfo(taskID, outState)
|
||||
}
|
||||
|
||||
// hasTaskCompletionHandler 判断任务是否存在自定义完成处理器。
|
||||
func hasTaskCompletionHandler(taskID uint32) bool {
|
||||
taskCompletionRegistry.RLock()
|
||||
_, ok := taskCompletionRegistry.handlers[taskID]
|
||||
taskCompletionRegistry.RUnlock()
|
||||
return ok
|
||||
}
|
||||
|
||||
// getTaskCompletionHandler 获取任务的自定义完成处理器。
|
||||
func getTaskCompletionHandler(taskID uint32) TaskCompletionHandler {
|
||||
taskCompletionRegistry.RLock()
|
||||
handler := taskCompletionRegistry.handlers[taskID]
|
||||
taskCompletionRegistry.RUnlock()
|
||||
return handler
|
||||
}
|
||||
|
||||
// canCompleteTaskReward 判断任务是否具备可执行的奖励逻辑。
|
||||
// 只要存在默认奖励,或已注册自定义处理器,就允许进入完成流程。
|
||||
func (p *Player) canCompleteTaskReward(taskID, outState int) bool {
|
||||
if taskID <= 0 {
|
||||
return false
|
||||
}
|
||||
return p.getTaskGift(taskID, outState) != nil || hasTaskCompletionHandler(uint32(taskID))
|
||||
}
|
||||
|
||||
// ApplyTaskCompletion 执行任务完成时的奖励发放入口。
|
||||
// 流程分两层:
|
||||
// 1. 先执行自定义处理器,处理特训/皮肤/额外次数校验等特殊逻辑;
|
||||
// 2. 若未要求跳过默认奖励,再回落到原有的物品/精灵发奖逻辑。
|
||||
func (p *Player) ApplyTaskCompletion(taskID uint32, outState int, result *tasklogic.CompleteTaskOutboundInfo) (*taskRewardGrantResult, errorcode.ErrorCode) {
|
||||
if p == nil {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemError
|
||||
}
|
||||
|
||||
ctx := &TaskCompletionContext{
|
||||
TaskID: taskID,
|
||||
OutState: outState,
|
||||
Config: configservice.NewTaskService().Get(int(taskID), outState),
|
||||
Reward: tasklogic.GetTaskInfo(int(taskID), outState),
|
||||
Result: result,
|
||||
}
|
||||
|
||||
if ctx.Reward == nil && !hasTaskCompletionHandler(taskID) {
|
||||
return nil, errorcode.ErrorCodes.ErrNeedCompleteTaskForPrize
|
||||
}
|
||||
|
||||
if handler := getTaskCompletionHandler(taskID); handler != nil {
|
||||
if err := handler(p, ctx); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.SkipDefaultReward {
|
||||
if result != nil {
|
||||
result.ItemLen = uint32(len(result.ItemList))
|
||||
}
|
||||
return &taskRewardGrantResult{Items: make([]data.ItemInfo, 0)}, 0
|
||||
}
|
||||
|
||||
if ctx.Reward == nil {
|
||||
return nil, errorcode.ErrorCodes.ErrNeedCompleteTaskForPrize
|
||||
}
|
||||
|
||||
return p.grantTaskReward(ctx.Reward, result), 0
|
||||
}
|
||||
|
||||
// grantTaskReward 发放 task 配表里的默认奖励。
|
||||
// 这里仍负责原有的固定奖励模型:物品、精灵、称号,以及配置里声明的任务宠奖励。
|
||||
func (p *Player) grantTaskReward(reward *tasklogic.TaskResult, result *tasklogic.CompleteTaskOutboundInfo) *taskRewardGrantResult {
|
||||
granted := &taskRewardGrantResult{
|
||||
Items: make([]data.ItemInfo, 0),
|
||||
}
|
||||
|
||||
if reward == nil {
|
||||
if result != nil {
|
||||
result.ItemLen = uint32(len(result.ItemList))
|
||||
}
|
||||
return granted
|
||||
}
|
||||
|
||||
if reward.Pet != nil {
|
||||
p.Service.Pet.PetAdd(reward.Pet, 0)
|
||||
granted.Pet = reward.Pet
|
||||
if result != nil {
|
||||
result.CaptureTime = reward.Pet.CatchTime
|
||||
result.PetTypeId = reward.Pet.ID
|
||||
}
|
||||
}
|
||||
|
||||
for _, item := range reward.ItemList {
|
||||
if !p.ItemAdd(item.ItemId, item.ItemCnt) {
|
||||
continue
|
||||
}
|
||||
granted.Items = append(granted.Items, item)
|
||||
if result != nil {
|
||||
result.ItemList = append(result.ItemList, item)
|
||||
}
|
||||
}
|
||||
|
||||
if reward.Title != 0 {
|
||||
p.GiveTitle(reward.Title)
|
||||
}
|
||||
if reward.RewardPetID != 0 {
|
||||
p.GrantTaskPetRewards(reward.RewardPetID, reward.TrainSkillIDs, reward.SkinIDs)
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
result.ItemLen = uint32(len(result.ItemList))
|
||||
}
|
||||
return granted
|
||||
}
|
||||
|
||||
// SendTaskCompletionBonus 将任务奖励转换为旧的奖励展示协议并推送给前端。
|
||||
func (p *Player) SendTaskCompletionBonus(bonusID uint32, granted *taskRewardGrantResult) {
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
|
||||
res := &fightinfo.S2C_GET_BOSS_MONSTER{
|
||||
BonusID: bonusID,
|
||||
}
|
||||
if granted != nil && granted.Pet != nil {
|
||||
res.PetID = granted.Pet.ID
|
||||
res.CaptureTm = granted.Pet.CatchTime
|
||||
}
|
||||
if granted != nil {
|
||||
for _, item := range granted.Items {
|
||||
res.AddItemInfo(item)
|
||||
}
|
||||
}
|
||||
|
||||
if res.HasReward() {
|
||||
p.SendPackCmd(8004, res)
|
||||
}
|
||||
}
|
||||
115
logic/service/player/task_reward.go
Normal file
115
logic/service/player/task_reward.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package player
|
||||
|
||||
import "blazing/modules/player/model"
|
||||
|
||||
func applyTaskRewardToPetInfo(pet *model.PetInfo, trainSkillIDs, skinIDs []uint32) bool {
|
||||
if pet == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
changed := false
|
||||
|
||||
mergedSkills := mergeTaskRewardIDs(pet.ExtSKill, trainSkillIDs)
|
||||
if len(mergedSkills) != len(pet.ExtSKill) {
|
||||
pet.ExtSKill = mergedSkills
|
||||
changed = true
|
||||
}
|
||||
|
||||
mergedSkins := mergeTaskRewardIDs(pet.ExtSkin, skinIDs)
|
||||
if len(mergedSkins) != len(pet.ExtSkin) {
|
||||
pet.ExtSkin = mergedSkins
|
||||
changed = true
|
||||
}
|
||||
|
||||
if pet.SkinID == 0 {
|
||||
for _, skinID := range mergedSkins {
|
||||
if skinID == 0 {
|
||||
continue
|
||||
}
|
||||
pet.SkinID = skinID
|
||||
changed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return changed
|
||||
}
|
||||
|
||||
func mergeTaskRewardIDs(dst []uint32, src []uint32) []uint32 {
|
||||
if len(src) == 0 {
|
||||
return dst
|
||||
}
|
||||
|
||||
seen := make(map[uint32]struct{}, len(dst)+len(src))
|
||||
result := make([]uint32, 0, len(dst)+len(src))
|
||||
|
||||
for _, id := range dst {
|
||||
if id == 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[id]; ok {
|
||||
continue
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
result = append(result, id)
|
||||
}
|
||||
|
||||
for _, id := range src {
|
||||
if id == 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[id]; ok {
|
||||
continue
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
result = append(result, id)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *Player) GrantTaskPetRewards(targetPetID uint32, trainSkillIDs, skinIDs []uint32) bool {
|
||||
if p == nil || targetPetID == 0 || (len(trainSkillIDs) == 0 && len(skinIDs) == 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
changed := false
|
||||
|
||||
for i := range p.Info.PetList {
|
||||
if p.Info.PetList[i].ID != targetPetID {
|
||||
continue
|
||||
}
|
||||
if applyTaskRewardToPetInfo(&p.Info.PetList[i], trainSkillIDs, skinIDs) {
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
|
||||
for i := range p.Info.BackupPetList {
|
||||
if p.Info.BackupPetList[i].ID != targetPetID {
|
||||
continue
|
||||
}
|
||||
if applyTaskRewardToPetInfo(&p.Info.BackupPetList[i], trainSkillIDs, skinIDs) {
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
|
||||
if p.Service == nil || p.Service.Pet == nil {
|
||||
return changed
|
||||
}
|
||||
|
||||
allPets := p.Service.Pet.PetInfo(0)
|
||||
for i := range allPets {
|
||||
if allPets[i].Data.ID != targetPetID {
|
||||
continue
|
||||
}
|
||||
if !applyTaskRewardToPetInfo(&allPets[i].Data, trainSkillIDs, skinIDs) {
|
||||
continue
|
||||
}
|
||||
allPets[i].Data.CatchTime = allPets[i].CatchTime
|
||||
if p.Service.Pet.Update(allPets[i].Data) {
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
|
||||
return changed
|
||||
}
|
||||
@@ -42,10 +42,11 @@ type Space struct {
|
||||
WeatherType []uint32
|
||||
TimeBoss info.S2C_2022
|
||||
|
||||
IsTime bool
|
||||
DropItemIds []uint32
|
||||
PitS *csmap.CsMap[int, []model.MapPit]
|
||||
MapNodeS *csmap.CsMap[uint32, *model.MapNode]
|
||||
IsTime bool
|
||||
IsLevelBreakMap bool
|
||||
DropItemIds []uint32
|
||||
PitS *csmap.CsMap[int, []model.MapPit]
|
||||
MapNodeS *csmap.CsMap[uint32, *model.MapNode]
|
||||
}
|
||||
|
||||
func NewSpace() *Space {
|
||||
@@ -185,6 +186,9 @@ func (ret *Space) init() {
|
||||
if r.IsTimeSpace != 0 {
|
||||
ret.IsTime = true
|
||||
}
|
||||
if r.IsLevelBreakMap != 0 {
|
||||
ret.IsLevelBreakMap = true
|
||||
}
|
||||
ret.MapBossSInfo = info.MapModelBroadcastInfo{}
|
||||
ret.MapBossSInfo.INFO = make([]info.MapModelBroadcastEntry, 0)
|
||||
|
||||
|
||||
@@ -10,8 +10,11 @@ import (
|
||||
type TaskResult struct {
|
||||
Pet *model.PetInfo `json:"petTypeId" description:"发放的精灵ID"` // 发放的精灵ID,
|
||||
|
||||
ItemList []data.ItemInfo `json:"itemList" description:"发放物品的数组"` // 发放物品的数组,
|
||||
Title uint32 `json:"title" description:"称号奖励"`
|
||||
ItemList []data.ItemInfo `json:"itemList" description:"发放物品的数组"` // 发放物品的数组,
|
||||
Title uint32 `json:"title" description:"称号奖励"`
|
||||
RewardPetID uint32 `json:"rewardPetId" description:"宠物相关奖励目标精灵ID"`
|
||||
TrainSkillIDs []uint32 `json:"trainSkillIds" description:"特训技能奖励"`
|
||||
SkinIDs []uint32 `json:"skinIds" description:"皮肤奖励"`
|
||||
}
|
||||
|
||||
func GetTaskInfo(id, ot int) *TaskResult {
|
||||
@@ -26,6 +29,12 @@ func GetTaskInfo(id, ot int) *TaskResult {
|
||||
if pet != nil {
|
||||
ret.Pet = model.GenPetInfo(int(pet.MonID), int(pet.DV), int(pet.Nature), int(pet.Effect), int(pet.Lv), nil, 0)
|
||||
}
|
||||
ret.RewardPetID = r.RewardPetID
|
||||
ret.TrainSkillIDs = append(ret.TrainSkillIDs, r.TrainSkillIDs...)
|
||||
ret.SkinIDs = append(ret.SkinIDs, r.SkinIDs...)
|
||||
if ret.Pet != nil {
|
||||
applyTaskRewardPetExtras(ret.Pet, ret.TrainSkillIDs, ret.SkinIDs)
|
||||
}
|
||||
|
||||
for _, itemID := range r.ItemRewardIds {
|
||||
iteminfo := service.NewItemService().GetItemCount(itemID)
|
||||
@@ -39,3 +48,59 @@ func GetTaskInfo(id, ot int) *TaskResult {
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func applyTaskRewardPetExtras(pet *model.PetInfo, trainSkillIDs, skinIDs []uint32) {
|
||||
if pet == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if merged := mergeTaskRewardIDs(pet.ExtSKill, trainSkillIDs); len(merged) != len(pet.ExtSKill) {
|
||||
pet.ExtSKill = merged
|
||||
}
|
||||
|
||||
mergedSkins := mergeTaskRewardIDs(pet.ExtSkin, skinIDs)
|
||||
if len(mergedSkins) != len(pet.ExtSkin) {
|
||||
pet.ExtSkin = mergedSkins
|
||||
}
|
||||
if pet.SkinID == 0 {
|
||||
for _, skinID := range mergedSkins {
|
||||
if skinID != 0 {
|
||||
pet.SkinID = skinID
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mergeTaskRewardIDs(dst []uint32, src []uint32) []uint32 {
|
||||
if len(src) == 0 {
|
||||
return dst
|
||||
}
|
||||
|
||||
seen := make(map[uint32]struct{}, len(dst)+len(src))
|
||||
result := make([]uint32, 0, len(dst)+len(src))
|
||||
|
||||
for _, id := range dst {
|
||||
if id == 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[id]; ok {
|
||||
continue
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
result = append(result, id)
|
||||
}
|
||||
|
||||
for _, id := range src {
|
||||
if id == 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[id]; ok {
|
||||
continue
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
result = append(result, id)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1,94 +1,97 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"blazing/common/rpc"
|
||||
"blazing/cool"
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/yudeguang/ratelimit"
|
||||
|
||||
i18n "blazing/modules/base/middleware"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gcmd"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/xiaoqidun/qqwry"
|
||||
)
|
||||
|
||||
var (
|
||||
Main = gcmd.Command{
|
||||
Name: "main",
|
||||
Usage: "main",
|
||||
Brief: "start http server",
|
||||
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
|
||||
|
||||
r := parser.GetOpt("debug", false)
|
||||
if r.Bool() {
|
||||
g.DB().SetDebug(true)
|
||||
// service.NewServerService().SetServerScreen(0, "sss")
|
||||
cool.Config.ServerInfo.IsDebug = 1
|
||||
}
|
||||
if cool.IsRedisMode {
|
||||
go rpc.ListenFunc(ctx)
|
||||
}
|
||||
// // 从文件加载IP数据库
|
||||
if err := qqwry.LoadFile("public/qqwry.ipdb"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//go robot()
|
||||
//go reg()
|
||||
go startrobot()
|
||||
s := g.Server()
|
||||
s.Use(Limiter, ghttp.MiddlewareHandlerResponse)
|
||||
s.EnableAdmin()
|
||||
s.SetServerAgent(cool.Config.Name)
|
||||
s.BindHookHandler("/*", ghttp.HookBeforeServe, beforeServeHook)
|
||||
// runtime.SetMutexProfileFraction(1) // (非必需)开启对锁调用的跟踪
|
||||
// runtime.SetBlockProfileRate(1) // (非必需)开启对阻塞操作的跟踪
|
||||
// s.EnablePProf()
|
||||
// 如果存在 data/cool-admin-vue/dist 目录,则设置为主目录
|
||||
if gfile.IsDir("public") {
|
||||
s.SetServerRoot("public")
|
||||
}
|
||||
// i18n 信息
|
||||
s.BindHandler("/i18n", i18n.I18nInfo)
|
||||
// g.Server().BindMiddleware("/*", MiddlewareCORS)
|
||||
s.Run()
|
||||
return nil
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func beforeServeHook(r *ghttp.Request) {
|
||||
//glog.Debugf(r.GetCtx(), "beforeServeHook [is file:%v] URI:%s", r.IsFileRequest(), r.RequestURI)
|
||||
r.Response.CORSDefault()
|
||||
}
|
||||
|
||||
// var limiter = rate.NewLimiter(rate.Limit(150), 50)
|
||||
var limiter *ratelimit.Rule = ratelimit.NewRule()
|
||||
|
||||
// 简单规则案例
|
||||
func init() {
|
||||
|
||||
//步骤二:增加一条或者多条规则组成复合规则,此复合规则必须至少包含一条规则
|
||||
limiter.AddRule(time.Second*1, 20)
|
||||
//步骤三:调用函数判断某用户是否允许访问 allow:= r.AllowVisit(user)
|
||||
|
||||
}
|
||||
|
||||
// Limiter is a middleware that implements rate limiting for all HTTP requests.
|
||||
// It returns HTTP 429 (Too Many Requests) when the rate limit is exceeded.
|
||||
func Limiter(r *ghttp.Request) {
|
||||
// 3. 为任意键 "some-key" 获取一个速率限制器
|
||||
// - rate.Limit(2): 表示速率为 "每秒2个请求"
|
||||
// - 2: 表示桶的容量 (Burst),允许瞬时处理2个请求
|
||||
ip := r.GetClientIp()
|
||||
if !limiter.AllowVisitByIP4(ip) {
|
||||
r.Response.WriteStatusExit(429) // Return 429 Too Many Requests
|
||||
|
||||
r.ExitAll()
|
||||
}
|
||||
r.Middleware.Next()
|
||||
}
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"blazing/common/rpc"
|
||||
"blazing/cool"
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/yudeguang/ratelimit"
|
||||
|
||||
i18n "blazing/modules/base/middleware"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gcmd"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/xiaoqidun/qqwry"
|
||||
)
|
||||
|
||||
var (
|
||||
Main = gcmd.Command{
|
||||
Name: "main",
|
||||
Usage: "main",
|
||||
Brief: "start http server",
|
||||
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
|
||||
|
||||
r := parser.GetOpt("debug", false)
|
||||
if r.Bool() {
|
||||
g.DB().SetDebug(true)
|
||||
// service.NewServerService().SetServerScreen(0, "sss")
|
||||
cool.Config.ServerInfo.IsDebug = 1
|
||||
}
|
||||
if err = cool.RunAutoMigrate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if cool.IsRedisMode {
|
||||
go rpc.ListenFunc(ctx)
|
||||
}
|
||||
// // 从文件加载IP数据库
|
||||
if err := qqwry.LoadFile("public/qqwry.ipdb"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//go robot()
|
||||
//go reg()
|
||||
go startrobot()
|
||||
s := g.Server()
|
||||
s.Use(Limiter, ghttp.MiddlewareHandlerResponse)
|
||||
s.EnableAdmin()
|
||||
s.SetServerAgent(cool.Config.Name)
|
||||
s.BindHookHandler("/*", ghttp.HookBeforeServe, beforeServeHook)
|
||||
// runtime.SetMutexProfileFraction(1) // (非必需)开启对锁调用的跟踪
|
||||
// runtime.SetBlockProfileRate(1) // (非必需)开启对阻塞操作的跟踪
|
||||
// s.EnablePProf()
|
||||
// 如果存在 data/cool-admin-vue/dist 目录,则设置为主目录
|
||||
if gfile.IsDir("public") {
|
||||
s.SetServerRoot("public")
|
||||
}
|
||||
// i18n 信息
|
||||
s.BindHandler("/i18n", i18n.I18nInfo)
|
||||
// g.Server().BindMiddleware("/*", MiddlewareCORS)
|
||||
s.Run()
|
||||
return nil
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func beforeServeHook(r *ghttp.Request) {
|
||||
//glog.Debugf(r.GetCtx(), "beforeServeHook [is file:%v] URI:%s", r.IsFileRequest(), r.RequestURI)
|
||||
r.Response.CORSDefault()
|
||||
}
|
||||
|
||||
// var limiter = rate.NewLimiter(rate.Limit(150), 50)
|
||||
var limiter *ratelimit.Rule = ratelimit.NewRule()
|
||||
|
||||
// 简单规则案例
|
||||
func init() {
|
||||
|
||||
//步骤二:增加一条或者多条规则组成复合规则,此复合规则必须至少包含一条规则
|
||||
limiter.AddRule(time.Second*1, 20)
|
||||
//步骤三:调用函数判断某用户是否允许访问 allow:= r.AllowVisit(user)
|
||||
|
||||
}
|
||||
|
||||
// Limiter is a middleware that implements rate limiting for all HTTP requests.
|
||||
// It returns HTTP 429 (Too Many Requests) when the rate limit is exceeded.
|
||||
func Limiter(r *ghttp.Request) {
|
||||
// 3. 为任意键 "some-key" 获取一个速率限制器
|
||||
// - rate.Limit(2): 表示速率为 "每秒2个请求"
|
||||
// - 2: 表示桶的容量 (Burst),允许瞬时处理2个请求
|
||||
ip := r.GetClientIp()
|
||||
if !limiter.AllowVisitByIP4(ip) {
|
||||
r.Response.WriteStatusExit(429) // Return 429 Too Many Requests
|
||||
|
||||
r.ExitAll()
|
||||
}
|
||||
r.Middleware.Next()
|
||||
}
|
||||
|
||||
@@ -1,86 +1,86 @@
|
||||
server:
|
||||
name: "blazing server"
|
||||
address: ":15948" #前端服务器+rpc地址
|
||||
openapiPath: "/api.json"
|
||||
swaggerPath: "/swagger"
|
||||
clientMaxBodySize:
|
||||
20971520 # 20MB in bytes 20*1024*1024
|
||||
# 平滑重启特性
|
||||
graceful: true # 是否开启平滑重启特性,开启时将会在本地增加10000的本地TCP端口用于进程间通信。默认false
|
||||
gracefulTimeout: 2 # 父进程在平滑重启后多少秒退出,默认2秒。若请求耗时大于该值,可能会导致请求中断
|
||||
gracefulShutdownTimeout: 5 # 关闭Server时如果存在正在执行的HTTP请求,Server等待多少秒才执行强行关闭
|
||||
logger:
|
||||
level: "all"
|
||||
stdout: true
|
||||
|
||||
database:
|
||||
default:
|
||||
type: "pgsql"
|
||||
host: "43.248.3.21"
|
||||
port: "5432"
|
||||
user: "user_YrK4j7"
|
||||
pass: "password_jSDm76"
|
||||
name: "bl"
|
||||
debug: false
|
||||
timezone: "Asia/Shanghai"
|
||||
createdAt: "createTime"
|
||||
updatedAt: "updateTime"
|
||||
#timeMaintainDisabled: false # (可选)是否完全关闭时间更新特性,为true时CreatedAt/UpdatedAt/DeletedAt都将失效
|
||||
# deletedAt: "deleteTime"
|
||||
# default:
|
||||
# type: "mysql"
|
||||
# host: "159.75.107.160"
|
||||
# port: "3306"
|
||||
# user: "blazing"
|
||||
# pass: "zjjSrsBMrB5RmjE7"
|
||||
# name: "blazing"
|
||||
# charset: "utf8mb4"
|
||||
# timezone: "Asia/Shanghai"
|
||||
# debug: true
|
||||
# createdAt: "createTime"
|
||||
# updatedAt: "updateTime"
|
||||
# deletedAt: "deleteTime"
|
||||
# baseConfig:
|
||||
# type: "sqlite"
|
||||
# link: "base-config.sqlite"
|
||||
# extra: busy_timeout=5000
|
||||
# createdAt: "createTime"
|
||||
# updatedAt: "updateTime"
|
||||
# debug: true
|
||||
|
||||
# Redis 配置示例
|
||||
redis:
|
||||
cool:
|
||||
address: "43.248.3.21:6379"
|
||||
db: 0
|
||||
pass: "redis_TxYnSy"
|
||||
|
||||
blazing:
|
||||
autoMigrate: true
|
||||
eps: true
|
||||
file:
|
||||
mode: "local" # local | minio | oss
|
||||
#前端上传地址,因为放弃本地,所以这个弃用了 ,现在被当成rpc地址
|
||||
domain: "61.147.247.41"
|
||||
# oss配置项兼容 minio oss 需要配置bucket公开读
|
||||
oss:
|
||||
endpoint: "192.168.192.110:9000"
|
||||
accessKeyID: "accessKeyID"
|
||||
secretAccessKey: "secretAccessKey"
|
||||
bucketName: "blazing"
|
||||
useSSL: false #minio用到
|
||||
location: "us-east-1" #minio用到
|
||||
|
||||
modules:
|
||||
base:
|
||||
jwt:
|
||||
sso: false
|
||||
secret: "sun-base88776655"
|
||||
token:
|
||||
expire: 7200 # 2*3600
|
||||
refreshExpire: 1296000 # 24*3600*15
|
||||
middleware:
|
||||
authority:
|
||||
enable: true
|
||||
log:
|
||||
enable: true
|
||||
server:
|
||||
name: "blazing server"
|
||||
address: ":15948" #前端服务器+rpc地址
|
||||
openapiPath: "/api.json"
|
||||
swaggerPath: "/swagger"
|
||||
clientMaxBodySize:
|
||||
20971520 # 20MB in bytes 20*1024*1024
|
||||
# 平滑重启特性
|
||||
# graceful: true # 是否开启平滑重启特性,开启时将会在本地增加10000的本地TCP端口用于进程间通信。默认false
|
||||
# gracefulTimeout: 2 # 父进程在平滑重启后多少秒退出,默认2秒。若请求耗时大于该值,可能会导致请求中断
|
||||
# gracefulShutdownTimeout: 5 # 关闭Server时如果存在正在执行的HTTP请求,Server等待多少秒才执行强行关闭
|
||||
logger:
|
||||
level: "all"
|
||||
stdout: true
|
||||
|
||||
database:
|
||||
default:
|
||||
type: "pgsql"
|
||||
host: "43.248.3.21"
|
||||
port: "5432"
|
||||
user: "user_YrK4j7"
|
||||
pass: "password_jSDm76"
|
||||
name: "bl"
|
||||
debug: false
|
||||
timezone: "Asia/Shanghai"
|
||||
createdAt: "createTime"
|
||||
updatedAt: "updateTime"
|
||||
#timeMaintainDisabled: false # (可选)是否完全关闭时间更新特性,为true时CreatedAt/UpdatedAt/DeletedAt都将失效
|
||||
# deletedAt: "deleteTime"
|
||||
# default:
|
||||
# type: "mysql"
|
||||
# host: "159.75.107.160"
|
||||
# port: "3306"
|
||||
# user: "blazing"
|
||||
# pass: "zjjSrsBMrB5RmjE7"
|
||||
# name: "blazing"
|
||||
# charset: "utf8mb4"
|
||||
# timezone: "Asia/Shanghai"
|
||||
# debug: true
|
||||
# createdAt: "createTime"
|
||||
# updatedAt: "updateTime"
|
||||
# deletedAt: "deleteTime"
|
||||
# baseConfig:
|
||||
# type: "sqlite"
|
||||
# link: "base-config.sqlite"
|
||||
# extra: busy_timeout=5000
|
||||
# createdAt: "createTime"
|
||||
# updatedAt: "updateTime"
|
||||
# debug: true
|
||||
|
||||
# Redis 配置示例
|
||||
redis:
|
||||
cool:
|
||||
address: "43.248.3.21:6379"
|
||||
db: 0
|
||||
pass: "redis_TxYnSy"
|
||||
|
||||
blazing:
|
||||
autoMigrate: true
|
||||
eps: true
|
||||
file:
|
||||
mode: "local" # local | minio | oss
|
||||
#前端上传地址,因为放弃本地,所以这个弃用了 ,现在被当成rpc地址
|
||||
domain: "43.248.3.21"
|
||||
# oss配置项兼容 minio oss 需要配置bucket公开读
|
||||
oss:
|
||||
endpoint: "192.168.192.110:9000"
|
||||
accessKeyID: "accessKeyID"
|
||||
secretAccessKey: "secretAccessKey"
|
||||
bucketName: "blazing"
|
||||
useSSL: false #minio用到
|
||||
location: "us-east-1" #minio用到
|
||||
|
||||
modules:
|
||||
base:
|
||||
jwt:
|
||||
sso: false
|
||||
secret: "sun-base88776655"
|
||||
token:
|
||||
expire: 7200 # 2*3600
|
||||
refreshExpire: 1296000 # 24*3600*15
|
||||
middleware:
|
||||
authority:
|
||||
enable: true
|
||||
log:
|
||||
enable: true
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
playerservice "blazing/modules/player/service"
|
||||
|
||||
"github.com/deatil/go-cryptobin/cryptobin/crypto"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
@@ -123,8 +122,8 @@ type SessionRes struct {
|
||||
UserID int `json:"userid"`
|
||||
Session string `json:"session"`
|
||||
|
||||
Server gdb.List `json:"server"`
|
||||
PetID []int `json:"petid"`
|
||||
Server []config.ServerShowInfo `json:"server"`
|
||||
PetID []int `json:"petid"`
|
||||
}
|
||||
|
||||
type RegReq struct {
|
||||
|
||||
@@ -1,211 +1,213 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"blazing/common/rpc"
|
||||
"blazing/cool"
|
||||
"blazing/modules/base/config"
|
||||
"blazing/modules/config/service"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/google/uuid"
|
||||
"github.com/lxzan/gws"
|
||||
)
|
||||
|
||||
const UpStream = "http://43.248.3.21:45632/"
|
||||
|
||||
func MiddlewareCORS(r *ghttp.Request) {
|
||||
r.Response.CORSDefault()
|
||||
corsOptions := r.Response.DefaultCORSOptions()
|
||||
corsOptions.AllowDomain = []string{"*", "localhost", "tauri.localhost"}
|
||||
if !r.Response.CORSAllowedOrigin(corsOptions) {
|
||||
r.Response.WriteStatus(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
r.Response.CORS(corsOptions)
|
||||
if r.Response.Request.Method == "OPTIONS" {
|
||||
r.Response.WriteStatus(http.StatusOK)
|
||||
return
|
||||
}
|
||||
// fmt.Println(r.Response.Header())
|
||||
//g.Dump(r.Response.Server.SetConfig(gtt))
|
||||
//r.Response.Header().Del("Server") // 删除Server头
|
||||
// r.Response.Header().Set("Server", "blazing")
|
||||
r.Middleware.Next()
|
||||
}
|
||||
func StartServerProxy() {
|
||||
s := g.Server()
|
||||
// Parse the upstream URL
|
||||
u, _ := url.Parse(UpStream)
|
||||
// Create a new reverse proxy instance
|
||||
proxy := httputil.NewSingleHostReverseProxy(u)
|
||||
// Configure error handling for proxy failures
|
||||
proxy.ErrorHandler = func(writer http.ResponseWriter, request *http.Request, e error) {
|
||||
writer.WriteHeader(http.StatusBadGateway)
|
||||
}
|
||||
|
||||
s.BindHandler("/bbs/api/fof/upload", func(r *ghttp.Request) {
|
||||
// 1. 调用上传方法(仍返回单个URL字符串,不改动Upload方法)
|
||||
urlStr, err := cool.File().Upload(r.Context())
|
||||
|
||||
// 2. 错误处理:返回标准化错误JSON
|
||||
if err != nil {
|
||||
r.Response.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
r.Response.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(r.Response.Writer).Encode(map[string]interface{}{
|
||||
"code": 1,
|
||||
"msg": err.Error(),
|
||||
"data": nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 3. 基于返回的URL,构造完整的JSON结构体(和示例完全一致)
|
||||
// 解析URL中的文件名和路径(从urlStr中提取)
|
||||
baseName := filepath.Base(urlStr) // 提取文件名(如13e8d062-xxx.jpg)
|
||||
dir := gtime.Now().Format("Y-m-d") // 日期目录(2026-02-07)
|
||||
path := fmt.Sprintf("%s/%s", dir, baseName) // 拼接path字段
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
randomID := strconv.Itoa(rand.Intn(1000)) // 模拟ID(如54)
|
||||
uuidStr := uuid.New().String() // 生成UUID
|
||||
humanSize := "743kB" // 模拟易读大小(可根据实际需求优化)
|
||||
fileSize := int64(760783) // 模拟文件大小(字节)
|
||||
|
||||
// 构造和示例完全一致的响应结构体
|
||||
fullResponse := map[string]interface{}{
|
||||
"data": []map[string]interface{}{
|
||||
{
|
||||
"type": "files",
|
||||
"id": randomID,
|
||||
"attributes": map[string]interface{}{
|
||||
"baseName": baseName,
|
||||
"path": path,
|
||||
"url": urlStr, // 用Upload返回的URL
|
||||
"type": "image/jpeg", // 模拟MIME类型
|
||||
"size": fileSize,
|
||||
"humanSize": humanSize,
|
||||
"createdAt": nil, // null
|
||||
"uuid": uuidStr,
|
||||
"tag": "just-url",
|
||||
"hidden": false,
|
||||
"bbcode": `[img]` + urlStr + `[/img]`, // 和URL一致
|
||||
"shared": false,
|
||||
"canViewInfo": false,
|
||||
"canHide": true,
|
||||
"canDelete": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// 4. 输出完整JSON响应
|
||||
r.Response.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
if err := json.NewEncoder(r.Response.Writer).Encode(fullResponse); err != nil {
|
||||
// 兜底错误
|
||||
fmt.Fprintf(r.Response.Writer, `{"code":1,"msg":"响应生成失败:%s","data":null}`, err.Error())
|
||||
}
|
||||
})
|
||||
// Handle all requests with path prefix "/proxy/*"
|
||||
s.BindHandler("/bbs/*url", func(r *ghttp.Request) {
|
||||
var (
|
||||
//originalPath = r.Request.URL.Path
|
||||
proxyToPath = "/" + r.Get("url").String()
|
||||
)
|
||||
// Rewrite the request path
|
||||
r.Request.URL.Path = proxyToPath
|
||||
// Log the proxy operation
|
||||
//g.Log().Infof(r.Context(), `proxy:"%s" -> backend:"%s"`, originalPath, proxyToPath)
|
||||
// Ensure request body can be read multiple times if needed
|
||||
r.MakeBodyRepeatableRead(false)
|
||||
// Forward the request to the backend server
|
||||
proxy.ServeHTTP(r.Response.Writer, r.Request)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func init() {
|
||||
if config.Config.Middleware.Authority.Enable {
|
||||
g.Server().BindMiddleware("/admin/*/open/*", BaseAuthorityMiddlewareOpen)
|
||||
g.Server().BindMiddleware("/rpc/*", BaseAuthorityMiddlewareOpen)
|
||||
g.Server().BindMiddleware("/admin/*/comm/*", BaseAuthorityMiddlewareComm)
|
||||
g.Server().BindMiddleware("/admin/*", BaseAuthorityMiddleware)
|
||||
// g.Server().BindMiddleware("/*", AutoI18n)
|
||||
g.Server().BindMiddleware("/*", MiddlewareCORS)
|
||||
|
||||
}
|
||||
if config.Config.Middleware.Log.Enable {
|
||||
g.Server().BindMiddleware("/admin/*", BaseLog)
|
||||
}
|
||||
StartServerProxy()
|
||||
tt := rpc.CServer()
|
||||
|
||||
g.Server().BindHandler("/rpc/*", func(r *ghttp.Request) {
|
||||
|
||||
tt.ServeHTTP(r.Response.Writer, r.Request)
|
||||
|
||||
})
|
||||
g.Server().Use(CompressMiddleware)
|
||||
g.Server().BindHandler("/server/*", func(r *ghttp.Request) {
|
||||
servert := new(ServerHandler)
|
||||
id := gconv.Uint16(r.URL.Query().Get("id"))
|
||||
servert.isinstall = gconv.Uint32(r.URL.Query().Get("isinstall"))
|
||||
servert.ServerList = service.NewServerService().StartUPdate(id, int(servert.isinstall))
|
||||
|
||||
upgrader := gws.NewUpgrader(servert, &gws.ServerOption{
|
||||
|
||||
Authorize: func(_ *http.Request, _ gws.SessionStorage) bool {
|
||||
|
||||
tokenString := r.URL.Query().Get("Authorization")
|
||||
token, err := jwt.ParseWithClaims(tokenString, &cool.Claims{}, func(_ *jwt.Token) (interface{}, error) {
|
||||
|
||||
return []byte(config.Config.Jwt.Secret), nil
|
||||
})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if !token.Valid {
|
||||
return false
|
||||
}
|
||||
admin := token.Claims.(*cool.Claims)
|
||||
if admin.UserId != 10001 {
|
||||
return false
|
||||
}
|
||||
|
||||
// var name = r.URL.Query().Get("name")
|
||||
// if name == "" {
|
||||
// return false
|
||||
// }
|
||||
// t, _ := service.NewBaseSysUserService().Person(admin.UserID)
|
||||
|
||||
//Loger.Debug(context.TODO(), t.Mimi)
|
||||
// session.Store("name", t.Mimi)
|
||||
//session.Store("key", r.Header.Get("Sec-WebSocket-Key"))
|
||||
return true
|
||||
},
|
||||
})
|
||||
|
||||
socket, err := upgrader.Upgrade(r.Response.Writer, r.Request)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
// ants.Submit(func() {
|
||||
// socket.ReadLoop()
|
||||
// })
|
||||
// ants.Submit(func() { socket.ReadLoop() })
|
||||
go socket.ReadLoop()
|
||||
|
||||
})
|
||||
}
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"blazing/common/rpc"
|
||||
"blazing/cool"
|
||||
"blazing/modules/base/config"
|
||||
"blazing/modules/config/service"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/google/uuid"
|
||||
"github.com/lxzan/gws"
|
||||
)
|
||||
|
||||
const UpStream = "http://43.248.3.21:45632/"
|
||||
|
||||
func MiddlewareCORS(r *ghttp.Request) {
|
||||
r.Response.CORSDefault()
|
||||
corsOptions := r.Response.DefaultCORSOptions()
|
||||
corsOptions.AllowDomain = []string{"*", "localhost", "tauri.localhost"}
|
||||
if !r.Response.CORSAllowedOrigin(corsOptions) {
|
||||
r.Response.WriteStatus(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
r.Response.CORS(corsOptions)
|
||||
if r.Response.Request.Method == "OPTIONS" {
|
||||
r.Response.WriteStatus(http.StatusOK)
|
||||
return
|
||||
}
|
||||
// fmt.Println(r.Response.Header())
|
||||
//g.Dump(r.Response.Server.SetConfig(gtt))
|
||||
//r.Response.Header().Del("Server") // 删除Server头
|
||||
// r.Response.Header().Set("Server", "blazing")
|
||||
r.Middleware.Next()
|
||||
}
|
||||
func StartServerProxy() {
|
||||
s := g.Server()
|
||||
// Parse the upstream URL
|
||||
u, _ := url.Parse(UpStream)
|
||||
// Create a new reverse proxy instance
|
||||
proxy := httputil.NewSingleHostReverseProxy(u)
|
||||
// Configure error handling for proxy failures
|
||||
proxy.ErrorHandler = func(writer http.ResponseWriter, request *http.Request, e error) {
|
||||
writer.WriteHeader(http.StatusBadGateway)
|
||||
}
|
||||
|
||||
s.BindHandler("/bbs/api/fof/upload", func(r *ghttp.Request) {
|
||||
// 1. 调用上传方法(仍返回单个URL字符串,不改动Upload方法)
|
||||
urlStr, err := cool.File().Upload(r.Context())
|
||||
|
||||
// 2. 错误处理:返回标准化错误JSON
|
||||
if err != nil {
|
||||
r.Response.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
r.Response.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(r.Response.Writer).Encode(map[string]interface{}{
|
||||
"code": 1,
|
||||
"msg": err.Error(),
|
||||
"data": nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 3. 基于返回的URL,构造完整的JSON结构体(和示例完全一致)
|
||||
// 解析URL中的文件名和路径(从urlStr中提取)
|
||||
baseName := filepath.Base(urlStr) // 提取文件名(如13e8d062-xxx.jpg)
|
||||
dir := gtime.Now().Format("Y-m-d") // 日期目录(2026-02-07)
|
||||
path := fmt.Sprintf("%s/%s", dir, baseName) // 拼接path字段
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
randomID := strconv.Itoa(rand.Intn(1000)) // 模拟ID(如54)
|
||||
uuidStr := uuid.New().String() // 生成UUID
|
||||
humanSize := "743kB" // 模拟易读大小(可根据实际需求优化)
|
||||
fileSize := int64(760783) // 模拟文件大小(字节)
|
||||
|
||||
// 构造和示例完全一致的响应结构体
|
||||
fullResponse := map[string]interface{}{
|
||||
"data": []map[string]interface{}{
|
||||
{
|
||||
"type": "files",
|
||||
"id": randomID,
|
||||
"attributes": map[string]interface{}{
|
||||
"baseName": baseName,
|
||||
"path": path,
|
||||
"url": urlStr, // 用Upload返回的URL
|
||||
"type": "image/jpeg", // 模拟MIME类型
|
||||
"size": fileSize,
|
||||
"humanSize": humanSize,
|
||||
"createdAt": nil, // null
|
||||
"uuid": uuidStr,
|
||||
"tag": "just-url",
|
||||
"hidden": false,
|
||||
"bbcode": `[img]` + urlStr + `[/img]`, // 和URL一致
|
||||
"shared": false,
|
||||
"canViewInfo": false,
|
||||
"canHide": true,
|
||||
"canDelete": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// 4. 输出完整JSON响应
|
||||
r.Response.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
if err := json.NewEncoder(r.Response.Writer).Encode(fullResponse); err != nil {
|
||||
// 兜底错误
|
||||
fmt.Fprintf(r.Response.Writer, `{"code":1,"msg":"响应生成失败:%s","data":null}`, err.Error())
|
||||
}
|
||||
})
|
||||
// Handle all requests with path prefix "/proxy/*"
|
||||
s.BindHandler("/bbs/*url", func(r *ghttp.Request) {
|
||||
var (
|
||||
//originalPath = r.Request.URL.Path
|
||||
proxyToPath = "/" + r.Get("url").String()
|
||||
)
|
||||
// Rewrite the request path
|
||||
r.Request.URL.Path = proxyToPath
|
||||
// Log the proxy operation
|
||||
//g.Log().Infof(r.Context(), `proxy:"%s" -> backend:"%s"`, originalPath, proxyToPath)
|
||||
// Ensure request body can be read multiple times if needed
|
||||
r.MakeBodyRepeatableRead(false)
|
||||
// Forward the request to the backend server
|
||||
proxy.ServeHTTP(r.Response.Writer, r.Request)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func init() {
|
||||
if config.Config.Middleware.Authority.Enable {
|
||||
g.Server().BindMiddleware("/admin/*/open/*", BaseAuthorityMiddlewareOpen)
|
||||
g.Server().BindMiddleware("/rpc/*", BaseAuthorityMiddlewareOpen)
|
||||
g.Server().BindMiddleware("/admin/*/comm/*", BaseAuthorityMiddlewareComm)
|
||||
g.Server().BindMiddleware("/seer/game/cdk/*", BaseAuthorityMiddlewareComm)
|
||||
g.Server().BindMiddleware("/seer/game/cdk/*", BaseAuthorityMiddleware)
|
||||
g.Server().BindMiddleware("/admin/*", BaseAuthorityMiddleware)
|
||||
// g.Server().BindMiddleware("/*", AutoI18n)
|
||||
g.Server().BindMiddleware("/*", MiddlewareCORS)
|
||||
|
||||
}
|
||||
if config.Config.Middleware.Log.Enable {
|
||||
g.Server().BindMiddleware("/admin/*", BaseLog)
|
||||
}
|
||||
StartServerProxy()
|
||||
tt := rpc.CServer()
|
||||
|
||||
g.Server().BindHandler("/rpc/*", func(r *ghttp.Request) {
|
||||
|
||||
tt.ServeHTTP(r.Response.Writer, r.Request)
|
||||
|
||||
})
|
||||
g.Server().Use(CompressMiddleware)
|
||||
g.Server().BindHandler("/server/*", func(r *ghttp.Request) {
|
||||
servert := new(ServerHandler)
|
||||
id := gconv.Uint16(r.URL.Query().Get("id"))
|
||||
servert.isinstall = gconv.Uint32(r.URL.Query().Get("isinstall"))
|
||||
servert.ServerList = service.NewServerService().StartUPdate(id, int(servert.isinstall))
|
||||
|
||||
upgrader := gws.NewUpgrader(servert, &gws.ServerOption{
|
||||
|
||||
Authorize: func(_ *http.Request, _ gws.SessionStorage) bool {
|
||||
|
||||
tokenString := r.URL.Query().Get("Authorization")
|
||||
token, err := jwt.ParseWithClaims(tokenString, &cool.Claims{}, func(_ *jwt.Token) (interface{}, error) {
|
||||
|
||||
return []byte(config.Config.Jwt.Secret), nil
|
||||
})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if !token.Valid {
|
||||
return false
|
||||
}
|
||||
admin := token.Claims.(*cool.Claims)
|
||||
if admin.UserId != 10001 {
|
||||
return false
|
||||
}
|
||||
|
||||
// var name = r.URL.Query().Get("name")
|
||||
// if name == "" {
|
||||
// return false
|
||||
// }
|
||||
// t, _ := service.NewBaseSysUserService().Person(admin.UserID)
|
||||
|
||||
//Loger.Debug(context.TODO(), t.Mimi)
|
||||
// session.Store("name", t.Mimi)
|
||||
//session.Store("key", r.Header.Get("Sec-WebSocket-Key"))
|
||||
return true
|
||||
},
|
||||
})
|
||||
|
||||
socket, err := upgrader.Upgrade(r.Response.Writer, r.Request)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
// ants.Submit(func() {
|
||||
// socket.ReadLoop()
|
||||
// })
|
||||
// ants.Submit(func() { socket.ReadLoop() })
|
||||
go socket.ReadLoop()
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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:"剩余可兑换次数"`
|
||||
|
||||
@@ -71,6 +71,10 @@ func roundFloat32(val float32, decimals int) float32 {
|
||||
return float32(math.Round(float64(val*shift))) / shift
|
||||
}
|
||||
|
||||
func randomFloat32Range(r *rand.Rand, min, max float32) float32 {
|
||||
return min + float32(r.Float64())*(max-min)
|
||||
}
|
||||
|
||||
// 预处理矩阵:自动修正越界参数,确保合规
|
||||
func ProcessOffspringMatrix(mat [4][5]float32) [4][5]float32 {
|
||||
var processedMat [4][5]float32
|
||||
@@ -118,71 +122,62 @@ func calculateMatrixHash(mm MonsterMatrix) string {
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
// GenerateRandomParentMatrix 调整随机参数范围,解决暗色问题,贴合你的示例
|
||||
// GenerateRandomParentMatrix 生成色域更大的父矩阵,同时抬高黑场避免整只精灵发黑
|
||||
func GenerateRandomParentMatrix() MonsterMatrix {
|
||||
// 1. 保留高熵种子逻辑(确保唯一性,不变)
|
||||
salt := "player-matrix-random-seed"
|
||||
now := time.Now().UnixNano()
|
||||
randomNum := rand.Int63()
|
||||
seed := now ^ randomNum ^ int64(hashString(salt))
|
||||
r := rand.New(rand.NewSource(seed))
|
||||
|
||||
// 2. 调整随机参数范围(贴合你的示例,解决暗色问题)
|
||||
// 对比你的示例:亮度大多在[-30,30],对比度[0.7,1.7],饱和度[0.4,1.4],偏移[-10,10]
|
||||
params := struct {
|
||||
brightness float32 // 从[-125,125]调整为[-30,60],避免过低亮度
|
||||
contrast float32 // 从[0.4,2.2]调整为[0.7,1.7],贴合你的示例
|
||||
saturation float32 // 从[0.1,2.0]调整为[0.4,1.4],避免低饱和度灰暗
|
||||
hueRotate float32 // 从[-180,180]调整为[-50,50],减少极端色相导致的暗沉
|
||||
rOffset float32 // 从[-50,50]调整为[-10,10],避免亮度偏移过大
|
||||
gOffset float32 // 同上
|
||||
bOffset float32 // 同上
|
||||
}{
|
||||
brightness: float32(r.Float64()*90 - 30), // [-30, 60](贴合示例亮度范围,减少暗色)
|
||||
contrast: float32(r.Float64()*1.0 + 0.7), // [0.7, 1.7](与你的示例完全匹配)
|
||||
saturation: float32(r.Float64()*1.0 + 0.4), // [0.4, 1.4](与你的示例完全匹配)
|
||||
hueRotate: float32(r.Float64()*100 - 50), // [-50, 50](避免极端色相)
|
||||
rOffset: float32(r.Float64()*20 - 10), // [-10, 10](小幅亮度偏移,贴合示例)
|
||||
gOffset: float32(r.Float64()*20 - 10), // 同上
|
||||
bOffset: float32(r.Float64()*20 - 10), // 同上
|
||||
matrix := newIdentityMatrix()
|
||||
dominant := r.Intn(3)
|
||||
accent := (dominant + 1 + r.Intn(2)) % 3
|
||||
baseLift := randomFloat32Range(r, 10, 24)
|
||||
|
||||
for row := 0; row < 3; row++ {
|
||||
for col := 0; col < 3; col++ {
|
||||
value := randomFloat32Range(r, -0.22, 0.22)
|
||||
if row == col {
|
||||
value = randomFloat32Range(r, 0.8, 1.45)
|
||||
}
|
||||
if row == dominant && col == dominant {
|
||||
value += randomFloat32Range(r, 0.35, 0.7)
|
||||
}
|
||||
if row == accent && col == accent {
|
||||
value += randomFloat32Range(r, 0.1, 0.3)
|
||||
}
|
||||
if row == dominant && col != row {
|
||||
value += randomFloat32Range(r, -0.12, 0.28)
|
||||
}
|
||||
matrix[row][col] = clampFloat32(roundFloat32(value, FloatPrecision), MatrixMinVal, MatrixMaxVal)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 矩阵计算逻辑不变(确保与你的示例参数分布一致)
|
||||
matrix := newIdentityMatrix()
|
||||
contrast := params.contrast
|
||||
matrix[0][0] *= contrast
|
||||
matrix[1][1] *= contrast
|
||||
matrix[2][2] *= contrast
|
||||
matrix[0][4] = baseLift + randomFloat32Range(r, -5, 10)
|
||||
matrix[1][4] = baseLift + randomFloat32Range(r, -5, 10)
|
||||
matrix[2][4] = baseLift + randomFloat32Range(r, -5, 10)
|
||||
matrix[dominant][4] += randomFloat32Range(r, 8, 18)
|
||||
matrix[accent][4] += randomFloat32Range(r, 2, 8)
|
||||
|
||||
sat := params.saturation
|
||||
grayR, grayG, grayB := float32(0.299)*(1-sat), float32(0.587)*(1-sat), float32(0.114)*(1-sat)
|
||||
matrix[0][0] = grayR + sat*matrix[0][0]
|
||||
matrix[0][1], matrix[0][2] = grayG, grayB
|
||||
matrix[1][0] = grayR
|
||||
matrix[1][1] = grayG + sat*matrix[1][1]
|
||||
matrix[1][2] = grayB
|
||||
matrix[2][0] = grayR
|
||||
matrix[2][1] = grayG
|
||||
matrix[2][2] = grayB + sat*matrix[2][2]
|
||||
avgBrightness := (matrix[0][4] + matrix[1][4] + matrix[2][4]) / 3
|
||||
if avgBrightness < 12 {
|
||||
lift := 12 - avgBrightness + randomFloat32Range(r, 0, 6)
|
||||
for row := 0; row < 3; row++ {
|
||||
matrix[row][4] += lift
|
||||
}
|
||||
}
|
||||
if matrix[dominant][4] < 16 {
|
||||
matrix[dominant][4] = 16 + randomFloat32Range(r, 0, 10)
|
||||
}
|
||||
|
||||
angle := params.hueRotate * float32(math.Pi) / 180
|
||||
cos, sin := float32(math.Cos(float64(angle))), float32(math.Sin(float64(angle)))
|
||||
r1, g1, b1 := matrix[0][0], matrix[0][1], matrix[0][2]
|
||||
r2, g2, b2 := matrix[1][0], matrix[1][1], matrix[1][2]
|
||||
for row := 0; row < 3; row++ {
|
||||
for col := 0; col < 3; col++ {
|
||||
matrix[row][col] = clampFloat32(roundFloat32(matrix[row][col], FloatPrecision), MatrixMinVal, MatrixMaxVal)
|
||||
}
|
||||
matrix[row][4] = clampFloat32(roundFloat32(matrix[row][4], FloatPrecision), BrightnessMinVal, BrightnessMaxVal)
|
||||
}
|
||||
|
||||
matrix[0][0] = r1*cos - r2*sin
|
||||
matrix[0][1] = r1*sin + r2*cos
|
||||
matrix[1][0] = g1*cos - g2*sin
|
||||
matrix[1][1] = g1*sin + g2*cos
|
||||
matrix[2][0] = b1*cos - b2*sin
|
||||
matrix[2][1] = b1*sin + b2*cos
|
||||
|
||||
// 亮度偏移:范围缩小后,避免过暗/过亮
|
||||
matrix[0][4] = params.brightness + params.rOffset
|
||||
matrix[1][4] = params.brightness + params.gOffset
|
||||
matrix[2][4] = params.brightness + params.bOffset
|
||||
|
||||
// 4. 封装为MonsterMatrix(不变)
|
||||
parent := MonsterMatrix{
|
||||
Matrix: matrix,
|
||||
Hash: "",
|
||||
|
||||
@@ -18,6 +18,8 @@ type MapConfig struct {
|
||||
WeatherType []uint32 `gorm:"type:int[];comment:'天气类型( 0 晴天,1-雨天,2-雪天)'" json:"weather_type"`
|
||||
//是否超时空
|
||||
IsTimeSpace int `gorm:"type:int;default:0;comment:'是否超时空'" json:"is_time_space"`
|
||||
// 是否等级突破地图
|
||||
IsLevelBreakMap int `gorm:"type:int;default:0;comment:'是否等级突破地图'" json:"is_level_break_map"`
|
||||
|
||||
// 掉落物配置
|
||||
DropItemIds []uint32 `gorm:"type:int[];comment:'掉落物IDs" json:"drop_item_ids"`
|
||||
|
||||
@@ -32,6 +32,7 @@ type MapNode struct {
|
||||
IsBroadcast uint32 `gorm:"type:int;default:0;comment:'广播模型ID(0表示不广播)'" json:"is_broadcast"`
|
||||
|
||||
IsGroupBoss uint32 `gorm:"type:int;default:0;comment:'是否为组队Boss(0否1是)'" json:"is_group_boss" description:"是否为组队Boss"`
|
||||
PkFlag uint32 `gorm:"type:int;default:0;comment:'是否单精灵对战(0否1是)'" json:"pk_flag" description:"是否单精灵对战"`
|
||||
|
||||
TriggerPlotID uint32 `gorm:"default:0;comment:'触发剧情ID(0表示无剧情)'" json:"trigger_plot_id" description:"触发剧情ID"`
|
||||
|
||||
|
||||
@@ -1,39 +1,40 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"blazing/cool"
|
||||
"blazing/cool/coolconfig"
|
||||
)
|
||||
|
||||
const TableNameServerList = "server_list"
|
||||
|
||||
// 服务器设置天气地图|地图是否显示其他人,设置异色概率|ip
|
||||
// ServerList mapped from table <server_list>
|
||||
type ServerList struct {
|
||||
*cool.Model
|
||||
coolconfig.ServerList
|
||||
//isonline
|
||||
IsOnline uint8 `gorm:"column:isonline;default:0;comment:'是否在线'" json:"isonline"`
|
||||
}
|
||||
|
||||
// TableName ServerList's table name
|
||||
func (*ServerList) TableName() string {
|
||||
return TableNameServerList
|
||||
}
|
||||
|
||||
// GroupName ServerList's table group
|
||||
func (*ServerList) GroupName() string {
|
||||
return "default"
|
||||
}
|
||||
|
||||
// NewServerList create a new ServerList
|
||||
func NewServerList() *ServerList {
|
||||
return &ServerList{
|
||||
Model: cool.NewModel(),
|
||||
}
|
||||
}
|
||||
|
||||
// init 创建表
|
||||
func init() {
|
||||
cool.CreateTable(&ServerList{})
|
||||
}
|
||||
package model
|
||||
|
||||
import (
|
||||
"blazing/cool"
|
||||
"blazing/cool/coolconfig"
|
||||
)
|
||||
|
||||
const TableNameServerList = "server_list"
|
||||
|
||||
// 服务器设置天气地图|地图是否显示其他人,设置异色概率|ip
|
||||
// ServerList mapped from table <server_list>
|
||||
type ServerList struct {
|
||||
*cool.Model
|
||||
coolconfig.ServerList
|
||||
ServerShow *ServerShow `orm:"with:server_id=online_id" gorm:"-" json:"servershow,omitempty"`
|
||||
// isonline
|
||||
IsOnline uint8 `gorm:"column:isonline;default:0;comment:'是否在线'" json:"isonline"`
|
||||
}
|
||||
|
||||
// TableName ServerList's table name
|
||||
func (*ServerList) TableName() string {
|
||||
return TableNameServerList
|
||||
}
|
||||
|
||||
// GroupName ServerList's table group
|
||||
func (*ServerList) GroupName() string {
|
||||
return "default"
|
||||
}
|
||||
|
||||
// NewServerList create a new ServerList
|
||||
func NewServerList() *ServerList {
|
||||
return &ServerList{
|
||||
Model: cool.NewModel(),
|
||||
}
|
||||
}
|
||||
|
||||
// init 创建表
|
||||
func init() {
|
||||
cool.CreateTable(&ServerList{})
|
||||
}
|
||||
|
||||
39
modules/config/model/server_show.go
Normal file
39
modules/config/model/server_show.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"blazing/cool"
|
||||
"time"
|
||||
)
|
||||
|
||||
const TableNameServerShow = "server_show"
|
||||
|
||||
// ServerShow 绑定服务器展示信息(冠名、属主、到期时间)。
|
||||
type ServerShow struct {
|
||||
*cool.Model
|
||||
ServerID uint32 `gorm:"column:server_id;comment:'服务器ID';index:idx_server_show_server_id;uniqueIndex:idx_server_show_server_owner" json:"server_id"`
|
||||
Name string `gorm:"comment:'服务器展示名'" json:"name"`
|
||||
Owner uint32 `gorm:"comment:'服务器属主';uniqueIndex:idx_server_show_server_owner" json:"owner"`
|
||||
ExpireTime time.Time `gorm:"column:expire_time;default:0;comment:'展示到期时间'" json:"expire_time"`
|
||||
}
|
||||
|
||||
// TableName ServerShow's table name
|
||||
func (*ServerShow) TableName() string {
|
||||
return TableNameServerShow
|
||||
}
|
||||
|
||||
// GroupName ServerShow's table group
|
||||
func (*ServerShow) GroupName() string {
|
||||
return "default"
|
||||
}
|
||||
|
||||
// NewServerShow create a new ServerShow
|
||||
func NewServerShow() *ServerShow {
|
||||
return &ServerShow{
|
||||
Model: cool.NewModel(),
|
||||
}
|
||||
}
|
||||
|
||||
// init 创建表
|
||||
func init() {
|
||||
cool.CreateTable(&ServerShow{})
|
||||
}
|
||||
@@ -26,6 +26,9 @@ type TaskConfig struct {
|
||||
// 奖励配置
|
||||
ItemRewardIds []uint32 `gorm:"not null;type:jsonb;default:'[]';comment:'绑定奖励物品ID数组,关联item_gift表主键'" json:"item_reward_ids" description:"奖励物品数组"`
|
||||
ElfRewardIds uint32 `gorm:"not null;default:0;comment:'绑定奖励精灵ID,关联elf_gift表主键'" json:"elf_reward_ids" description:"绑定奖励精灵ID"`
|
||||
RewardPetID uint32 `gorm:"not null;default:0;comment:'宠物相关奖励生效的目标精灵ID,0表示不对已有精灵发放'" json:"reward_pet_id" description:"目标精灵ID"`
|
||||
TrainSkillIDs []uint32 `gorm:"not null;type:jsonb;default:'[]';comment:'任务奖励的特训技能ID数组'" json:"train_skill_ids" description:"特训技能奖励数组"`
|
||||
SkinIDs []uint32 `gorm:"not null;type:jsonb;default:'[]';comment:'任务奖励的皮肤ID数组'" json:"skin_ids" description:"皮肤奖励数组"`
|
||||
|
||||
//绑定奖励
|
||||
TitleRewardIds uint32 `gorm:"not null;default:0;comment:'绑定奖励称号'" json:"title_reward_ids" description:"绑定奖励称号"`
|
||||
|
||||
@@ -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"
|
||||
@@ -12,6 +14,7 @@ import (
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
@@ -165,19 +168,19 @@ type ServerNamingCDKResult struct {
|
||||
ServerName string
|
||||
OwnerID uint32
|
||||
ServerExpireTime time.Time
|
||||
CDKExpireTime time.Time
|
||||
}
|
||||
|
||||
// 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.ServerList
|
||||
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 {
|
||||
@@ -202,7 +205,7 @@ func (s *CdkService) UseServerNamingCDK(ctx context.Context, code string, ownerI
|
||||
if server.OnlineID == 0 {
|
||||
return gerror.New("服务器不存在")
|
||||
}
|
||||
if server.Owner != ownerID && !serverService.CanUseDonationName(server, now) {
|
||||
if !serverService.CanUseDonationName(server, ownerID, now) {
|
||||
return gerror.New("服务器不可冠名")
|
||||
}
|
||||
|
||||
@@ -215,23 +218,50 @@ func (s *CdkService) UseServerNamingCDK(ctx context.Context, code string, ownerI
|
||||
return gerror.New("cdk已被使用")
|
||||
}
|
||||
|
||||
updated = server
|
||||
var currentShow model.ServerShow
|
||||
if err := tx.Model(model.NewServerShow()).
|
||||
Where("server_id", serverID).
|
||||
Where("owner", ownerID).
|
||||
OrderDesc("id").
|
||||
Limit(1).
|
||||
Scan(¤tShow); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updated = currentShow
|
||||
updated.ServerID = serverID
|
||||
updated.Name = serverName
|
||||
updated.Owner = ownerID
|
||||
if server.Owner != ownerID {
|
||||
updated.CDKExpireTime = now.AddDate(0, 1, 0)
|
||||
|
||||
if currentShow.ServerID == 0 || !serverService.isActiveServerShow(¤tShow, now) {
|
||||
updated.ExpireTime = now.AddDate(0, 1, 0)
|
||||
} else {
|
||||
baseTime := server.CDKExpireTime
|
||||
baseTime := currentShow.ExpireTime
|
||||
if baseTime.IsZero() {
|
||||
baseTime = now
|
||||
}
|
||||
updated.CDKExpireTime = baseTime.AddDate(0, 1, 0)
|
||||
updated.ExpireTime = baseTime.AddDate(0, 1, 0)
|
||||
}
|
||||
|
||||
_, err = tx.Model(model.NewServerList()).Where("online_id", serverID).Data(g.Map{
|
||||
"name": updated.Name,
|
||||
"owner": updated.Owner,
|
||||
"cdk_expire_time": updated.CDKExpireTime,
|
||||
if currentShow.ServerID == 0 {
|
||||
_, err = tx.Model(model.NewServerShow()).Data(g.Map{
|
||||
"server_id": updated.ServerID,
|
||||
"name": updated.Name,
|
||||
"owner": updated.Owner,
|
||||
"expire_time": updated.ExpireTime,
|
||||
}).Insert()
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Model(model.NewServerShow()).Where("server_id", serverID).Data(g.Map{
|
||||
"name": updated.Name,
|
||||
"owner": updated.Owner,
|
||||
"expire_time": updated.ExpireTime,
|
||||
}).Update()
|
||||
return err
|
||||
})
|
||||
@@ -241,12 +271,13 @@ 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.OnlineID,
|
||||
ServerID: updated.ServerID,
|
||||
ServerName: updated.Name,
|
||||
OwnerID: updated.Owner,
|
||||
ServerExpireTime: updated.ExpireTime,
|
||||
CDKExpireTime: updated.CDKExpireTime,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -263,6 +294,18 @@ func (s *CdkService) BatchGenerate(ctx context.Context, count int) (interface{},
|
||||
delete(requestData, "cdk_code")
|
||||
delete(requestData, "id")
|
||||
|
||||
cdkType := gconv.Uint32(requestData["type"])
|
||||
if cdkType != CDKTypeReward && cdkType != CDKTypeServerNaming {
|
||||
return nil, gerror.New("CDK类型非法")
|
||||
}
|
||||
requestData["type"] = cdkType
|
||||
if cdkType == CDKTypeServerNaming {
|
||||
requestData["exchange_remain_count"] = int64(1)
|
||||
requestData["item_reward_ids"] = []uint32{}
|
||||
requestData["elf_reward_ids"] = []uint32{}
|
||||
requestData["title_reward_ids"] = uint32(0)
|
||||
}
|
||||
|
||||
codes, err := s.generateAvailableCodes(count)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -26,7 +26,7 @@ func (s *EggService) GetData(p1 uint32) []int32 {
|
||||
m := dbm_enable(s.Model)
|
||||
|
||||
var pet []model.Egg //一个特性应该是唯一的,但是我们要获取默认随机特性
|
||||
m.Wheref(`male_pet_ids @> ARRAY[?]::integer[]`, p1).Scan(&pet)
|
||||
m.Wheref(`? = ANY(male_pet_ids)`, p1).Scan(&pet)
|
||||
var petIDs []int32
|
||||
for _, p := range pet {
|
||||
petIDs = append(petIDs, p.FemalePetIDs...)
|
||||
@@ -40,8 +40,8 @@ func (s *EggService) GetResult(m, f, level uint32) (uint32, bool) {
|
||||
md := dbm_enable(s.Model)
|
||||
|
||||
var pet *model.Egg //一个特性应该是唯一的,但是我们要获取默认随机特性
|
||||
md.Wheref(`male_pet_ids @> ARRAY[?]::integer[]`, m).
|
||||
Wheref(`female_pet_ids @> ARRAY[?]::integer[]`, f).Scan(&pet)
|
||||
md.Wheref(`? = ANY(male_pet_ids)`, m).
|
||||
Wheref(`? = ANY(female_pet_ids)`, f).Scan(&pet)
|
||||
if pet != nil {
|
||||
pet.Probs[len(pet.Probs)-1] += int32(level)
|
||||
t, _ := utils.RandomByWeight(pet.OutputMons, pet.Probs)
|
||||
|
||||
@@ -41,7 +41,7 @@ func (s *MapPitService) GetDataALL(mapid uint32) []model.MapPit {
|
||||
func (s *MapPitService) GetPet(petid uint32) []model.MapPit {
|
||||
|
||||
var pet []model.MapPit //一个特性应该是唯一的,但是我们要获取默认随机特性
|
||||
dbm_enable(s.Model).Wheref(`refresh_id @> ARRAY[?]::integer[]`, petid).Scan(&pet)
|
||||
dbm_enable(s.Model).Wheref(`? = ANY(refresh_id)`, petid).Scan(&pet)
|
||||
|
||||
return pet
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ func (s *PetFusionService) Data(p1, p2, rand uint32) uint32 {
|
||||
func (s *PetFusionService) getData(p1, p2 uint32) uint32 {
|
||||
|
||||
var pet []model.PetFusion //一个特性应该是唯一的,但是我们要获取默认随机特性
|
||||
dbm_enable(s.Model).Where("main_pet_id", p1).Wheref(`sub_pet_ids @> ARRAY[?]::integer[]`, p2).Scan(&pet)
|
||||
dbm_enable(s.Model).Where("main_pet_id", p1).Wheref(`? = ANY(sub_pet_ids)`, p2).Scan(&pet)
|
||||
if len(pet) != 0 {
|
||||
var pets, props []int
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gmap"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
@@ -21,45 +22,72 @@ type ServerService struct {
|
||||
bucket string
|
||||
}
|
||||
|
||||
type ServerShowInfo struct {
|
||||
OnlineID uint32 `json:"online_id"`
|
||||
Name string `json:"name"`
|
||||
IP string `json:"ip"`
|
||||
Port uint32 `json:"port"`
|
||||
IsVip uint32 `json:"is_vip"`
|
||||
IsDebug uint8 `json:"is_debug"`
|
||||
IsOpen uint8 `json:"is_open"`
|
||||
//Owner uint32 `json:"owner"`
|
||||
// ExpireTime time.Time `json:"expire_time"`
|
||||
// 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{
|
||||
Model: model.NewServerList(),
|
||||
PageQueryOp: &cool.QueryOp{
|
||||
ModifyResult: func(ctx g.Ctx, data interface{}) interface{} {
|
||||
var rr []g.MapStrAny
|
||||
r, _ := gconv.Map(data)["list"].(gdb.Result)
|
||||
for i := 0; i < len(r); i++ {
|
||||
t, ok := cool.GetClient(gconv.Uint32(r[i].Map()["online_id"]), gconv.Uint32(r[i].Map()["port"]))
|
||||
// tt.Friends = v.Friends
|
||||
subm := r[i].GMap()
|
||||
|
||||
if ok {
|
||||
|
||||
err := t.KickPerson(0) //实现指定服务器踢人
|
||||
|
||||
if err == nil {
|
||||
r, _ := utils.TcpPing(fmt.Sprintf("%s:%d", r[i].Map()["ip"], r[i].Map()["port"]))
|
||||
|
||||
subm.Set("isonline", r)
|
||||
|
||||
} else {
|
||||
subm.Set("isonline", 0)
|
||||
}
|
||||
|
||||
} else {
|
||||
subm.Set("isonline", 0)
|
||||
}
|
||||
rr = append(rr, subm.MapStrAny())
|
||||
}
|
||||
|
||||
data.(map[string]interface{})["list"] = rr
|
||||
|
||||
return data
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
cf.PageQueryOp = &cool.QueryOp{
|
||||
ModifyResult: func(ctx g.Ctx, data interface{}) interface{} {
|
||||
var rr []g.MapStrAny
|
||||
r, _ := gconv.Map(data)["list"].(gdb.Result)
|
||||
|
||||
// now := time.Now()
|
||||
serverIDs := make([]uint32, 0, len(r))
|
||||
for i := 0; i < len(r); i++ {
|
||||
serverID := gconv.Uint32(r[i].Map()["online_id"])
|
||||
if serverID == 0 {
|
||||
continue
|
||||
}
|
||||
serverIDs = append(serverIDs, serverID)
|
||||
}
|
||||
//showMap := cf.getPrimaryActiveServerShowMap(serverIDs, now)
|
||||
|
||||
for i := 0; i < len(r); i++ {
|
||||
t, ok := cool.GetClient(gconv.Uint32(r[i].Map()["online_id"]), gconv.Uint32(r[i].Map()["port"]))
|
||||
subm := r[i].GMap()
|
||||
//cf.applyServerShowMap(subm, gconv.Uint32(r[i].Map()["online_id"]), showMap)
|
||||
|
||||
if ok {
|
||||
err := t.KickPerson(0) //实现指定服务器踢人
|
||||
if err == nil {
|
||||
r, _ := utils.TcpPing(fmt.Sprintf("%s:%d", r[i].Map()["ip"], r[i].Map()["port"]))
|
||||
subm.Set("isonline", r)
|
||||
} else {
|
||||
subm.Set("isonline", 0)
|
||||
}
|
||||
} else {
|
||||
subm.Set("isonline", 0)
|
||||
}
|
||||
rr = append(rr, subm.MapStrAny())
|
||||
}
|
||||
|
||||
data.(map[string]interface{})["list"] = rr
|
||||
return data
|
||||
},
|
||||
}
|
||||
|
||||
cfg := storage.Config{
|
||||
Zone: &storage.ZoneHuadong,
|
||||
UseHTTPS: true,
|
||||
@@ -67,107 +95,204 @@ func NewServerService() *ServerService {
|
||||
}
|
||||
mac := qbox.NewMac("DzMpomnPxqBHkIcvxTbC-hl_8LjVB0LXZuhCky_u", "bhoxrpG1s7MBmSS2I1k5t9zMpuiderpBDZoIPQKU")
|
||||
cf.bucket = "blazingt"
|
||||
|
||||
cf.manager = storage.NewBucketManager(mac, &cfg)
|
||||
return cf
|
||||
}
|
||||
func (s *ServerService) GetPort(DepartmentID uint) gdb.List {
|
||||
var res gdb.Result
|
||||
m := cool.DBM(s.Model).Where("is_open", 1).Fields("ip", "port", "online_id", "is_vip", "name")
|
||||
if DepartmentID != 1 {
|
||||
|
||||
res, _ = m.All()
|
||||
|
||||
} else {
|
||||
res, _ = m.Where("is_debug", 0).All()
|
||||
func (s *ServerService) GetPort(DepartmentID uint) []ServerShowInfo {
|
||||
servers := s.getRawServers()
|
||||
now := time.Now()
|
||||
showMap := s.getActiveServerShowListMap(s.collectServerIDs(servers), now)
|
||||
items := make([]ServerShowInfo, 0, len(servers))
|
||||
for _, server := range servers {
|
||||
if server.IsOpen != 1 {
|
||||
continue
|
||||
}
|
||||
if DepartmentID == 1 && server.IsDebug != 0 {
|
||||
continue
|
||||
}
|
||||
items = append(items, ServerShowInfo{
|
||||
OnlineID: server.OnlineID,
|
||||
Name: server.Name,
|
||||
IP: server.IP,
|
||||
Port: server.Port,
|
||||
IsVip: server.IsVip,
|
||||
IsDebug: server.IsDebug,
|
||||
IsOpen: server.IsOpen,
|
||||
//Owner: 0,
|
||||
// ExpireTime: time.Time{},
|
||||
})
|
||||
for i := range showMap[server.OnlineID] {
|
||||
show := &showMap[server.OnlineID][i]
|
||||
itemOnlineID := server.OnlineID
|
||||
if show.ID > 0 {
|
||||
itemOnlineID = uint32(show.ID)
|
||||
}
|
||||
item := ServerShowInfo{
|
||||
OnlineID: itemOnlineID,
|
||||
Name: server.Name,
|
||||
IP: server.IP,
|
||||
Port: server.Port,
|
||||
IsVip: server.IsVip,
|
||||
IsDebug: server.IsDebug,
|
||||
IsOpen: server.IsOpen,
|
||||
|
||||
// ExpireTime: show.ExpireTime,
|
||||
// ServerShow: show,
|
||||
}
|
||||
if show.Name != "" {
|
||||
item.Name = show.Name
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
}
|
||||
|
||||
return res.List()
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
func (s *ServerService) GetServer() []model.ServerList {
|
||||
var item []model.ServerList
|
||||
cool.DBM(s.Model).Scan(&item)
|
||||
|
||||
item := s.getRawServers()
|
||||
s.applyServerShowList(item, time.Now())
|
||||
return item
|
||||
|
||||
}
|
||||
|
||||
func (s *ServerService) StartUPdate(OnlineID uint16, isinstall int) model.ServerList {
|
||||
|
||||
m := cool.DBM(s.Model).Where("online_id", OnlineID)
|
||||
|
||||
var tttt model.ServerList
|
||||
m.Scan(&tttt)
|
||||
if isinstall == 1 {
|
||||
tttt.IsOpen = uint8(0)
|
||||
m.Save(tttt)
|
||||
}
|
||||
|
||||
// s.CleanCache()
|
||||
return tttt
|
||||
|
||||
}
|
||||
|
||||
func (s *ServerService) SetServerID(OnlineID uint32, Port uint32) error {
|
||||
|
||||
m := cool.DBM(s.Model).Where("online_id", OnlineID)
|
||||
|
||||
var tttt model.ServerList
|
||||
m.Scan(&tttt)
|
||||
tttt.Port = Port
|
||||
tttt.OnlineID = OnlineID
|
||||
tttt.IsOpen = 1
|
||||
|
||||
m.Save(tttt)
|
||||
// s.CleanCache()
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (s *ServerService) GetServerID(OnlineID uint32) model.ServerList {
|
||||
var tttt model.ServerList
|
||||
cool.DBM(s.Model).Where("online_id", OnlineID).Scan(&tttt)
|
||||
|
||||
dbm_nocache_noenable(s.Model).Where("online_id", OnlineID).Scan(&tttt)
|
||||
showMap := s.getPrimaryActiveServerShowMap([]uint32{OnlineID}, time.Now())
|
||||
s.applyServerShow(&tttt, showMap[OnlineID], time.Now())
|
||||
return tttt
|
||||
|
||||
}
|
||||
|
||||
// GetDonationAvailableServerIDs 返回当前可被冠名占用的服务器ID列表。
|
||||
func (s *ServerService) GetDonationAvailableServerIDs() []uint32 {
|
||||
now := time.Now()
|
||||
builder := dbm_nocache_noenable(s.Model).Builder().Where("owner", 0).WhereOr("cdk_expire_time <= ?", now)
|
||||
|
||||
var rows []struct {
|
||||
OnlineID uint32 `json:"online_id"`
|
||||
}
|
||||
dbm_nocache_noenable(s.Model).Fields("online_id").Where(builder).OrderAsc("online_id").Scan(&rows)
|
||||
|
||||
ids := make([]uint32, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
if row.OnlineID == 0 {
|
||||
servers := s.getRawServers()
|
||||
ids := make([]uint32, 0, len(servers))
|
||||
for _, server := range servers {
|
||||
if server.OnlineID == 0 {
|
||||
continue
|
||||
}
|
||||
ids = append(ids, row.OnlineID)
|
||||
ids = append(ids, server.OnlineID)
|
||||
}
|
||||
sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
|
||||
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, now time.Time) bool {
|
||||
if server.OnlineID == 0 {
|
||||
return false
|
||||
func (s *ServerService) CanUseDonationName(server model.ServerList, ownerID uint32, now time.Time) bool {
|
||||
return server.OnlineID != 0
|
||||
}
|
||||
|
||||
func (s *ServerService) getRawServers() []model.ServerList {
|
||||
var item []model.ServerList
|
||||
dbm_nocache_noenable(s.Model).Scan(&item)
|
||||
return item
|
||||
}
|
||||
|
||||
func (s *ServerService) collectServerIDs(servers []model.ServerList) []uint32 {
|
||||
serverIDs := make([]uint32, 0, len(servers))
|
||||
for i := range servers {
|
||||
if servers[i].OnlineID == 0 {
|
||||
continue
|
||||
}
|
||||
serverIDs = append(serverIDs, servers[i].OnlineID)
|
||||
}
|
||||
if server.Owner == 0 {
|
||||
return true
|
||||
}
|
||||
return !server.CDKExpireTime.After(now)
|
||||
return serverIDs
|
||||
}
|
||||
|
||||
// 保存版本号
|
||||
func (s *ServerService) SetServerScreen(id uint32, name string) {
|
||||
|
||||
cool.DBM(s.Model).Where("online_id", id).Data("old_screen", name).Update()
|
||||
|
||||
}
|
||||
|
||||
func (s *ServerService) GetFile() string {
|
||||
var files []File
|
||||
prefix := "logic"
|
||||
@@ -178,7 +303,6 @@ func (s *ServerService) GetFile() string {
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
// 添加文件到结果列表
|
||||
for _, entry := range entries {
|
||||
files = append(files, File{
|
||||
Name: entry.Key,
|
||||
@@ -187,22 +311,19 @@ func (s *ServerService) GetFile() string {
|
||||
Time: entry.PutTime,
|
||||
})
|
||||
}
|
||||
|
||||
// 如果没有更多文件,结束循环
|
||||
if !hasNext {
|
||||
break
|
||||
}
|
||||
|
||||
// 更新标记,继续下一页
|
||||
marker = nextMarker
|
||||
}
|
||||
|
||||
sort.Slice(files, func(i, j int) bool {
|
||||
return files[i].Time > files[j].Time
|
||||
})
|
||||
|
||||
if len(files) == 0 {
|
||||
return ""
|
||||
}
|
||||
return files[0].Name
|
||||
|
||||
}
|
||||
|
||||
type File struct {
|
||||
@@ -213,3 +334,100 @@ type File struct {
|
||||
Modified string `json:"modified"`
|
||||
Time int64 `json:"time"`
|
||||
}
|
||||
|
||||
func (s *ServerService) isActiveServerShow(show *model.ServerShow, now time.Time) bool {
|
||||
if show == nil {
|
||||
return false
|
||||
}
|
||||
if show.ServerID == 0 || show.Owner == 0 {
|
||||
return false
|
||||
}
|
||||
if show.ExpireTime.IsZero() {
|
||||
return false
|
||||
}
|
||||
return show.ExpireTime.After(now)
|
||||
}
|
||||
|
||||
func (s *ServerService) applyServerShowList(servers []model.ServerList, now time.Time) {
|
||||
showMap := s.getPrimaryActiveServerShowMap(s.collectServerIDs(servers), now)
|
||||
for i := range servers {
|
||||
s.applyServerShow(&servers[i], showMap[servers[i].OnlineID], now)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ServerService) applyServerShow(server *model.ServerList, show *model.ServerShow, now time.Time) {
|
||||
if server == nil {
|
||||
return
|
||||
}
|
||||
server.ServerShow = nil
|
||||
server.Owner = 0
|
||||
server.ExpireTime = time.Time{}
|
||||
if !s.isActiveServerShow(show, now) {
|
||||
return
|
||||
}
|
||||
server.ServerShow = show
|
||||
if show.Name != "" {
|
||||
server.Name = show.Name
|
||||
}
|
||||
server.Owner = show.Owner
|
||||
server.ExpireTime = show.ExpireTime
|
||||
}
|
||||
|
||||
func (s *ServerService) getActiveServerShowListMap(serverIDs []uint32, now time.Time) map[uint32][]model.ServerShow {
|
||||
showMap := make(map[uint32][]model.ServerShow, len(serverIDs))
|
||||
if len(serverIDs) == 0 {
|
||||
return showMap
|
||||
}
|
||||
var shows []model.ServerShow
|
||||
dbm_nocache_noenable(model.NewServerShow()).WhereIn("server_id", serverIDs).Scan(&shows)
|
||||
for i := range shows {
|
||||
show := &shows[i]
|
||||
if !s.isActiveServerShow(show, now) {
|
||||
continue
|
||||
}
|
||||
showMap[show.ServerID] = append(showMap[show.ServerID], *show)
|
||||
}
|
||||
for serverID := range showMap {
|
||||
sort.Slice(showMap[serverID], func(i, j int) bool {
|
||||
left := showMap[serverID][i]
|
||||
right := showMap[serverID][j]
|
||||
if !left.ExpireTime.Equal(right.ExpireTime) {
|
||||
return left.ExpireTime.After(right.ExpireTime)
|
||||
}
|
||||
return left.ID > right.ID
|
||||
})
|
||||
}
|
||||
return showMap
|
||||
}
|
||||
|
||||
func (s *ServerService) getPrimaryActiveServerShowMap(serverIDs []uint32, now time.Time) map[uint32]*model.ServerShow {
|
||||
listMap := s.getActiveServerShowListMap(serverIDs, now)
|
||||
showMap := make(map[uint32]*model.ServerShow, len(listMap))
|
||||
for serverID := range listMap {
|
||||
if len(listMap[serverID]) == 0 {
|
||||
continue
|
||||
}
|
||||
show := listMap[serverID][0]
|
||||
showMap[serverID] = &show
|
||||
}
|
||||
return showMap
|
||||
}
|
||||
|
||||
func (s *ServerService) applyServerShowMap(serverMap *gmap.StrAnyMap, serverID uint32, showMap map[uint32]*model.ServerShow) {
|
||||
if serverMap == nil {
|
||||
return
|
||||
}
|
||||
serverMap.Set("owner", uint32(0))
|
||||
serverMap.Set("expire_time", time.Time{})
|
||||
show, ok := showMap[serverID]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if show.Name != "" {
|
||||
serverMap.Set("name", show.Name)
|
||||
}
|
||||
serverMap.Set("owner", show.Owner)
|
||||
serverMap.Set("expire_time", show.ExpireTime)
|
||||
serverMap.Set("servershow", show)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package service
|
||||
|
||||
import (
|
||||
"blazing/common/data"
|
||||
"blazing/common/utils"
|
||||
"blazing/cool"
|
||||
"blazing/modules/config/model"
|
||||
"context"
|
||||
@@ -21,6 +20,13 @@ func NewShinyService() *ShinyService {
|
||||
return &ShinyService{
|
||||
&cool.Service{
|
||||
Model: model.NewColorfulSkin(),
|
||||
ListQueryOp: &cool.QueryOp{
|
||||
Where: func(ctx context.Context) []g.Array {
|
||||
return []g.Array{
|
||||
{"is_enable", 1},
|
||||
}
|
||||
},
|
||||
},
|
||||
InsertParam: func(ctx context.Context) g.MapStrAny {
|
||||
admin := cool.GetAdmin(ctx)
|
||||
userId := admin.UserId
|
||||
@@ -43,18 +49,52 @@ func (s *ShinyService) ModifyBefore(ctx context.Context, method string, param g.
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (s *ShinyService) RandShiny(id uint32) *data.GlowFilter {
|
||||
|
||||
func (s *ShinyService) listByPetID(id uint32) []model.ColorfulSkin {
|
||||
var ret []model.ColorfulSkin
|
||||
|
||||
// 执行 Raw SQL 并扫描返回值
|
||||
dbm_enable(s.Model).
|
||||
Wheref(`bind_elf_ids @> ?::jsonb`, id).
|
||||
Wheref(`jsonb_typeof(bind_elf_ids) = ?`, "array").Scan(&ret)
|
||||
Wheref(`CAST(? AS text) = ANY(ARRAY(SELECT jsonb_array_elements_text(bind_elf_ids)))`, id).
|
||||
Wheref(`jsonb_typeof(bind_elf_ids) = ?`, "array").
|
||||
Scan(&ret)
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func pickColorfulSkinByWeight(items []model.ColorfulSkin) *model.ColorfulSkin {
|
||||
if len(items) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
totalWeight := 0
|
||||
for _, item := range items {
|
||||
if item.ElfProbability > 0 {
|
||||
totalWeight += int(item.ElfProbability)
|
||||
}
|
||||
}
|
||||
if totalWeight <= 0 {
|
||||
return &items[grand.Intn(len(items))]
|
||||
}
|
||||
|
||||
randomValue := grand.Intn(totalWeight)
|
||||
for i := range items {
|
||||
weight := int(items[i].ElfProbability)
|
||||
if weight <= 0 {
|
||||
continue
|
||||
}
|
||||
if randomValue < weight {
|
||||
return &items[i]
|
||||
}
|
||||
randomValue -= weight
|
||||
}
|
||||
|
||||
return &items[0]
|
||||
}
|
||||
|
||||
func (s *ShinyService) RandShiny(id uint32) *data.GlowFilter {
|
||||
ret := s.listByPetID(id)
|
||||
|
||||
for _, v := range ret {
|
||||
//print(v.ID)
|
||||
|
||||
id := v.ID
|
||||
|
||||
if grand.Meet(int(v.ElfProbability), 1000) {
|
||||
@@ -74,32 +114,16 @@ func (s *ShinyService) RandShiny(id uint32) *data.GlowFilter {
|
||||
|
||||
var t = data.GetDef()
|
||||
|
||||
t.ColorMatrixFilter = model.GenerateRandomOffspringMatrix().Get()
|
||||
t.ColorMatrixFilter = model.GenerateRandomParentMatrix().Get()
|
||||
|
||||
return &t
|
||||
}
|
||||
|
||||
func (s *ShinyService) RandomByWeightShiny(id uint32) *data.GlowFilter {
|
||||
|
||||
var ret []model.ColorfulSkin
|
||||
|
||||
// 执行 Raw SQL 并扫描返回值
|
||||
dbm_enable(s.Model).
|
||||
Wheref(`bind_elf_ids @> ?::jsonb`, id).
|
||||
Wheref(`jsonb_typeof(bind_elf_ids) = ?`, "array").Scan(&ret)
|
||||
if len(ret) == 0 {
|
||||
r := pickColorfulSkinByWeight(s.listByPetID(id))
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
var rets []model.ColorfulSkin
|
||||
var props []int
|
||||
|
||||
for _, v := range ret {
|
||||
rets = append(rets, v)
|
||||
props = append(props, int(v.ElfProbability))
|
||||
|
||||
}
|
||||
|
||||
r, _ := utils.RandomByWeight(rets, props)
|
||||
if cool.Config.ServerInfo.IsVip == 0 {
|
||||
m := cool.DBM(s.Model).Where("id", r.ID)
|
||||
m.Increment("refresh_count", 1)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
BIN
modules/model.test
Executable file
BIN
modules/model.test
Executable file
Binary file not shown.
@@ -31,6 +31,21 @@ 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非法"`
|
||||
}
|
||||
|
||||
type DonationServerInfoRes struct {
|
||||
ServerID uint32 `json:"server_id"`
|
||||
ServerName string `json:"server_name"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
type DonationRedeemReq struct {
|
||||
g.Meta `path:"/donation/redeem" method:"POST"`
|
||||
CDKCode string `json:"cdk_code" v:"required#CDK不能为空"`
|
||||
@@ -43,7 +58,6 @@ type DonationRedeemRes struct {
|
||||
ServerName string `json:"server_name"`
|
||||
OwnerID uint32 `json:"owner_id"`
|
||||
ServerExpireTime time.Time `json:"server_expire_time"`
|
||||
CDKExpireTime time.Time `json:"cdk_expire_time"`
|
||||
}
|
||||
|
||||
// DonationServerIDs 查询当前可用于服务器绑定CDK的服务器ID列表。
|
||||
@@ -58,6 +72,41 @@ 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 {
|
||||
return cool.Fail(err.Error()), nil
|
||||
}
|
||||
|
||||
admin := cool.GetAdmin(ctx)
|
||||
if admin == nil || admin.UserId == 0 {
|
||||
return cool.Fail("未登录或登录已失效"), nil
|
||||
}
|
||||
|
||||
server := configservice.NewServerService().GetServerID(req.ServerID)
|
||||
if server.OnlineID == 0 {
|
||||
return cool.Fail("服务器不存在"), nil
|
||||
}
|
||||
|
||||
return cool.Ok(&DonationServerInfoRes{
|
||||
ServerID: server.OnlineID,
|
||||
ServerName: server.Name,
|
||||
Remark: server.Desc,
|
||||
}), nil
|
||||
}
|
||||
|
||||
// DonationRedeem 兑换服务器绑定类型CDK并完成冠名写入。
|
||||
func (c *CdkController) DonationRedeem(ctx context.Context, req *DonationRedeemReq) (res *cool.BaseRes, err error) {
|
||||
if err = g.Validator().Data(req).Run(ctx); err != nil {
|
||||
@@ -86,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
|
||||
@@ -112,6 +161,5 @@ func (c *CdkController) DonationRedeem(ctx context.Context, req *DonationRedeemR
|
||||
ServerName: serverInfo.ServerName,
|
||||
OwnerID: serverInfo.OwnerID,
|
||||
ServerExpireTime: serverInfo.ServerExpireTime,
|
||||
CDKExpireTime: serverInfo.CDKExpireTime,
|
||||
}), nil
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user