完成dnd5e属性系统、技能系统、检定机制、简化的角色管理、法术系统、简单的增益系统以及一些游戏辅助功能
This commit is contained in:
parent
9d3cbf1f95
commit
7e9c4932fb
@ -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)
|
||||
|
@ -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
156
dnd5e/config/classes.json
Normal 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
143
dnd5e/config/races.json
Normal 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
92
dnd5e/config/skills.json
Normal 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": "用于善意地影响他人或达成友好协议"
|
||||
}
|
||||
}
|
597
dnd5e/example_implementation.go
Normal file
597
dnd5e/example_implementation.go
Normal 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
306
dnd5e/go_interfaces.go
Normal 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
633
docs/dnd5e_design.md
Normal 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
24
test/test_dice.go
Normal 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
BIN
webui/account.css.hertz.gz
Normal file
Binary file not shown.
BIN
webui/account.html.hertz.gz
Normal file
BIN
webui/account.html.hertz.gz
Normal file
Binary file not shown.
BIN
webui/dice.css.hertz.gz
Normal file
BIN
webui/dice.css.hertz.gz
Normal file
Binary file not shown.
BIN
webui/dice.html.hertz.gz
Normal file
BIN
webui/dice.html.hertz.gz
Normal file
Binary file not shown.
BIN
webui/dice.js.hertz.gz
Normal file
BIN
webui/dice.js.hertz.gz
Normal file
Binary file not shown.
Binary file not shown.
BIN
webui/styles.css.hertz.gz
Normal file
BIN
webui/styles.css.hertz.gz
Normal file
Binary file not shown.
BIN
webui/tools.css.hertz.gz
Normal file
BIN
webui/tools.css.hertz.gz
Normal file
Binary file not shown.
@ -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
BIN
webui/tools.html.hertz.gz
Normal file
Binary file not shown.
157
webui/tools.js
157
webui/tools.js
@ -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
BIN
webui/tools.js.hertz.gz
Normal file
Binary file not shown.
BIN
wind-dice.exe
Normal file
BIN
wind-dice.exe
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user