载入中...
搜索中...
未找到
BattleSystem.cpp
浏览该文件的文档.
1#include "rpg/BattleSystem.h"
2
4#include "rpg/RPGActor.h"
5#include "rpg/TraitSystem.h"
6
7#include <cctype>
8#include <cmath>
9#include <cstdlib>
10#include <optional>
11#include <random>
12#include <unordered_map>
13
14namespace eve::rpg {
15
16namespace {
17
18std::unordered_map<std::string, SkillDamageSpec> &skillDamageTable() {
19 static std::unordered_map<std::string, SkillDamageSpec> t;
20 return t;
21}
22
23eve::Result<double> parseFormula(const std::string &formula, RPGActor *attacker, RPGActor *target,
24 bool validateOnly);
25
26} // namespace
27
29 const SkillDamageSpec &spec) {
30 if (skillId.empty())
32 eve::DiagnosticCode::InvalidArgument, "skill damage id must not be empty", "skillId"));
33 if (!spec.formula.empty()) {
34 auto formula = parseFormula(spec.formula, nullptr, nullptr, true);
35 if (!formula) return eve::Result<void>::failure(formula.status());
36 }
37 skillDamageTable()[skillId] = spec;
39}
40
41void BattleSystem::registerSkillDamage(const std::string &skillId, const SkillDamageSpec &spec) {
42 registerSkillDamageChecked(skillId, spec).ignore("legacy RPG damage registration facade");
43}
44
45const SkillDamageSpec *BattleSystem::findSkillDamage(const std::string &skillId) {
46 auto &t = skillDamageTable();
47 auto it = t.find(skillId);
48 return it == t.end() ? nullptr : &it->second;
49}
50
51void BattleSystem::clearSkillDamage() { skillDamageTable().clear(); }
52
53namespace {
54
55// ---- 极简表达式求值器:支持数字、a.<param>、b.<param>、+ - * / ( ) ----
56struct FormulaParser {
57 const std::string &s;
58 size_t pos = 0;
59 RPGActor *a;
60 RPGActor *b;
62 std::optional<eve::Diagnostic> error;
63
64 FormulaParser(const std::string &str, RPGActor *atk, RPGActor *tgt, bool syntaxOnly)
65 : s(str), a(atk), b(tgt), validateOnly(syntaxOnly) {}
66
67 void skipSpace() {
68 while (pos < s.size() && std::isspace(static_cast<unsigned char>(s[pos]))) ++pos;
69 }
70
71 double parseExpression() {
72 double lhs = parseTerm();
73 while (true) {
74 skipSpace();
75 if (pos >= s.size()) break;
76 const char c = s[pos];
77 if (c == '+') {
78 ++pos;
79 lhs += parseTerm();
80 } else if (c == '-') {
81 ++pos;
82 lhs -= parseTerm();
83 } else {
84 break;
85 }
86 }
87 return lhs;
88 }
89
90 double parseTerm() {
91 double lhs = parseFactor();
92 while (true) {
93 skipSpace();
94 if (pos >= s.size()) break;
95 const char c = s[pos];
96 if (c == '*') {
97 ++pos;
98 lhs *= parseFactor();
99 } else if (c == '/') {
100 ++pos;
101 const double rhs = parseFactor();
102 if (validateOnly) {
103 lhs = 1.0;
104 } else if (rhs == 0.0) {
105 if (!error)
107 "damage formula cannot divide by zero",
108 "formula[" + std::to_string(pos) + "]");
109 lhs = 0.0;
110 } else {
111 lhs /= rhs;
112 }
113 } else {
114 break;
115 }
116 }
117 return lhs;
118 }
119
120 double parseFactor() {
121 skipSpace();
122 if (pos < s.size() && (s[pos] == '+' || s[pos] == '-')) {
123 const bool negate = s[pos++] == '-';
124 const double value = parseFactor();
125 return negate ? -value : value;
126 }
127 if (pos < s.size() && s[pos] == '(') {
128 ++pos;
129 double v = parseExpression();
130 skipSpace();
131 if (pos >= s.size() || s[pos] != ')') {
132 fail("damage formula is missing a closing parenthesis");
133 return 0.0;
134 }
135 ++pos;
136 return v;
137 }
138 if (pos < s.size() && (s[pos] == 'a' || s[pos] == 'b') && pos + 1 < s.size() &&
139 s[pos + 1] == '.') {
140 const bool isA = s[pos] == 'a';
141 pos += 2;
142 std::string name;
143 while (pos < s.size() && (std::isalnum(static_cast<unsigned char>(s[pos])) ||
144 s[pos] == '_')) {
145 name.push_back(s[pos++]);
146 }
147 if (name.empty()) {
148 fail("damage formula attribute name must not be empty");
149 return 0.0;
150 }
151 RPGActor *actor = isA ? a : b;
152 return actor ? AttributeSystem::getFinal(actor, name) : 0.0;
153 }
154 if (pos < s.size() && (std::isdigit(static_cast<unsigned char>(s[pos])) || s[pos] == '.')) {
155 std::string num;
156 while (pos < s.size() && (std::isdigit(static_cast<unsigned char>(s[pos])) ||
157 s[pos] == '.')) {
158 num.push_back(s[pos++]);
159 }
160 char *end = nullptr;
161 const double value = std::strtod(num.c_str(), &end);
162 if (end == num.c_str() || *end != '\0' || !std::isfinite(value)) {
163 fail("damage formula contains an invalid number");
164 return 0.0;
165 }
166 return value;
167 }
168 fail("damage formula expected a number, attribute, unary sign, or parenthesized expression");
169 return 0.0;
170 }
171
172 void fail(std::string message) {
173 if (error) return;
175 "formula[" + std::to_string(pos) + "]");
176 }
177};
178
179eve::Result<double> parseFormula(const std::string &formula, RPGActor *attacker, RPGActor *target,
180 bool validateOnly) {
181 if (formula.empty())
183 eve::DiagnosticCode::ParseError, "damage formula must not be empty", "formula"));
184 FormulaParser parser(formula, attacker, target, validateOnly);
185 const double value = parser.parseExpression();
186 parser.skipSpace();
187 if (parser.error) return eve::Result<double>::failure(std::move(*parser.error));
188 if (parser.pos != formula.size())
190 eve::DiagnosticCode::ParseError, "damage formula contains trailing input",
191 "formula[" + std::to_string(parser.pos) + "]"));
192 if (!std::isfinite(value))
194 eve::DiagnosticCode::InvalidArgument, "damage formula result must be finite", "formula"));
196}
197
198} // namespace
199
200double BattleSystem::evaluateFormula(const std::string &formula, RPGActor *attacker,
201 RPGActor *target) {
202 return std::move(evaluateFormulaChecked(formula, attacker, target)).valueOr(0.0);
203}
204
206 RPGActor *target) {
207 return parseFormula(formula, attacker, target, false);
208}
209
211 const SkillDamageSpec &spec, unsigned seed) {
212 DamageResult result;
213 if (!attacker || !target) return result;
214 std::mt19937 rng(seed);
215 std::uniform_real_distribution<double> dist(0.0, 1.0);
216
217 // 命中判定:攻击方 hit + (100 - 目标 evasion)
218 double attackerHit = 100.0 + TraitSystem::getExParam(attacker, "hit");
219 double targetEvasion = TraitSystem::getExParam(target, "evasion");
220 double hitChance = spec.hitChance + attackerHit - targetEvasion;
221 if (dist(rng) * 100.0 >= hitChance) {
222 result.hit = false;
223 return result;
224 }
225
226 // 公式(默认物理:a.atk - b.def,至少 1)
227 double amount;
228 if (spec.formula.empty()) {
229 amount = AttributeSystem::getFinal(attacker, "attack") -
231 } else {
232 amount = evaluateFormula(spec.formula, attacker, target);
233 }
234 if (amount <= 0.0) amount = 1.0;
235
236 // 元素耐性
237 if (!spec.element.empty()) {
239 amount *= result.elementRate;
240 }
241
242 // 暴击(RPG Maker 默认 3x)
243 double critChance = spec.critChance + TraitSystem::getExParam(attacker, "critRate");
244 if (critChance > 0.0 && dist(rng) < critChance) {
245 result.crit = true;
246 amount *= 3.0;
247 }
248
249 result.amount = amount;
250 return result;
251}
252
253} // namespace eve::rpg
LogicalId target
double value
RPG's thin compatibility facade over the attributes module.
const std::string & s
bool validateOnly
伤害公式 DSL 与技能伤害注册。
std::string message
float v
std::int32_t c
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::string error
Definition Package.cpp:60
std::uint32_t seed
Definition PointSet.cpp:807
float t
特征系统:施加/移除运行时特征,并提供耐性/特殊参数/攻击附加等查询。
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
void ignore(std::string_view reason={}) const noexcept
Explicitly discard this result after documenting the reason.
Definition Result.h:537
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
static double getFinal(RPGActor *actor, const std::string &attribute)
Compute the final value through the canonical attributes engine.
static DamageResult resolveHit(RPGActor *attacker, RPGActor *target, const SkillDamageSpec &spec, unsigned seed)
结算一次命中:命中判定、公式、元素耐性、暴击。
static void clearSkillDamage()
Clears skill damage.
static eve::Result< double > evaluateFormulaChecked(const std::string &formula, RPGActor *attacker, RPGActor *target)
Strictly parse and evaluate a damage formula.
static double evaluateFormula(const std::string &formula, RPGActor *attacker, RPGActor *target)
求值伤害公式。
static void registerSkillDamage(const std::string &skillId, const SkillDamageSpec &spec)
Compatibility facade; invalid definitions are ignored.
static eve::Result< void > registerSkillDamageChecked(const std::string &skillId, const SkillDamageSpec &spec)
Validate and publish one skill damage definition atomically.
static const SkillDamageSpec * findSkillDamage(const std::string &skillId)
Finds skill damage.
属性 / 状态 / 技能三表合一的 ECS 实体。
Definition RPGActor.h:34
static double getElementRate(RPGActor *actor, const std::string &element)
元素耐性乘积(1 = 无修正,0.5 = 减半)。
static double getExParam(RPGActor *actor, const std::string &exParam)
特殊参数加值(hit/evasion/critRate/critEvade/magicEvade/magicReflect/...)。
RPG 模块的基础实体:把属性 / 状态 / 技能三张表挂到一个 ECS 实体上。
一次命中结算结果。
一次技能命中的伤害规格。
double critChance
追加到攻击方 critRate 的暴击率
int hitChance
命中率(百分比)
std::string formula
公式字符串,如 "a.atk * 4 - b.def * 2";空则对 hp 用默认物理公式。
std::string element
元素("" = 无);伤害乘目标对应元素耐性。