完成dnd5e属性系统、技能系统、检定机制、简化的角色管理、法术系统、简单的增益系统以及一些游戏辅助功能

This commit is contained in:
Forest 2025-08-08 20:14:34 +08:00
parent 9d3cbf1f95
commit 7e9c4932fb
23 changed files with 2084 additions and 70 deletions

View File

@ -3,12 +3,13 @@ package core
import (
"ProjectWIND/LOG"
"ProjectWIND/wba"
"github.com/dop251/goja"
"os"
"path/filepath"
"reflect"
"strings"
"sync"
"github.com/dop251/goja"
)
var GlobalAppList = make(map[string]wba.AppInfo)
@ -56,6 +57,7 @@ var runtimePool = sync.Pool{
_ = rt.Set("wsp", ProtocolApi)
_ = rt.Set("wsd", DatabaseApi)
_ = rt.Set("wst", ToolsApi)
_ = rt.Set("wsc", CalculatorApi)
return rt
},
}
@ -74,6 +76,7 @@ func GetRuntimeCopy() *goja.Runtime {
_ = rt.Set("wsp", base.Get("wsp"))
_ = rt.Set("wsd", base.Get("wsd"))
_ = rt.Set("wst", base.Get("wst"))
_ = rt.Set("wsc", base.Get("wsc"))
// 放回基础runtime
runtimePool.Put(base)

View File

@ -3,6 +3,7 @@ package core
import (
"ProjectWIND/LOG"
"context"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/app/server"
"github.com/cloudwego/hertz/pkg/common/hlog"
@ -15,46 +16,45 @@ func WebServer(port string) *server.Hertz {
h := server.Default(server.WithHostPorts("0.0.0.0:" + port))
LOG.Info("WebUI已启动监听端口%v", port)
h.Use(LoggingMiddleware())
// 主页路由
h.GET("/", func(ctx context.Context, c *app.RequestContext) {
//返回webui/index.html
c.File("./webui/index.html")
})
h.GET("/index", func(ctx context.Context, c *app.RequestContext) {
//返回webui/index.html
c.File("./webui/index.html")
})
h.GET("/index.html", func(ctx context.Context, c *app.RequestContext) {
//返回webui/index.html
c.File("./webui/index.html")
})
// 账号设置页面路由
h.GET("/account.html", func(ctx context.Context, c *app.RequestContext) {
c.File("./webui/account.html")
})
// 辅助工具页面路由
h.GET("/tools.html", func(ctx context.Context, c *app.RequestContext) {
c.File("./webui/tools.html")
})
// 静态文件服务路由
h.GET("/styles.css", func(ctx context.Context, c *app.RequestContext) {
c.File("./webui/styles.css")
})
h.GET("/account.css", func(ctx context.Context, c *app.RequestContext) {
c.File("./webui/account.css")
})
h.GET("/tools.css", func(ctx context.Context, c *app.RequestContext) {
c.File("./webui/tools.css")
})
h.GET("/tools.js", func(ctx context.Context, c *app.RequestContext) {
c.File("./webui/tools.js")
})
// 图片资源路由
h.GET("/image/:filename", func(ctx context.Context, c *app.RequestContext) {
filename := c.Param("filename")
c.File("./webui/image/" + filename)
})
// 应用 API 路由
h.GET("/app/api/:appName/*action", func(ctx context.Context, c *app.RequestContext) {
appName := c.Param("appName")

156
dnd5e/config/classes.json Normal file
View File

@ -0,0 +1,156 @@
{
"fighter": {
"name": "战士",
"hitDie": 10,
"primaryAbility": ["strength", "dexterity"],
"savingThrows": ["strength", "constitution"],
"skillChoices": 2,
"availableSkills": [
"acrobatics", "animal_handling", "athletics", "history",
"insight", "intimidation", "perception", "survival"
],
"proficiencies": {
"armor": ["light", "medium", "heavy", "shields"],
"weapons": ["simple", "martial"],
"tools": []
},
"features": [
{"level": 1, "name": "战斗风格", "description": "选择一种战斗专精"},
{"level": 1, "name": "二度攻击", "description": "在升级时获得额外攻击"},
{"level": 2, "name": "行动如潮", "description": "每短休恢复一次的额外行动"}
],
"description": "精通所有武器和护甲的专业战士"
},
"wizard": {
"name": "法师",
"hitDie": 6,
"primaryAbility": ["intelligence"],
"savingThrows": ["intelligence", "wisdom"],
"skillChoices": 2,
"availableSkills": [
"arcana", "history", "insight", "investigation",
"medicine", "religion"
],
"proficiencies": {
"armor": [],
"weapons": ["daggers", "darts", "slings", "quarterstaffs", "light crossbows"],
"tools": []
},
"spellcasting": {
"ability": "intelligence",
"ritual": true,
"spellbook": true
},
"features": [
{"level": 1, "name": "法术书", "description": "记录已知法术的法术书"},
{"level": 1, "name": "奥术恢复", "description": "通过学习恢复法术位"},
{"level": 2, "name": "奥术传统", "description": "选择专精的法术学派"}
],
"description": "通过学习和实践掌握奥术力量的法术使用者"
},
"rogue": {
"name": "游荡者",
"hitDie": 8,
"primaryAbility": ["dexterity"],
"savingThrows": ["dexterity", "intelligence"],
"skillChoices": 4,
"availableSkills": [
"acrobatics", "athletics", "deception", "insight",
"intimidation", "investigation", "perception", "performance",
"persuasion", "sleight_of_hand", "stealth"
],
"proficiencies": {
"armor": ["light"],
"weapons": ["simple", "hand crossbows", "longswords", "rapiers", "shortswords"],
"tools": ["thieves' tools"]
},
"features": [
{"level": 1, "name": "专精", "description": "双倍熟练加值应用于特定技能"},
{"level": 1, "name": "偷袭攻击", "description": "在特定条件下造成额外伤害"},
{"level": 1, "name": "盗贼黑话", "description": "理解盗贼的秘密语言"}
],
"description": "依靠技巧、潜行和精确打击的专家"
},
"cleric": {
"name": "牧师",
"hitDie": 8,
"primaryAbility": ["wisdom"],
"savingThrows": ["wisdom", "charisma"],
"skillChoices": 2,
"availableSkills": [
"history", "insight", "medicine", "persuasion", "religion"
],
"proficiencies": {
"armor": ["light", "medium", "shields"],
"weapons": ["simple"],
"tools": []
},
"spellcasting": {
"ability": "wisdom",
"ritual": true,
"prepared": true
},
"features": [
{"level": 1, "name": "神术", "description": "从神祇获得的神圣法术能力"},
{"level": 1, "name": "神圣领域", "description": "选择侍奉的神祇领域"},
{"level": 2, "name": "引导神力", "description": "引导神祇力量的特殊能力"}
],
"description": "侍奉神祇并获得神圣力量的法术使用者"
},
"ranger": {
"name": "游侠",
"hitDie": 10,
"primaryAbility": ["dexterity", "wisdom"],
"savingThrows": ["strength", "dexterity"],
"skillChoices": 3,
"availableSkills": [
"animal_handling", "athletics", "insight", "investigation",
"nature", "perception", "stealth", "survival"
],
"proficiencies": {
"armor": ["light", "medium", "shields"],
"weapons": ["simple", "martial"],
"tools": []
},
"spellcasting": {
"ability": "wisdom",
"ritual": false,
"prepared": true,
"halfCaster": true
},
"features": [
{"level": 1, "name": "宿敌", "description": "对特定类型敌人的专精"},
{"level": 1, "name": "自然探索", "description": "在特定地形的专长"},
{"level": 2, "name": "战斗风格", "description": "选择战斗专精"}
],
"description": "荒野的守护者,精通追踪和生存"
},
"paladin": {
"name": "圣骑士",
"hitDie": 10,
"primaryAbility": ["strength", "charisma"],
"savingThrows": ["wisdom", "charisma"],
"skillChoices": 2,
"availableSkills": [
"athletics", "insight", "intimidation", "medicine",
"persuasion", "religion"
],
"proficiencies": {
"armor": ["light", "medium", "heavy", "shields"],
"weapons": ["simple", "martial"],
"tools": []
},
"spellcasting": {
"ability": "charisma",
"ritual": false,
"prepared": true,
"halfCaster": true
},
"features": [
{"level": 1, "name": "神圣感知", "description": "感知邪恶存在的能力"},
{"level": 1, "name": "圣疗", "description": "治疗伤势的神圣力量"},
{"level": 2, "name": "战斗风格", "description": "选择战斗专精"}
],
"description": "被神圣誓言约束的神圣战士"
}
}

143
dnd5e/config/races.json Normal file
View File

@ -0,0 +1,143 @@
{
"human": {
"name": "人类",
"abilityBonus": {
"all": 1
},
"size": "Medium",
"speed": 30,
"languages": ["通用语"],
"traits": [
"额外技能熟练",
"额外专长"
],
"description": "人类是最适应性强的种族,在各个领域都能取得成就"
},
"elf": {
"name": "精灵",
"abilityBonus": {
"dexterity": 2
},
"size": "Medium",
"speed": 30,
"languages": ["通用语", "精灵语"],
"traits": [
"敏锐感官",
"精灵血统",
"冥想",
"精灵武器训练"
],
"description": "精灵拥有超自然的优雅和敏捷,与魔法有着深厚的联系"
},
"dwarf": {
"name": "矮人",
"abilityBonus": {
"constitution": 2
},
"size": "Medium",
"speed": 25,
"languages": ["通用语", "矮人语"],
"traits": [
"黑暗视觉",
"矮人韧性",
"矮人战斗训练",
"工具熟练",
"巨石敏感"
],
"description": "矮人以其坚韧、工艺技能和对魔法的抗性而闻名"
},
"halfling": {
"name": "半身人",
"abilityBonus": {
"dexterity": 2
},
"size": "Small",
"speed": 25,
"languages": ["通用语", "半身人语"],
"traits": [
"幸运",
"勇敢",
"半身人敏捷"
],
"description": "半身人虽然身材矮小,但拥有非凡的勇气和幸运"
},
"dragonborn": {
"name": "龙裔",
"abilityBonus": {
"strength": 2,
"charisma": 1
},
"size": "Medium",
"speed": 30,
"languages": ["通用语", "龙语"],
"traits": [
"龙族血统",
"吐息武器",
"伤害抗性"
],
"description": "龙裔承继了古龙的血脉,拥有强大的力量和威严"
},
"gnome": {
"name": "侏儒",
"abilityBonus": {
"intelligence": 2
},
"size": "Small",
"speed": 25,
"languages": ["通用语", "侏儒语"],
"traits": [
"黑暗视觉",
"侏儒狡黠",
"修补匠"
],
"description": "侏儒以其好奇心、发明才能和对魔法的亲和力著称"
},
"half_elf": {
"name": "半精灵",
"abilityBonus": {
"charisma": 2,
"choice": 2
},
"size": "Medium",
"speed": 30,
"languages": ["通用语", "精灵语"],
"traits": [
"敏锐感官",
"精灵血统",
"技能多样性"
],
"description": "半精灵结合了人类的适应性和精灵的优雅"
},
"half_orc": {
"name": "半兽人",
"abilityBonus": {
"strength": 2,
"constitution": 1
},
"size": "Medium",
"speed": 30,
"languages": ["通用语", "兽人语"],
"traits": [
"黑暗视觉",
"不屈意志",
"野蛮攻击"
],
"description": "半兽人在两个世界间挣扎,但拥有强大的战斗能力"
},
"tiefling": {
"name": "魔人",
"abilityBonus": {
"intelligence": 1,
"charisma": 2
},
"size": "Medium",
"speed": 30,
"languages": ["通用语", "炼狱语"],
"traits": [
"黑暗视觉",
"地狱抗性",
"炼狱传承"
],
"description": "魔人承载着恶魔血统,拥有神秘的魔法能力"
}
}

92
dnd5e/config/skills.json Normal file
View File

@ -0,0 +1,92 @@
{
"athletics": {
"name": "运动",
"ability": "strength",
"description": "用于爬行、跳跃、游泳等体力活动"
},
"acrobatics": {
"name": "杂技",
"ability": "dexterity",
"description": "用于保持平衡、翻滚和灵巧动作"
},
"sleight_of_hand": {
"name": "巧手",
"ability": "dexterity",
"description": "用于扒窃、隐藏物品和巧妙操作"
},
"stealth": {
"name": "潜行",
"ability": "dexterity",
"description": "用于隐匿和悄无声息地移动"
},
"arcana": {
"name": "奥秘",
"ability": "intelligence",
"description": "关于法术、魔法物品和奥术现象的知识"
},
"history": {
"name": "历史",
"ability": "intelligence",
"description": "关于历史事件、传说人物和古老王国的知识"
},
"investigation": {
"name": "调查",
"ability": "intelligence",
"description": "用于寻找线索、推理和分析信息"
},
"nature": {
"name": "自然",
"ability": "intelligence",
"description": "关于地形、植物、动物和天气的知识"
},
"religion": {
"name": "宗教",
"ability": "intelligence",
"description": "关于神祇、仪式、祈祷和宗教等级的知识"
},
"animal_handling": {
"name": "驯兽",
"ability": "wisdom",
"description": "用于安抚驯化动物或让野兽保持冷静"
},
"insight": {
"name": "洞察",
"ability": "wisdom",
"description": "用于判断他人的真实意图和情绪"
},
"medicine": {
"name": "医药",
"ability": "wisdom",
"description": "用于稳定濒死角色和诊断疾病"
},
"perception": {
"name": "察觉",
"ability": "wisdom",
"description": "用于注意隐藏物体、角色或生物"
},
"survival": {
"name": "生存",
"ability": "wisdom",
"description": "用于追踪、觅食和在野外求生"
},
"deception": {
"name": "欺骗",
"ability": "charisma",
"description": "用于隐瞒真相或说服他人相信谎言"
},
"intimidation": {
"name": "威吓",
"ability": "charisma",
"description": "用于通过威胁影响他人"
},
"performance": {
"name": "表演",
"ability": "charisma",
"description": "用于娱乐观众,包括音乐、舞蹈、戏剧等"
},
"persuasion": {
"name": "说服",
"ability": "charisma",
"description": "用于善意地影响他人或达成友好协议"
}
}

View File

@ -0,0 +1,597 @@
package dnd5e
import (
"ProjectWIND/wba"
"encoding/json"
"fmt"
"strconv"
"time"
)
// 这是一个实验性实现,用于测试和确定需要的接口
// ===== 核心数据结构 =====
// Character DND5e角色结构
type Character struct {
ID string `json:"id"`
Name string `json:"name"`
PlayerName string `json:"player_name"`
Race string `json:"race"`
Class string `json:"class"`
Level int `json:"level"`
Background string `json:"background"`
Alignment string `json:"alignment"`
Abilities AbilityScores `json:"abilities"`
HitPoints HitPointInfo `json:"hit_points"`
ArmorClass int `json:"armor_class"`
Skills map[string]int `json:"skills"`
SavingThrows map[string]int `json:"saving_throws"`
Initiative int `json:"initiative"`
Speed int `json:"speed"`
SpellSlots map[int]int `json:"spell_slots"`
KnownSpells []string `json:"known_spells"`
Equipment []EquipmentItem `json:"equipment"`
Experience int `json:"experience"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// AbilityScores 六大基础属性
type AbilityScores struct {
Strength int `json:"strength"` // 力量
Dexterity int `json:"dexterity"` // 敏捷
Constitution int `json:"constitution"` // 体质
Intelligence int `json:"intelligence"` // 智力
Wisdom int `json:"wisdom"` // 感知
Charisma int `json:"charisma"` // 魅力
}
// HitPointInfo 生命值信息
type HitPointInfo struct {
Maximum int `json:"maximum"`
Current int `json:"current"`
Temp int `json:"temporary"`
}
// EquipmentItem 装备物品
type EquipmentItem struct {
Name string `json:"name"`
Type string `json:"type"`
Quantity int `json:"quantity"`
Weight float64 `json:"weight"`
Value int `json:"value"`
Properties []string `json:"properties"`
Equipped bool `json:"equipped"`
}
// CheckResult 检定结果
type CheckResult struct {
Roll int `json:"roll"`
Modifier int `json:"modifier"`
Total int `json:"total"`
DC int `json:"dc"`
Success bool `json:"success"`
CriticalHit bool `json:"critical_hit"`
CriticalMiss bool `json:"critical_miss"`
}
// ===== 需要的接口定义 =====
// DND5eManager 主要管理接口
type DND5eManager struct {
calculator wba.WindStandardCalculator
database wba.WindStandardDatabaseAPI
protocol wba.WindStandardProtocolAPI
appInfo wba.AppInfo
}
// NewDND5eManager 创建DND5e管理器
func NewDND5eManager(calc wba.WindStandardCalculator, db wba.WindStandardDatabaseAPI, proto wba.WindStandardProtocolAPI, app wba.AppInfo) *DND5eManager {
return &DND5eManager{
calculator: calc,
database: db,
protocol: proto,
appInfo: app,
}
}
// ===== 角色管理功能 =====
// CreateCharacter 创建角色
func (dm *DND5eManager) CreateCharacter(userID string, name string, race string, class string) (*Character, error) {
// 生成角色ID
characterID := fmt.Sprintf("dnd5e_%s_%d", userID, time.Now().Unix())
// 生成基础属性4d6取最高3个
abilities := AbilityScores{
Strength: dm.generateAbilityScore(),
Dexterity: dm.generateAbilityScore(),
Constitution: dm.generateAbilityScore(),
Intelligence: dm.generateAbilityScore(),
Wisdom: dm.generateAbilityScore(),
Charisma: dm.generateAbilityScore(),
}
// 创建角色
character := &Character{
ID: characterID,
Name: name,
PlayerName: userID,
Race: race,
Class: class,
Level: 1,
Abilities: abilities,
HitPoints: HitPointInfo{
Maximum: dm.calculateInitialHP(class, abilities.Constitution),
Current: dm.calculateInitialHP(class, abilities.Constitution),
Temp: 0,
},
ArmorClass: 10 + dm.getAbilityModifier(abilities.Dexterity),
Skills: make(map[string]int),
SavingThrows: make(map[string]int),
Initiative: dm.getAbilityModifier(abilities.Dexterity),
Speed: 30, // 默认速度
SpellSlots: make(map[int]int),
KnownSpells: []string{},
Equipment: []EquipmentItem{},
Experience: 0,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
// 应用种族和职业修正
dm.applyRaceModifiers(character, race)
dm.applyClassModifiers(character, class)
// 保存到数据库
characterJSON, err := json.Marshal(character)
if err != nil {
return nil, err
}
// 需要的数据库接口:存储角色数据
success := dm.database.SetOutUserVariable(dm.appInfo, "dnd5e_characters",
wba.MessageEventInfo{UserId: parseUserID(userID)}, characterID, string(characterJSON))
if !success {
return nil, fmt.Errorf("failed to save character to database")
}
// 更新用户的角色列表
dm.addCharacterToUserList(userID, characterID)
return character, nil
}
// GetCharacter 获取角色
func (dm *DND5eManager) GetCharacter(userID string, characterID string) (*Character, error) {
// 需要的数据库接口:读取角色数据
characterJSON, success := dm.database.GetOutUserVariable(dm.appInfo, "dnd5e_characters",
wba.MessageEventInfo{UserId: parseUserID(userID)}, characterID)
if !success {
return nil, fmt.Errorf("character not found")
}
var character Character
if err := json.Unmarshal([]byte(characterJSON), &character); err != nil {
return nil, err
}
return &character, nil
}
// ===== 属性和技能系统 =====
// generateAbilityScore 生成属性值4d6取最高3个
func (dm *DND5eManager) generateAbilityScore() int {
rolls := []int64{}
for i := 0; i < 4; i++ {
roll := dm.calculator.RandI(1, 6)
rolls = append(rolls, roll)
}
// 排序并取最高3个
total := int64(0)
for i := 0; i < 3; i++ {
max := int64(0)
maxIndex := 0
for j, roll := range rolls {
if roll > max {
max = roll
maxIndex = j
}
}
total += max
rolls[maxIndex] = 0 // 标记为已使用
}
return int(total)
}
// getAbilityModifier 计算属性调整值
func (dm *DND5eManager) getAbilityModifier(score int) int {
return (score - 10) / 2
}
// MakeAbilityCheck 进行属性检定
func (dm *DND5eManager) MakeAbilityCheck(character *Character, ability string, dc int) CheckResult {
// 需要的计算器接口投掷1d20
roll := dm.calculator.RandI(1, 20)
// 获取属性值
var abilityScore int
switch ability {
case "strength":
abilityScore = character.Abilities.Strength
case "dexterity":
abilityScore = character.Abilities.Dexterity
case "constitution":
abilityScore = character.Abilities.Constitution
case "intelligence":
abilityScore = character.Abilities.Intelligence
case "wisdom":
abilityScore = character.Abilities.Wisdom
case "charisma":
abilityScore = character.Abilities.Charisma
default:
abilityScore = 10
}
modifier := dm.getAbilityModifier(abilityScore)
total := int(roll) + modifier
return CheckResult{
Roll: int(roll),
Modifier: modifier,
Total: total,
DC: dc,
Success: total >= dc,
CriticalHit: roll == 20,
CriticalMiss: roll == 1,
}
}
// MakeSkillCheck 进行技能检定
func (dm *DND5eManager) MakeSkillCheck(character *Character, skill string, dc int) CheckResult {
roll := dm.calculator.RandI(1, 20)
// 获取技能调整值
skillModifier := dm.getSkillModifier(character, skill)
total := int(roll) + skillModifier
return CheckResult{
Roll: int(roll),
Modifier: skillModifier,
Total: total,
DC: dc,
Success: total >= dc,
CriticalHit: roll == 20,
CriticalMiss: roll == 1,
}
}
// ===== 战斗系统 =====
// MakeAttackRoll 进行攻击骰
func (dm *DND5eManager) MakeAttackRoll(character *Character, weaponName string, targetAC int) CheckResult {
roll := dm.calculator.RandI(1, 20)
// 简化:使用力量调整值作为攻击调整值
modifier := dm.getAbilityModifier(character.Abilities.Strength)
total := int(roll) + modifier
return CheckResult{
Roll: int(roll),
Modifier: modifier,
Total: total,
DC: targetAC,
Success: total >= targetAC,
CriticalHit: roll == 20,
CriticalMiss: roll == 1,
}
}
// RollDamage 投掷伤害
func (dm *DND5eManager) RollDamage(character *Character, weaponName string) int {
// 简化使用1d8+力量调整值
roll := dm.calculator.RandI(1, 8)
modifier := dm.getAbilityModifier(character.Abilities.Strength)
return int(roll) + modifier
}
// ===== 指令处理器 =====
// HandleDNDCommand 处理DND指令
func (dm *DND5eManager) HandleDNDCommand(args []string, msg wba.MessageEventInfo) {
if len(args) == 0 {
dm.protocol.SendMsg(msg, "DND5e指令帮助\n.dnd create <角色名> <种族> <职业> - 创建角色\n.dnd show - 显示角色信息\n.dnd roll <属性> - 属性检定", false)
return
}
userID := strconv.FormatInt(msg.UserId, 10)
switch args[0] {
case "create":
if len(args) < 4 {
dm.protocol.SendMsg(msg, "用法: .dnd create <角色名> <种族> <职业>", false)
return
}
name, race, class := args[1], args[2], args[3]
character, err := dm.CreateCharacter(userID, name, race, class)
if err != nil {
dm.protocol.SendMsg(msg, fmt.Sprintf("创建角色失败: %v", err), false)
return
}
response := fmt.Sprintf("成功创建角色 %s\n种族: %s\n职业: %s\n等级: %d\n属性:\n力量: %d\n敏捷: %d\n体质: %d\n智力: %d\n感知: %d\n魅力: %d",
character.Name, character.Race, character.Class, character.Level,
character.Abilities.Strength, character.Abilities.Dexterity, character.Abilities.Constitution,
character.Abilities.Intelligence, character.Abilities.Wisdom, character.Abilities.Charisma)
dm.protocol.SendMsg(msg, response, false)
case "show":
// 获取用户的当前角色
character, err := dm.getActiveCharacter(userID)
if err != nil {
dm.protocol.SendMsg(msg, "未找到角色,请先创建角色", false)
return
}
response := dm.formatCharacterInfo(character)
dm.protocol.SendMsg(msg, response, false)
case "roll":
if len(args) < 2 {
dm.protocol.SendMsg(msg, "用法: .dnd roll <属性名>", false)
return
}
character, err := dm.getActiveCharacter(userID)
if err != nil {
dm.protocol.SendMsg(msg, "未找到角色,请先创建角色", false)
return
}
ability := args[1]
dc := 15 // 默认DC
if len(args) > 2 {
if parsedDC, err := strconv.Atoi(args[2]); err == nil {
dc = parsedDC
}
}
result := dm.MakeAbilityCheck(character, ability, dc)
response := fmt.Sprintf("%s 进行 %s 检定:\n骰子: %d\n调整值: %+d\n总计: %d\nDC: %d\n结果: %s",
character.Name, ability, result.Roll, result.Modifier, result.Total, result.DC,
map[bool]string{true: "成功", false: "失败"}[result.Success])
if result.CriticalHit {
response += "\n🎉 大成功!"
} else if result.CriticalMiss {
response += "\n💥 大失败!"
}
dm.protocol.SendMsg(msg, response, false)
default:
dm.protocol.SendMsg(msg, "未知的DND指令使用 .dnd 查看帮助", false)
}
}
// ===== 辅助函数 =====
// 计算初始生命值
func (dm *DND5eManager) calculateInitialHP(class string, constitution int) int {
hitDie := map[string]int{
"fighter": 10,
"wizard": 6,
"rogue": 8,
"cleric": 8,
}
die := hitDie[class]
if die == 0 {
die = 8 // 默认
}
return die + dm.getAbilityModifier(constitution)
}
// 应用种族修正
func (dm *DND5eManager) applyRaceModifiers(character *Character, race string) {
switch race {
case "human":
// 人类:所有属性+1
character.Abilities.Strength++
character.Abilities.Dexterity++
character.Abilities.Constitution++
character.Abilities.Intelligence++
character.Abilities.Wisdom++
character.Abilities.Charisma++
case "elf":
// 精灵:敏捷+2
character.Abilities.Dexterity += 2
case "dwarf":
// 矮人:体质+2
character.Abilities.Constitution += 2
}
}
// 应用职业修正
func (dm *DND5eManager) applyClassModifiers(character *Character, class string) {
switch class {
case "fighter":
// 战士:力量和体质豁免熟练
character.SavingThrows["strength"] = dm.getAbilityModifier(character.Abilities.Strength) + 2
character.SavingThrows["constitution"] = dm.getAbilityModifier(character.Abilities.Constitution) + 2
case "wizard":
// 法师:智力和感知豁免熟练
character.SavingThrows["intelligence"] = dm.getAbilityModifier(character.Abilities.Intelligence) + 2
character.SavingThrows["wisdom"] = dm.getAbilityModifier(character.Abilities.Wisdom) + 2
}
}
// 获取技能调整值
func (dm *DND5eManager) getSkillModifier(character *Character, skill string) int {
// 技能对应的属性映射
skillAbilities := map[string]string{
"athletics": "strength",
"acrobatics": "dexterity",
"stealth": "dexterity",
"arcana": "intelligence",
"history": "intelligence",
"investigation": "intelligence",
"nature": "intelligence",
"religion": "intelligence",
"animal_handling": "wisdom",
"insight": "wisdom",
"medicine": "wisdom",
"perception": "wisdom",
"survival": "wisdom",
"deception": "charisma",
"intimidation": "charisma",
"performance": "charisma",
"persuasion": "charisma",
"sleight_of_hand": "dexterity",
}
ability := skillAbilities[skill]
if ability == "" {
return 0
}
// 获取基础属性调整值
var abilityScore int
switch ability {
case "strength":
abilityScore = character.Abilities.Strength
case "dexterity":
abilityScore = character.Abilities.Dexterity
case "constitution":
abilityScore = character.Abilities.Constitution
case "intelligence":
abilityScore = character.Abilities.Intelligence
case "wisdom":
abilityScore = character.Abilities.Wisdom
case "charisma":
abilityScore = character.Abilities.Charisma
default:
abilityScore = 10
}
modifier := dm.getAbilityModifier(abilityScore)
// 如果有熟练度,添加熟练度加值
if proficiency, exists := character.Skills[skill]; exists {
modifier += proficiency
}
return modifier
}
// 获取活跃角色
func (dm *DND5eManager) getActiveCharacter(userID string) (*Character, error) {
// 简化:获取用户的第一个角色
characterList, success := dm.database.GetOutUserVariable(dm.appInfo, "dnd5e_character_list",
wba.MessageEventInfo{UserId: parseUserID(userID)}, "characters")
if !success {
return nil, fmt.Errorf("no characters found")
}
var characters []string
if err := json.Unmarshal([]byte(characterList), &characters); err != nil {
return nil, err
}
if len(characters) == 0 {
return nil, fmt.Errorf("no characters found")
}
return dm.GetCharacter(userID, characters[0])
}
// 添加角色到用户列表
func (dm *DND5eManager) addCharacterToUserList(userID string, characterID string) {
characterList, success := dm.database.GetOutUserVariable(dm.appInfo, "dnd5e_character_list",
wba.MessageEventInfo{UserId: parseUserID(userID)}, "characters")
var characters []string
if success {
json.Unmarshal([]byte(characterList), &characters)
}
characters = append(characters, characterID)
charactersJSON, _ := json.Marshal(characters)
dm.database.SetOutUserVariable(dm.appInfo, "dnd5e_character_list",
wba.MessageEventInfo{UserId: parseUserID(userID)}, "characters", string(charactersJSON))
}
// 格式化角色信息
func (dm *DND5eManager) formatCharacterInfo(character *Character) string {
return fmt.Sprintf(`📋 角色信息
姓名: %s
种族: %s | 职业: %s | 等级: %d
生命值: %d/%d | 护甲等级: %d
属性:
力量: %d (%+d)
敏捷: %d (%+d)
体质: %d (%+d)
智力: %d (%+d)
感知: %d (%+d)
魅力: %d (%+d)
🎯 战斗:
先攻: %+d | 速度: %d尺`,
character.Name, character.Race, character.Class, character.Level,
character.HitPoints.Current, character.HitPoints.Maximum, character.ArmorClass,
character.Abilities.Strength, dm.getAbilityModifier(character.Abilities.Strength),
character.Abilities.Dexterity, dm.getAbilityModifier(character.Abilities.Dexterity),
character.Abilities.Constitution, dm.getAbilityModifier(character.Abilities.Constitution),
character.Abilities.Intelligence, dm.getAbilityModifier(character.Abilities.Intelligence),
character.Abilities.Wisdom, dm.getAbilityModifier(character.Abilities.Wisdom),
character.Abilities.Charisma, dm.getAbilityModifier(character.Abilities.Charisma),
character.Initiative, character.Speed)
}
// 辅助函数解析用户ID
func parseUserID(userID string) int64 {
id, _ := strconv.ParseInt(userID, 10, 64)
return id
}
// ===== 需要的接口总结 =====
/*
通过这个实验性实现我们可以确定需要以下Go语言接口
1. WindStandardCalculator 接口已存在:
- RandI(from, to int64) int64 // 随机数生成,用于骰子投掷
2. WindStandardDatabaseAPI 接口需要定义:
- SetOutUserVariable(app AppInfo, datamap string, msg MessageEventInfo, key string, value string) bool
- GetOutUserVariable(app AppInfo, datamap string, msg MessageEventInfo, key string) (string, bool)
- SetOutGlobalVariable(app AppInfo, datamap string, key string, value string) bool
- GetOutGlobalVariable(app AppInfo, datamap string, key string) (string, bool)
3. WindStandardProtocolAPI 接口已存在:
- SendMsg(msg MessageEventInfo, message string, autoEscape bool)
4. 配置管理接口需要扩展:
- 读取JSON配置文件
- 热重载配置
- 默认配置管理
5. 文件系统接口可能需要:
- 读取静态数据文件种族职业法术等
- 导入/导出角色数据
6. 缓存接口性能优化:
- 角色数据缓存
- 配置数据缓存
这个实现展示了DND5e系统的核心功能和所需的接口
*/

306
dnd5e/go_interfaces.go Normal file
View File

@ -0,0 +1,306 @@
package main
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/ProjectWIND/wba"
)
// DND5eDatabaseAPI Go接口封装 - 提供DND5e专用的数据库操作
type DND5eDatabaseAPI struct {
wsd wba.WindStandardDataBaseAPI
appInfo wba.AppInfo
msgInfo wba.MessageEventInfo
}
// NewDND5eDatabaseAPI 创建DND5e数据库API实例
func NewDND5eDatabaseAPI(wsd wba.WindStandardDataBaseAPI, appInfo wba.AppInfo, msgInfo wba.MessageEventInfo) *DND5eDatabaseAPI {
return &DND5eDatabaseAPI{
wsd: wsd,
appInfo: appInfo,
msgInfo: msgInfo,
}
}
// SaveCharacterData 保存角色数据
func (api *DND5eDatabaseAPI) SaveCharacterData(characterID string, characterData string) error {
key := fmt.Sprintf("dnd5e_character_%s", characterID)
success := api.wsd.SetOutGlobalVariable(api.appInfo, "dnd5e", api.msgInfo, key, characterData)
if !success {
return fmt.Errorf("failed to save character data for ID: %s", characterID)
}
return nil
}
// LoadCharacterData 加载角色数据
func (api *DND5eDatabaseAPI) LoadCharacterData(characterID string) (string, bool, error) {
key := fmt.Sprintf("dnd5e_character_%s", characterID)
data, exists := api.wsd.GetOutGlobalVariable(api.appInfo, "dnd5e", api.msgInfo, key)
return data, exists, nil
}
// SaveUserData 保存用户数据
func (api *DND5eDatabaseAPI) SaveUserData(key string, data string) error {
success := api.wsd.SetUserVariable(api.appInfo, api.msgInfo, key, data)
if !success {
return fmt.Errorf("failed to save user data for key: %s", key)
}
return nil
}
// LoadUserData 加载用户数据
func (api *DND5eDatabaseAPI) LoadUserData(key string) (string, bool, error) {
data, exists := api.wsd.GetUserVariable(api.appInfo, api.msgInfo, key)
return data, exists, nil
}
// SaveInitiativeData 保存先攻数据
func (api *DND5eDatabaseAPI) SaveInitiativeData(characterName string, initiative int) error {
key := fmt.Sprintf("initiative_%s", characterName)
value := strconv.Itoa(initiative)
success := api.wsd.SetOutGlobalVariable(api.appInfo, "dnd5e", api.msgInfo, key, value)
if !success {
return fmt.Errorf("failed to save initiative for character: %s", characterName)
}
return nil
}
// LoadInitiativeData 加载先攻数据
func (api *DND5eDatabaseAPI) LoadInitiativeData(characterName string) (int, bool, error) {
key := fmt.Sprintf("initiative_%s", characterName)
data, exists := api.wsd.GetOutGlobalVariable(api.appInfo, "dnd5e", api.msgInfo, key)
if !exists {
return 0, false, nil
}
initiative, err := strconv.Atoi(data)
if err != nil {
return 0, false, fmt.Errorf("invalid initiative data for character %s: %s", characterName, data)
}
return initiative, true, nil
}
// DND5eDiceAPI Go接口封装 - 提供DND5e专用的骰子计算
type DND5eDiceAPI struct {
wsc wba.WindStandardCalculator
}
// NewDND5eDiceAPI 创建DND5e骰子API实例
func NewDND5eDiceAPI(wsc wba.WindStandardCalculator) *DND5eDiceAPI {
return &DND5eDiceAPI{wsc: wsc}
}
// RollDice 执行骰子投掷
func (api *DND5eDiceAPI) RollDice(expression string) (wba.Result, error) {
result := api.wsc.Roll(expression)
return result, nil
}
// RollWithAdvantage 优势投掷
func (api *DND5eDiceAPI) RollWithAdvantage(baseExpression string) (wba.Result, error) {
// 对于优势投掷使用2d20kh1取较高值
expression := strings.Replace(baseExpression, "1d20", "2d20kh1", 1)
return api.RollDice(expression)
}
// RollWithDisadvantage 劣势投掷
func (api *DND5eDiceAPI) RollWithDisadvantage(baseExpression string) (wba.Result, error) {
// 对于劣势投掷使用2d20kl1取较低值
expression := strings.Replace(baseExpression, "1d20", "2d20kl1", 1)
return api.RollDice(expression)
}
// GenerateRandomStats 生成随机属性值4d6去最低
func (api *DND5eDiceAPI) GenerateRandomStats() ([]int, error) {
stats := make([]int, 6)
for i := 0; i < 6; i++ {
// 投掷4d6去掉最低的一个
result := api.wsc.Roll("4d6dl1")
stats[i] = int(result.Value)
}
return stats, nil
}
// DND5eMessageAPI Go接口封装 - 提供DND5e专用的消息发送
type DND5eMessageAPI struct {
wsp wba.WindStandardProtocolAPI
msgInfo wba.MessageEventInfo
}
// NewDND5eMessageAPI 创建DND5e消息API实例
func NewDND5eMessageAPI(wsp wba.WindStandardProtocolAPI, msgInfo wba.MessageEventInfo) *DND5eMessageAPI {
return &DND5eMessageAPI{
wsp: wsp,
msgInfo: msgInfo,
}
}
// SendMessage 发送消息
func (api *DND5eMessageAPI) SendMessage(message string) error {
api.wsp.SendMsg(api.msgInfo, message, false)
return nil
}
// ReplyMessage 回复消息
func (api *DND5eMessageAPI) ReplyMessage(message string) error {
// 添加回复标识
replyMessage := fmt.Sprintf("@%s %s", api.msgInfo.UserName, message)
api.wsp.SendMsg(api.msgInfo, replyMessage, false)
return nil
}
// SendFormattedMessage 发送格式化消息
func (api *DND5eMessageAPI) SendFormattedMessage(title string, content string) error {
message := fmt.Sprintf("【%s】\n%s", title, content)
return api.SendMessage(message)
}
// DND5eAPI 综合API - 整合所有DND5e相关的Go接口
type DND5eAPI struct {
Database *DND5eDatabaseAPI
Dice *DND5eDiceAPI
Message *DND5eMessageAPI
Tools wba.WindStandardTools
AppInfo wba.AppInfo
MsgInfo wba.MessageEventInfo
}
// NewDND5eAPI 创建DND5e综合API实例
func NewDND5eAPI(
wsd wba.WindStandardDataBaseAPI,
wsc wba.WindStandardCalculator,
wsp wba.WindStandardProtocolAPI,
wst wba.WindStandardTools,
appInfo wba.AppInfo,
msgInfo wba.MessageEventInfo,
) *DND5eAPI {
return &DND5eAPI{
Database: NewDND5eDatabaseAPI(wsd, appInfo, msgInfo),
Dice: NewDND5eDiceAPI(wsc),
Message: NewDND5eMessageAPI(wsp, msgInfo),
Tools: wst,
AppInfo: appInfo,
MsgInfo: msgInfo,
}
}
// LogInfo 记录信息日志
func (api *DND5eAPI) LogInfo(format string, args ...interface{}) {
api.Tools.LogWith("info", format, args...)
}
// LogError 记录错误日志
func (api *DND5eAPI) LogError(format string, args ...interface{}) {
api.Tools.LogWith("error", format, args...)
}
// LogDebug 记录调试日志
func (api *DND5eAPI) LogDebug(format string, args ...interface{}) {
api.Tools.LogWith("debug", format, args...)
}
// ParseJSONCharacter 解析JSON格式的角色数据
func (api *DND5eAPI) ParseJSONCharacter(jsonData string) (map[string]interface{}, error) {
var character map[string]interface{}
err := json.Unmarshal([]byte(jsonData), &character)
if err != nil {
api.LogError("Failed to parse character JSON: %v", err)
return nil, err
}
return character, nil
}
// SerializeCharacter 序列化角色数据为JSON
func (api *DND5eAPI) SerializeCharacter(character map[string]interface{}) (string, error) {
jsonData, err := json.Marshal(character)
if err != nil {
api.LogError("Failed to serialize character: %v", err)
return "", err
}
return string(jsonData), nil
}
// ValidateCharacterData 验证角色数据的完整性
func (api *DND5eAPI) ValidateCharacterData(character map[string]interface{}) []string {
var errors []string
// 检查必需字段
requiredFields := []string{"id", "name", "playerName", "level"}
for _, field := range requiredFields {
if _, exists := character[field]; !exists {
errors = append(errors, fmt.Sprintf("missing required field: %s", field))
}
}
// 检查等级范围
if level, ok := character["level"].(float64); ok {
if level < 1 || level > 20 {
errors = append(errors, "level must be between 1 and 20")
}
}
return errors
}
// GetCharacterList 获取用户的角色列表
func (api *DND5eAPI) GetCharacterList() ([]string, error) {
data, exists, err := api.Database.LoadUserData("dnd5e_character_list")
if err != nil {
return nil, err
}
if !exists || data == "" {
return []string{}, nil
}
var characterList []string
err = json.Unmarshal([]byte(data), &characterList)
if err != nil {
api.LogError("Failed to parse character list: %v", err)
return []string{}, nil
}
return characterList, nil
}
// AddCharacterToList 将角色添加到用户角色列表
func (api *DND5eAPI) AddCharacterToList(characterID string) error {
characterList, err := api.GetCharacterList()
if err != nil {
return err
}
// 检查是否已存在
for _, id := range characterList {
if id == characterID {
return nil // 已存在,不需要重复添加
}
}
// 添加到列表
characterList = append(characterList, characterID)
// 保存更新后的列表
jsonData, err := json.Marshal(characterList)
if err != nil {
return err
}
return api.Database.SaveUserData("dnd5e_character_list", string(jsonData))
}
// SetActiveCharacter 设置当前激活的角色
func (api *DND5eAPI) SetActiveCharacter(characterID string) error {
return api.Database.SaveUserData("dnd5e_active_character", characterID)
}
// GetActiveCharacter 获取当前激活的角色ID
func (api *DND5eAPI) GetActiveCharacter() (string, bool, error) {
return api.Database.LoadUserData("dnd5e_active_character")
}

633
docs/dnd5e_design.md Normal file
View File

@ -0,0 +1,633 @@
# DND5e 规则包设计文档
## 1. 项目介绍
DND5e规则包是基于ProjectWIND框架开发的龙与地下城第五版规则系统实现。本规则包提供完整的DND5e角色管理、骰子系统、技能检定等核心功能支持中文界面和命令。
### 1.1 核心功能
- **角色管理系统**:角色创建、查看、修改、删除
- **属性系统**:六大基础属性(力量、敏捷、体质、智力、感知、魅力)
- **技能系统**18项核心技能及对应属性检定
- **骰子系统**:标准骰子表达式、优势/劣势机制
- **先攻管理**:战斗先攻值设置和管理
- **法术位系统**:法术位设置、消耗、恢复
- **生命值管理**HP跟踪、伤害计算、治疗
- **死亡豁免**:昏迷状态下的死亡豁免检定
- **休息系统**:长休恢复机制
- **BUFF系统**:临时属性修正和状态管理
- **数据持久化**:角色数据存储与管理
### 1.2 技术使用
- **JavaScript主导**核心逻辑使用JavaScript实现
- **Go接口调用**仅在需要时调用ProjectWIND的Go接口
## 2. 系统架构
### 2.1 整体架构
```
DND5e规则包
├── JavaScript核心
│ ├── 角色管理 (Character)
│ ├── 属性系统 (AbilityScores)
│ ├── 技能系统 (SkillSystem)
│ ├── 骰子系统 (DiceSystem)
│ ├── 先攻管理 (Initiative)
│ ├── 法术位系统 (SpellSlots)
│ ├── 生命值系统 (HitPoints)
│ ├── BUFF系统 (BuffSystem)
│ └── 命令处理 (CommandProcessor)
├── Go接口封装
│ ├── 数据库操作 (Database)
│ ├── 骰子计算 (Calculator)
│ └── 消息发送 (Protocol)
└── 配置文件
├── 种族配置 (races.json)
├── 职业配置 (classes.json)
└── 技能配置 (skills.json)
```
### 2.2 数据流
```
用户命令 → 命令解析 → 业务逻辑处理 → 数据操作 → 结果返回
↓ ↓ ↓ ↓ ↓
.dnd roll 参数提取 骰子计算 数据存储 消息回复
```
## 3. 核心系统设计
### 3.1 角色系统
#### 3.1.1 角色数据结构
```javascript
class Character {
constructor() {
this.id = ''; // 角色ID
this.name = ''; // 角色名称
this.playerName = ''; // 玩家名称
this.race = ''; // 种族
this.class = ''; // 职业
this.level = 1; // 等级
this.abilities = new AbilityScores();
this.skills = new SkillSystem();
this.hitPoints = { max: 0, current: 0, temp: 0 };
this.armorClass = 10;
this.proficiencyBonus = 2;
this.initiative = 0; // 先攻值
this.spellSlots = {}; // 法术位
this.buffs = {}; // BUFF状态
this.deathSaves = { success: 0, failure: 0 }; // 死亡豁免
}
}
```
#### 3.1.2 属性系统
```javascript
class AbilityScores {
constructor() {
this.strength = 10; // 力量
this.dexterity = 10; // 敏捷
this.constitution = 10; // 体质
this.intelligence = 10; // 智力
this.wisdom = 10; // 感知
this.charisma = 10; // 魅力
}
getModifier(ability) {
return Math.floor((this[ability] - 10) / 2);
}
}
```
### 3.2 技能系统
#### 3.2.1 技能映射
| 技能 | 对应属性 | 中文名称 |
|------|----------|----------|
| athletics | strength | 运动 |
| acrobatics | dexterity | 杂技 |
| sleight_of_hand | dexterity | 巧手 |
| stealth | dexterity | 潜行 |
| arcana | intelligence | 奥秘 |
| history | intelligence | 历史 |
| investigation | intelligence | 调查 |
| nature | intelligence | 自然 |
| religion | intelligence | 宗教 |
| animal_handling | wisdom | 驯兽 |
| insight | wisdom | 洞察 |
| medicine | wisdom | 医药 |
| perception | wisdom | 察觉 |
| survival | wisdom | 生存 |
| deception | charisma | 欺骗 |
| intimidation | charisma | 威吓 |
| performance | charisma | 表演 |
| persuasion | charisma | 说服 |
#### 3.2.2 技能检定计算
```
技能检定 = 1d20 + 属性修正 + 熟练加值(如果熟练) + 专精加值(如果专精) + BUFF修正
```
### 3.3 骰子系统
#### 3.3.1 支持的骰子表达式
- 基础投掷:`1d20`, `2d6`, `4d6dl1`
- 优势投掷:`2d20kh1` (取较高值)
- 劣势投掷:`2d20kl1` (取较低值)
- 修正值:`1d20+5`, `2d6-1`
#### 3.3.2 优势/劣势机制
```javascript
rollWithAdvantage(advantage, disadvantage) {
if (advantage && disadvantage) {
return this.roll('1d20'); // 抵消,正常投掷
} else if (advantage) {
return this.roll('2d20kh1'); // 优势
} else if (disadvantage) {
return this.roll('2d20kl1'); // 劣势
} else {
return this.roll('1d20'); // 正常
}
}
```
### 3.4 先攻系统
```javascript
class InitiativeSystem {
setInitiative(characterName, value) {
// 支持多种格式:
// 固定值12
// 掷骰:+2 (d20+2)
// 表达式:=d20+3
// 复合:+1d8 (d20+1d8)
}
batchSetInitiative(entries) {
// 批量设置:"+3 角色1, +2 角色2"
}
}
```
### 3.5 法术位系统
```javascript
class SpellSlotSystem {
constructor() {
this.slots = {}; // {1: {current: 3, max: 3}, 2: {current: 2, max: 2}}
}
initSlots(levels) {
// .ss init 4 3 2 - 设置1/2/3环法术位
}
consumeSlot(level, count = 1) {
// .ss 3环 -1 - 消耗法术位
}
restoreSlots() {
// .ss rest - 恢复所有法术位
}
}
```
### 3.6 生命值和死亡豁免系统
```javascript
class HitPointSystem {
takeDamage(damage) {
// 处理伤害,检查是否昏迷
if (this.current <= 0) {
this.triggerDeathSaves();
}
}
deathSave(modifier = 0) {
// 死亡豁免检定
const roll = this.rollD20() + modifier;
if (roll >= 10) {
this.deathSaves.success++;
} else {
this.deathSaves.failure++;
}
// 检查结果
if (roll === 20) {
this.stabilize(); // 大成功
} else if (this.deathSaves.success >= 3) {
this.stabilize(); // 稳定
} else if (this.deathSaves.failure >= 3) {
this.die(); // 死亡
}
}
}
```
### 3.7 BUFF系统
```javascript
class BuffSystem {
addBuff(attribute, value, type = 'normal') {
// .buff 运动*:2 - 添加专精BUFF如吟游诗人的万事通特性
// .buff 运动:+2 - 添加属性BUFF (如祝福术)
}
removeBuff(attribute) {
// .buff del 力量 - 删除BUFF
}
clearBuffs() {
// .buff clr - 清除所有BUFF
}
calculateTotal(attribute) {
// 计算包含BUFF的最终值
}
}
```
## 4. 命令系统
### 4.1 完整命令列表
| 命令 | 功能 | 示例 |
|------|------|------|
| `.dnd` / `.dndx` | 生成随机属性 | `.dnd 5` / `.dndx 3` |
| `.st` | 属性设置/查看 | `.st 力量:16 敏捷:14` |
| `.ri` | 先攻设置 | `.ri +2` / `.ri +3 角色名` |
| `.ra` / `.rc` | 属性/技能检定 | `.ra 力量` / `.rc 运动` |
| `.rs` | 豁免检定 | `.rs 敏捷` |
| `.buff` | BUFF管理 | `.buff 运动*:2` |
| `.ss` | 法术位管理 | `.ss init 4 3 2` |
| `.cast` | 消耗法术位 | `.cast 2环` |
| `.ds` / `.死亡豁免` | 死亡豁免 | `.ds` / `.ds +1d4` |
| `.longrest` / `.长休` | 长休恢复 | `.longrest` |
| `.roll` / `.r` | 自由骰子 | `.r 1d20+5` |
| `.show` | 显示角色 | `.show` |
| `.switch` | 切换角色 | `.switch 角色名` |
| `.list` | 角色列表 | `.list` |
| `.help` | 帮助信息 | `.help` |
### 4.2 属性生成系统
```javascript
// .dnd [数量] - 生成自由分配属性
function generateAbilityScores(count = 1) {
const results = [];
for (let i = 0; i < count; i++) {
const scores = [];
for (let j = 0; j < 6; j++) {
// 4d6去最低
const rolls = [rollD6(), rollD6(), rollD6(), rollD6()];
rolls.sort((a, b) => b - a);
scores.push(rolls[0] + rolls[1] + rolls[2]);
}
results.push(scores);
}
return results;
}
// .dndx [数量] - 生成带属性名的预设
function generateAbilityScoresWithNames(count = 1) {
const abilities = ['力量', '敏捷', '体质', '智力', '感知', '魅力'];
const results = [];
for (let i = 0; i < count; i++) {
const character = {};
abilities.forEach(ability => {
const rolls = [rollD6(), rollD6(), rollD6(), rollD6()];
rolls.sort((a, b) => b - a);
character[ability] = rolls[0] + rolls[1] + rolls[2];
});
results.push(character);
}
return results;
}
```
### 4.3 先攻管理
```javascript
function handleInitiative(args) {
const value = args[1];
const characterName = args[2] || getCurrentCharacter();
if (value.includes(',')) {
// 批量设置:".ri +3 角色1, +2 角色2"
return batchSetInitiative(value);
}
let result;
if (value.startsWith('+') || value.startsWith('-')) {
// 掷骰:+2 表示 d20+2
const modifier = parseInt(value);
const roll = rollD20();
result = roll + modifier;
return `${characterName}的先攻: ${roll}[1d20] + ${modifier} = ${result}`;
} else if (value.startsWith('=')) {
// 表达式:=d20+3
const expression = value.substring(1);
result = rollExpression(expression);
return `${characterName}的先攻: ${result}`;
} else {
// 固定值12
result = parseInt(value);
return `${characterName}的先攻设为: ${result}`;
}
}
```
### 4.4 法术位管理
```javascript
function handleSpellSlots(args) {
const command = args[1];
switch (command) {
case 'init':
// .ss init 4 3 2
const slots = args.slice(2).map(Number);
return initializeSpellSlots(slots);
case 'set':
// .ss set 2环 4
return setSpellSlot(args[2], parseInt(args[3]));
case 'rest':
// .ss rest
return restoreAllSpellSlots();
case 'clr':
// .ss clr
return clearSpellSlots();
default:
if (command.includes('环')) {
// .ss 3环 -1
const level = parseInt(command);
const change = parseInt(args[2]);
return modifySpellSlot(level, change);
}
// .ss - 查看当前状况
return showSpellSlots();
}
}
```
### 4.5 死亡豁免系统
```javascript
function handleDeathSave(args) {
const character = getCurrentCharacter();
if (character.hitPoints.current > 0) {
return "角色未处于昏迷状态,无需死亡豁免";
}
const command = args[1];
if (command === 'stat') {
return showDeathSaveStatus(character);
}
if (command && (command.includes('成功') || command.includes('失败') || command.includes('s') || command.includes('f'))) {
// .ds 成功+1 或 .ds s+1
return modifyDeathSave(character, command);
}
// 进行死亡豁免检定
let modifier = 0;
if (args[1]) {
// .ds +1d4
modifier = parseModifier(args[1]);
}
const roll = rollD20();
const total = roll + modifier;
let result = `${character.name}的死亡豁免检定: [1d20=${roll}]`;
if (modifier !== 0) {
result += ` + ${modifier}`;
}
result += ` = ${total} `;
if (roll === 20) {
character.hitPoints.current = 1;
character.deathSaves = { success: 0, failure: 0 };
result += "医学奇迹HP回复1点";
} else if (roll === 1) {
character.deathSaves.failure += 2;
result += "大失败!死亡豁免失败+2";
} else if (total >= 10) {
character.deathSaves.success++;
result += "成功!死亡豁免成功+1";
} else {
character.deathSaves.failure++;
result += "失败!死亡豁免失败+1";
}
// 检查结果
if (character.deathSaves.success >= 3) {
result += " 角色稳定下来了!";
character.deathSaves = { success: 0, failure: 0 };
} else if (character.deathSaves.failure >= 3) {
result += " 角色不幸去世了!";
}
return result;
}
```
## 5. 数据存储设计
### 5.1 数据键设计
```javascript
// 用户数据键
const USER_KEYS = {
ACTIVE_CHARACTER: 'dnd5e_active_character', // 当前激活角色
CHARACTER_LIST: 'dnd5e_character_list' // 角色列表
};
// 全局数据键
const GLOBAL_KEYS = {
CHARACTER_DATA: (id) => `character_${id}`, // 角色数据
INITIATIVE_ORDER: 'dnd5e_initiative_order', // 先攻顺序
PLAYER_INDEX: 'dnd5e_player_index' // 玩家索引
};
```
### 5.2 数据存储策略
- **用户数据**:存储用户的激活角色和角色列表
- **全局数据**:存储具体的角色数据,支持角色共享
- **会话数据**:存储战斗相关的临时数据(先攻顺序等)
- **数据格式**JSON字符串便于序列化和反序列化
## 6. 技术实现
### 6.1 ProjectWIND接口使用
#### 6.1.1 数据库接口
```javascript
// 用户数据操作
await wsd.SetUserVariable(appInfo, msgInfo, key, value);
const [data, exists] = await wsd.GetUserVariable(appInfo, msgInfo, key);
// 全局数据操作
await wsd.SetOutGlobalVariable(appInfo, "dnd5e", msgInfo, key, value);
const [data, exists] = await wsd.GetOutGlobalVariable(appInfo, "dnd5e", msgInfo, key);
```
#### 6.1.2 骰子计算接口
```javascript
// 骰子投掷
const result = await wsc.Roll(expression);
// 返回: { expression, normalizedExpression, value }
// 随机数生成
const random = wsc.RandI(min, max);
```
#### 6.1.3 消息发送接口
```javascript
// 发送消息
wsp.SendMsg(msgInfo, message, false);
```
#### 6.1.4 日志接口
```javascript
// 记录日志
wst.LogWith('info', '角色创建成功: %s', characterName);
wst.LogWith('error', '错误信息: %s', error.message);
```
### 6.2 错误处理
```javascript
// 统一错误处理
function handleError(error, context) {
wst.LogWith('error', `${context}错误: ${error.message}`);
return `操作失败: ${error.message}`;
}
// 参数验证
function validateCommand(command, args, minArgs) {
if (args.length < minArgs) {
throw new Error(`${command}命令需要至少${minArgs}个参数`);
}
}
```
## 7. 配置文件
### 7.1 种族配置 (races.json)
```json
{
"human": {
"name": "人类",
"abilityBonus": { "all": 1 },
"traits": ["额外技能", "额外专长"]
},
"elf": {
"name": "精灵",
"abilityBonus": { "dexterity": 2 },
"traits": ["敏锐感官", "精灵血统"]
}
}
```
### 7.2 职业配置 (classes.json)
```json
{
"fighter": {
"name": "战士",
"hitDie": 10,
"primaryAbility": ["strength", "dexterity"],
"savingThrows": ["strength", "constitution"],
"skillChoices": 2,
"availableSkills": ["acrobatics", "animal_handling", "athletics"]
}
}
```
## 8. 应用注册
### 8.1 应用信息
```javascript
const dnd5eApp = wind.newApp("dnd5e", "1.0.0", "ProjectWIND")
.withDescription("DND5e规则包 - 龙与地下城第五版规则实现")
.withWebUrl("")
.withLicense("");
// 注册命令
dnd5eApp.addCmd("dnd", {
name: "dnd",
desc: "DND5e属性生成",
solve: handleDNDCommand
});
dnd5eApp.addCmd("st", {
name: "st",
desc: "属性设置和查看",
solve: handleSTCommand
});
dnd5eApp.addCmd("ri", {
name: "ri",
desc: "先攻设置",
solve: handleInitiativeCommand
});
// 注册应用
wind.registerApp(dnd5eApp);
```
## 9. 部署和使用
### 9.1 文件结构
```
ProjectWIND/dnd5e/
├── dnd5e_core.js # 核心JavaScript实现
├── go_interfaces.go # Go接口封装(可选)
└── config/
├── races.json # 种族配置
├── classes.json # 职业配置
└── skills.json # 技能配置
```
### 9.2 安装
- 将`dnd5e_core.js`放入ProjectWIND应用目录配置相关JSON文件重启ProjectWIND核心使用`.dnd help`查看可用命令
## 10. 可能继续的扩展
### 10.1 扩展
- 装备管理系统
- 战斗轮次管理
- 状态效果跟踪
### 10.2 长期扩展
- 怪物手册集成
- 玩家规则集成
- 装备信息数据库
- 法术数据库

24
test/test_dice.go Normal file
View File

@ -0,0 +1,24 @@
package main
import (
"ProjectWIND/dice_expr"
"fmt"
)
func main() {
tests := []string{
"2d6+3",
"d20",
"3d4-1",
"1d100",
}
for _, test := range tests {
_, value, err := dice_expr.Evaluate(test)
if err != nil {
fmt.Printf("%s: 错误 - %v\n", test, err)
} else {
fmt.Printf("%s = %d\n", test, value)
}
}
}

BIN
webui/account.css.hertz.gz Normal file

Binary file not shown.

BIN
webui/account.html.hertz.gz Normal file

Binary file not shown.

BIN
webui/dice.css.hertz.gz Normal file

Binary file not shown.

BIN
webui/dice.html.hertz.gz Normal file

Binary file not shown.

BIN
webui/dice.js.hertz.gz Normal file

Binary file not shown.

Binary file not shown.

BIN
webui/styles.css.hertz.gz Normal file

Binary file not shown.

BIN
webui/tools.css.hertz.gz Normal file

Binary file not shown.

View File

@ -95,7 +95,7 @@
<div class="command-input-area">
<label for="command-input">输入指令:</label>
<input type="text" id="command-input" placeholder="例如:.help, .st, .r 2d6+3">
<input type="text" id="command-input" placeholder="例如:.help, .r2d6, .r d20+5">
<button id="send-command">发送</button>
</div>
@ -111,7 +111,6 @@
<div class="preset-buttons">
<button class="preset-btn" data-command=".help">帮助</button>
<button class="preset-btn" data-command=".st">属性设置</button>
<button class="preset-btn" data-command=".r d20">投掷D20</button>
<button class="preset-btn" data-command=".sc">理智检定</button>
<button class="preset-btn" data-command=".coc">COC人物卡</button>
<button class="preset-btn" data-command=".dnd">DND人物卡</button>

BIN
webui/tools.html.hertz.gz Normal file

Binary file not shown.

View File

@ -1,3 +1,54 @@
// 全局骰子投掷函数
function simulateDiceRoll(expression) {
try {
const dicePattern = /(\d*)d(\d+)([+\-]\d+)?/gi;
let result = 0;
let match;
let breakdown = [];
let calculation = [];
if (dicePattern.test(expression)) {
dicePattern.lastIndex = 0;
while ((match = dicePattern.exec(expression)) !== null) {
const count = parseInt(match[1]) || 1;
const sides = parseInt(match[2]);
const modifier = parseInt(match[3]) || 0;
let rolls = [];
let rollSum = 0;
for (let i = 0; i < count; i++) {
const roll = Math.floor(Math.random() * sides) + 1;
rolls.push(roll);
rollSum += roll;
}
breakdown.push(`${count}d${sides}: [${rolls.join(', ')}] = ${rollSum}`);
if (modifier !== 0) {
calculation.push(`${rollSum} ${modifier >= 0 ? '+' : ''}${modifier}`);
rollSum += modifier;
} else {
calculation.push(`${rollSum}`);
}
result += rollSum;
}
return {
success: true,
total: result,
breakdown: breakdown.join('; '),
calculation: calculation.join(' + ') + ` = ${result}`
};
} else {
return { success: false, error: '无效的骰子表达式' };
}
} catch (error) {
return { success: false, error: error.message };
}
}
// 辅助工具页面的 JavaScript 功能
document.addEventListener('DOMContentLoaded', function() {
// 选项卡切换功能
@ -80,13 +131,12 @@ function initDiceTools() {
showResult('🎲 投掷中...', 'loading');
rollButton.disabled = true;
const mockResult = simulateDiceRoll(expression);
const mockResult = performDiceRoll(expression);
if (mockResult.success) {
showResult(`${expression} = ${mockResult.value}`, 'success');
showResultDetails(mockResult.breakdown, mockResult.calculation);
addToHistory(expression, mockResult.value);
updateStats();
} else {
showResult(`错误: ${mockResult.error}`, 'error');
hideResultDetails();
@ -170,59 +220,29 @@ function initDiceTools() {
}
// 模拟骰子投掷
function simulateDiceRoll(expression) {
try {
const dicePattern = /(\d*)d(\d+)([+\-]\d+)?/gi;
let result = 0;
let match;
let breakdown = [];
let calculation = [];
if (dicePattern.test(expression)) {
dicePattern.lastIndex = 0;
while ((match = dicePattern.exec(expression)) !== null) {
const count = parseInt(match[1]) || 1;
const sides = parseInt(match[2]);
const modifier = parseInt(match[3]) || 0;
let rolls = [];
let rollSum = 0;
for (let i = 0; i < count; i++) {
const roll = Math.floor(Math.random() * sides) + 1;
rolls.push(roll);
rollSum += roll;
}
breakdown.push(`${count}d${sides}: [${rolls.join(', ')}] = ${rollSum}`);
if (modifier !== 0) {
calculation.push(`${rollSum} ${modifier >= 0 ? '+' : ''}${modifier}`);
rollSum += modifier;
} else {
calculation.push(`${rollSum}`);
}
result += rollSum;
}
function performDiceRoll(expression) {
const result = simulateDiceRoll(expression);
if (result.success) {
// 更新统计
todayRolls++;
totalRolls++;
localStorage.setItem('totalRolls', totalRolls.toString());
updateStats();
return {
success: true,
value: result,
breakdown: breakdown.join('; '),
calculation: calculation.join(' + ') + ` = ${result}`
value: result.total,
breakdown: result.breakdown,
calculation: result.calculation
};
} else {
return { success: false, error: '无效的骰子表达式' };
}
} catch (error) {
return { success: false, error: error.message };
return result;
}
}
// 暴露函数到全局作用域,供指令测试使用
window.updateDiceStats = updateStats;
window.addToDiceHistory = addToHistory;
}
// 指令测试工具初始化
@ -271,7 +291,23 @@ function initCommandTools() {
const result = simulateCommand(command);
showCommandResult(result.message, result.success ? 'success' : 'error');
// 添加到历史
// 如果是骰子指令且执行成功,同时更新骰子统计
if (command.startsWith('.r') && result.success) {
const diceExpression = command.substring(2).trim();
// 触发骰子统计更新
if (window.updateDiceStats) {
window.updateDiceStats();
}
// 添加到骰子历史
if (window.addToDiceHistory) {
const diceResult = result.message.match(/投掷 .+?: (\d+)/);
if (diceResult) {
window.addToDiceHistory(diceExpression, diceResult[1]);
}
}
}
// 添加到指令历史
addToCommandHistory(command, result.message);
} catch (error) {
@ -332,10 +368,35 @@ function initCommandTools() {
// 模拟指令执行
function simulateCommand(command) {
// 处理骰子指令
if (command.startsWith('.r')) {
let diceExpression = command.substring(2).trim(); // 移除 ".r" 前缀
// 如果没有空格,可能是 .r2d6 这种格式,需要特殊处理
if (!diceExpression && command.length > 2) {
diceExpression = command.substring(2); // 不trim保持原始格式
}
if (!diceExpression) {
return { success: false, message: '请指定骰子表达式,如: .r d20, .r2d6 或 .r 2d6+3' };
}
try {
// 调用骰子投掷逻辑
const result = simulateDiceRoll(diceExpression);
return {
success: true,
message: `投掷 ${diceExpression}: ${result.total}${result.breakdown ? ` (${result.breakdown})` : ''}`
};
} catch (error) {
return { success: false, message: `骰子表达式错误: ${error.message}` };
}
}
// 其他固定指令
const responses = {
'.help': { success: true, message: '可用指令:.help, .st, .r, .sc, .coc, .dnd' },
'.help': { success: true, message: '可用指令:.help, .st, .r [骰子表达式], .sc, .coc, .dnd' },
'.st': { success: true, message: '属性设置功能暂未实现' },
'.r d20': { success: true, message: `投掷结果:${Math.floor(Math.random() * 20) + 1}` },
'.sc': { success: true, message: '理智检定功能暂未实现' },
'.coc': { success: true, message: 'COC人物卡生成功能暂未实现' },
'.dnd': { success: true, message: 'DND人物卡生成功能暂未实现' }

BIN
webui/tools.js.hertz.gz Normal file

Binary file not shown.

BIN
wind-dice.exe Normal file

Binary file not shown.