☰
VCMI Lua 脚本 API 指南:ENUM 全局枚举表完全解析
2026/10/12 7:18:34 网站建设 项目流程
  • 游戏开发

【免费下载链接】vcmi

Open-source engine for Heroes of Might and Magic III

项目地址:https://gitcode.com/gh_mirrors/vc/vcmi
点击查看免费下载

导读

在 VCMI 的 Lua 脚本 API 中,全局表ENUM是 mod 开发者与引擎底层枚举之间的"翻译层":它以字符串键暴露了一批整数常量,避免在脚本里书写难以维护的魔法数字。本文以官方参考文档 Enums.md 为核心,完整整理ENUM下全部 15 个枚举分组的键值对照表,并结合引擎源码(luascript/api/Enums.cpp)与仓库内真实脚本(scripts)说明每一组枚举的语义、适用场景与实战写法。读完本文,你将能熟练使用ENUM.HealLevel、ENUM.BonusDuration、ENUM.WallPart等常量编写战斗脚本、护盾/治疗效果与攻城逻辑。


一、ENUM 是什么:从魔法数字到具名常量

VCMI 允许 mod 通过 Lua 脚本(位于 scripts 目录,含 combat、spells、damage 三个子目录)扩展战斗、魔法与地图行为。引擎内部的枚举(如治疗等级、施法错误码、奖励来源)在 C++ 中是以整数表达的,如果 Lua 侧直接书写0、2、7这类数字,代码将难以阅读和维护。

ENUM正是为解决这一问题而注册的全局表。其官方定位为:

全局ENUM表 —— 脚本用于避免魔法数字的 string→integer 常量。表中的每个字段都是一个具名分组;运行时其值是一个 Lua 表,键为常量名。

也就是说,ENUM的结构是两层嵌套表:

ENUM.<分组名>.<常量名> -- 例如 ENUM.HealLevel.resurrect == 1

该全局表的注册位置在 luascript/LuaContext.cpp:

api::Enums enums; S.push(env->game()); lua_setglobal(L, "GAME"); S.push(env->services()); lua_setglobal(L, "LIBRARY"); S.push(enums); lua_setglobal(L, "ENUM");

引擎在初始化 Lua 上下文时依次注册了GAME、LIBRARY、ENUM三个全局对象,其中ENUM由api::Enums序列化而来。

从源码看分组如何导出

Enums类的声明位于 luascript/api/Enums.h,它通过serializeScript逐个注册分组,例如:

s("HealLevel", exportHealLevel(), "Outcome categories for the heal action: plain heal / resurrect / overheal."); s("BonusDuration", exportBonusDuration(), "Lifetime selectors used by Bonus / BonusDescriptor `duration`."); s("WallPart", exportWallPart(), "Town-wall sections referenced by siege APIs and `catapultAttack`.");

每个分组的实际内容定义在 luascript/api/Enums.cpp 中,其结构是EnumItem(键名 + C++ 枚举值 + 悬停描述)的列表,最终由模板EnumGroup承载。脚本读取ENUM.<Group>.<key>时得到的就是对应的 C++ 枚举值本身。

这套结构同时服务于 LuaLS(Lua Language Server)的类型提示:文档生成器 luascript/api/DocExport.cpp 会为每个枚举分组生成---@enum注解,使编辑器能自动补全ENUM.HealLevel.heal并在悬停时显示描述。


二、治疗与复活:HealLevel 与 HealPower

HealLevel —— 治疗动作的结果类别

治疗动作的三种结果类别:普通治疗 / 复活 / 过量治疗。对应的 C++ 定义是EHealLevel(lib/constants/Enumerations.h)。

KeyValue说明
heal0标准治疗——上限为当前部队数量,不复活阵亡单位
resurrect1复活部队中阵亡的单位,最高恢复至开战时数量
overheal2允许部队数量超过开战时数量(过量治疗)

导出实现见 luascript/api/Enums.cpp。

HealPower —— 治疗效果的存续性

治疗(尤其复活)效果在战斗结束后是否保留:仅本场战斗 vs 永久保留。C++ 定义是EHealPower(lib/constants/Enumerations.h)。

KeyValue说明
oneBattle0被复活的部队在战斗结束后消失
permanent1被复活的部队保留到战斗后的部队状态

导出实现见 luascript/api/Enums.cpp。

实战:lifeDrain 与重生脚本中的组合使用

HealLevel与HealPower通常成对传给server:healUnit。仓库内的 scripts/combat/lifeDrain.lua 演示了"复活 + 永久保留"的写法:

local healed, resurrected = server:healUnit(battle, unit, toHeal, ENUM.HealLevel.resurrect, ENUM.HealPower.permanent) if healed <= 0 then return end

而 scripts/combat/rebirth.lua 在重生效果中也使用了同样的组合。若希望复活仅作用于本场战斗(例如某些一次性法术),则应改用ENUM.HealPower.oneBattle。


三、施法校验:SpellCastProblem 错误码

SpellCastProblem是施法合法性校验返回的错误码集合,脚本可用它判断施法失败原因并给出相应反馈。C++ 定义是ESpellCastProblem(lib/constants/Enumerations.h),注意该枚举还有一个OK = 0的隐式成功值(源码中列为OK,位于 NO_HERO_TO_CAST_SPELL 之前),因此所有错误码从 1 开始。

KeyValue说明
noHeroToCastSpell1没有可用英雄来施放该法术
castsPerTurnLimit2施法者已用完本回合的施法次数
noSpellbook3施法者没有携带魔法书
heroDoesntKnowSpell4施法者的魔法书中没有该法术
notEnoughMana5施法者没有足够法力施放该法术
advmapSpellInsteadOfBattleSpell6在战斗上下文中尝试施放了冒险地图法术
spellLevelLimitExceeded7施法者的智慧术等级不足以施放该等级法术
noSpellsToDispel8施放了驱散但没有可移除的法术
noAppropriateTarget9未为法术选择合法目标
stackImmuneToSpell10所选目标单位对该法术免疫
wrongSpellTarget11所选目标不匹配该法术的 AimType
ongoinTacticPhase12战术部署阶段不允许施法
magicIsBlocked13战场上魔法被抑制(如禁魔地形或类似魔法抑制之球的效果)

导出实现见 luascript/api/Enums.cpp。注意源码中ONGOING_TACTIC_PHASE的拼写,与 Lua 侧键名ongoinTacticPhase(少一个 g)保持了一致,使用时请按表中键名书写。

实战:catapult 脚本中的校验错误码

scripts/spells/catapult.lua 在攻城法术的通用校验中使用ENUM.SpellCastProblem.noAppropriateTarget报告"无合法目标":

function Script:applicableGeneral(mechanics, problem) local battle = mechanics:getBattle() if not battle:hasFortifications() or (mechanics:isSmart() and mechanics:getCasterSide() ~= ENUM.BattleSide.attacker) or #potentialTargets(battle) == 0 then problem:addStandard(mechanics, ENUM.SpellCastProblem.noAppropriateTarget) return false end ... end

四、法术目标模式:AimType

AimType描述一个法术要求的目标类型:无目标、生物、障碍物、位置。C++ 定义位于 include/vcmi/spells/Magic.h:

enum class AimType { NOTHING, CREATURE, OBSTACLE, LOCATION };
KeyValue说明
nothing0法术不需要目标——选择后直接施放的范围法术
creature1法术以战场上的一个或多个单位为对象
obstacle2法术以已存在的障碍物为对象
location3法术以一个或多个战场格子为对象(无需单位)

导出实现见 luascript/api/Enums.cpp。在引擎内部,lib/battle/CBattleInfoCallback.cpp 依据AimType过滤候选目标:CREATURE收集单位、OBSTACLE收集障碍、LOCATION收集格子;当目标类型列表以NOTHING开头时则直接跳过目标筛选。


五、奖励系统四大枚举:BonusDuration / BonusSource / BonusValueType / BonusLimitEffect

VCMI 的奖励(Bonus)系统是战斗与成长数值的核心,ENUM中的四个分组正好对应 Bonus 与 BonusDescriptor 的四个关键字段。它们的 C++ 定义集中在 lib/bonuses/BonusEnum.h。

BonusDuration —— 奖励的存续期(duration 字段)

定义见 lib/bonuses/BonusEnum.h。与其它枚举不同,它使用uint16_t位掩码(每个值是 2 的幂),因此同一奖励可同时组合多个存续条件(例如同时"一回合"与"直到被攻击")。

KeyValue说明
permanent1永久生效(直到被显式移除)
oneBattle2当前战斗结束时过期
oneDay4当前游戏日结束时过期
oneWeek8当前游戏周结束时过期(注意:持续到本周结束,不是整整 7 天)
nTurns16N 个战斗回合后过期(战斗期间使用,战斗结束后奖励总是被移除)
nDays32N 个游戏日后过期
untilBeingAttacked64持有者第一次受到攻击时过期(每次单位受伤都会触发)
untilAttack128持有者发起任意攻击(含反击)时过期
stackGetsTurn256持有者所属部队行动时过期(用于防御姿态)
commanderKilled512与指挥官绑定——指挥官死亡时过期
untilOwnAttack1024持有者主动发起攻击(非反击)时过期
untilTakingIndirectDamage2048持有者受到间接伤害时过期(法术或环境伤害,不含近战/远程生物攻击、箭塔或弩车伤害)
untilAfterAttackSequence4096当前攻击与反击序列(含生物攻击、箭塔与战争机器)结算后过期

导出实现见 luascript/api/Enums.cpp。

BonusSource —— 奖励的来源类别(sourceType 字段)

定义见 lib/bonuses/BonusEnum.h 的BONUS_SOURCE_LIST宏与 lib/bonuses/BonusEnum.h 的枚举声明。它用于标记奖励来自哪一类系统,某些来源间存在叠加/去重规则。

KeyValue说明
artifact0由宝物定义授予;英雄携带多个相同宝物时不叠加
artifactInstance1由具体装备的宝物实例授予;多件相同宝物可叠加
objectType2由地图对象类型授予(如马厩)
objectInstance3由地图对象的特定实例授予;每个相同对象分别叠加
creatureAbility4生物类型固有(如龙息、亡灵)
terrainNative5主场地形奖励(生物位于其阵营的家乡地形)
terrainOverlay6战场地形覆盖奖励(魔法平原、火径等)
spellEffect7持有者身上的法术效果
townStructure8已建造的城镇建筑
heroBaseSkill9英雄主技能(攻击/防御/力量/知识)
secondarySkill10英雄辅助技能等级(战术、进攻术、智慧术等)
heroSpecial11英雄特长(生物亲和、法术亲和等)
army12部队构成奖励;目前仅用于士气
campaignBonus13战役场景带来的继承奖励
stackExperience14部队经验等级奖励
commander15指挥官单位能力
global16地图范围的全局效果(在游戏设置中定义)
other17无法归入其它类别的来源

导出实现见 luascript/api/Enums.cpp。

BonusValueType —— 奖励数值的组合规则(valueType 字段)

定义见 lib/bonuses/BonusEnum.h,它决定奖励值在叠加计算中以何种方式参与。

KeyValue说明
additiveValue0加到基础值上,但在percentToBase之后计算
baseNumber1在任何加法/百分比步骤之前,先加入基础值
percentToAll2百分比作用于所有来源合计的累计值
percentToBase3百分比只作用于基础值
percentToSource4百分比作用于同来源小计
percentToTargetType5百分比只作用于匹配 targetSourceType 的奖励
independentMax6独立上限——若大于累计值则取该值
independentMin7独立下限——若小于累计值则取该值

导出实现见 luascript/api/Enums.cpp。

BonusLimitEffect —— 奖励的战斗适用范围(effectRange 字段)

定义见 lib/bonuses/BonusEnum.h。源码注释特别指出:它用于标记如"精确"(Precision)这类只影响远程攻击的攻击/防御主技能奖励。

KeyValue说明
noLimit0适用于所有战斗类型
onlyDistanceFight1仅射击(远程)时生效
onlyMeleeFight2仅近战时生效

导出实现见 luascript/api/Enums.cpp。

实战:三个仓库内脚本的 Bonus 用法

  • scripts/combat/ballistaDamage.lua:查询来自宝物的攻击加成时,把ENUM.BonusSource.artifact与heroBaseSkill分别作为sourceType过滤条件,将"宝物攻击"与"英雄基础攻击"分离统计。
  • scripts/combat/rebirth.lua:为部队添加"免疫反击"奖励时组合使用ENUM.BonusDuration.nTurns(配合turns = 1)与ENUM.BonusSource.creatureAbility。
  • scripts/combat/soulSteal.lua:使用ENUM.BonusDuration.oneBattle标记本场战斗内有效的标记性奖励。
server:addUnitBonus(battle, unit, { type = "NO_RETALIATION", val = 0, duration = ENUM.BonusDuration.nTurns, turns = 1, sourceType = ENUM.BonusSource.creatureAbility, sourceID = unit:getCreature():getJsonKey() }, false)

由于BonusDuration是位掩码,若需要"战斗内且直到被攻击前"这类复合条件,可以用ENUM.BonusDuration.oneBattle + ENUM.BonusDuration.untilBeingAttacked(或位或运算)组合。


六、战场元素:ObstacleType、WallPart 与 BattleSide

ObstacleType —— 障碍物类别

用于 SpellObstacleDescriptor 的obstacleType字段。C++ 定义在 lib/battle/CObstacleInstance.h:

enum EObstacleType : ui8 { USUAL, ABSOLUTE_OBSTACLE, SPELL_CREATED, MOAT };
KeyValue说明
usual0战斗开始时随机放置的小型战场障碍物
absolute1阻挡战场固定区域的大型障碍物;地图上最多同时存在一个
spellCreated2由法术生成的障碍物(地雷、流沙、火墙等)
moat3城镇攻防战中的护城河地块

导出实现见 luascript/api/Enums.cpp。实战示例见 scripts/spells/moat.lua:根据法术是否可驱散,选择ENUM.ObstacleType.spellCreated或ENUM.ObstacleType.moat作为障碍类型。

WallPart —— 城镇城墙区段

攻城 API 与catapultAttack引用的城墙区段标识。C++ 定义在 lib/constants/Enumerations.h,前三个是负数哨兵值,从 0 开始才是真实区段:

enum class EWallPart : int8_t { INDESTRUCTIBLE_PART_OF_GATE = -3, INDESTRUCTIBLE_PART = -2, INVALID = -1, KEEP = 0, BOTTOM_TOWER, BOTTOM_WALL, BELOW_GATE, OVER_GATE, UPPER_WALL, UPPER_TOWER, GATE, PARTS_COUNT };
KeyValue说明
invalid-1无城墙区段;用于城防范围之外的格子
indestructiblePart-2始终存在且始终阻挡远程攻击的城墙段
indestructibleGate-3始终存在但不阻挡远程攻击的城门段
keep0中央城堡,通常由要塞(Citadel)建造
bottomTower1城墙底部箭塔,通常由城堡(Castle)建造
bottomWall2底部箭塔旁可摧毁的城墙段
belowGate3城门正下方的可摧毁城墙段
overGate4城门正上方的可摧毁城墙段
upperWall5上部箭塔旁的可摧毁城墙段
upperTower6城墙顶部箭塔,通常由城堡(Castle)建造
gate7可摧毁的城门,只有防守方可以开启

导出实现见 luascript/api/Enums.cpp。

实战:catapult 攻城脚本(scripts/spells/catapult.lua)把城墙区段组织为常量表,并映射每个区段的命中概率:

local WALLS = { ENUM.WallPart.upperWall, ENUM.WallPart.overGate, ENUM.WallPart.belowGate, ENUM.WallPart.bottomWall } local TOWERS = { ENUM.WallPart.upperTower, ENUM.WallPart.bottomTower } local KEEP = ENUM.WallPart.keep local GATE = ENUM.WallPart.gate

BattleSide —— 战场阵营标识

战场双方的阵营标识,同时提供"攻击方/防守方"与"左侧/右侧"两套等价命名。C++ 定义在 lib/battle/BattleSide.h,其中ATTACKER == LEFT_SIDE、DEFENDER == RIGHT_SIDE:

KeyValue说明
none-1与任何一方无关
attacker0发起战斗的攻击方军队;等价于left
defender1防守方军队;等价于right
left0从战场左侧开局的军队;等价于attacker
right1从战场右侧开局的军队;等价于defender

导出实现见 luascript/api/Enums.cpp。实战示例: scripts/combat/summonGuardians.lua 用side == ENUM.BattleSide.attacker判断召唤单位属于攻击方,进而执行 H3 原版"不可在敌方边界附近生成单位"的摆放规则;scripts/spells/catapult.lua 则用mechanics:getCasterSide() ~= ENUM.BattleSide.attacker限制智能施法只允许攻击方使用。


七、游戏进度:Difficulty、PlayerColor、PlayerStatus

Difficulty —— 游戏难度

五个难度等级以国际象棋棋子命名(兵最易、王最难),括号内为该难度的 AI 经济/资源系数。C++ 定义在 lib/mapping/MapDifficulty.h:

enum class EMapDifficulty : uint8_t { EASY = 0, NORMAL = 1, HARD = 2, EXPERT = 3, IMPOSSIBLE = 4, COUNT = 5 };
KeyValue说明
pawn0简单难度(80%)
knight1普通难度(100%)
rook2困难难度(130%)
queen3专家难度(160%)
king4极难难度(200%)

导出实现见 luascript/api/Enums.cpp。

PlayerColor —— 玩家颜色与中立归属

八种玩家颜色,外加neutral表示无主对象的归属(未占领城镇、矿场、巢穴等)。C++ 侧PlayerColor是基于StaticIdentifier的类,其哨兵值在 lib/constants/EntityIdentifiers.h 中定义(NEUTRAL = 255等),而 Lua 侧导出则遍历PlayerColor::ALL_PLAYERS()并追加neutral(见 luascript/api/Enums.cpp)。

KeyValue说明
red0红色玩家
blue1蓝色玩家
tan2褐色玩家
green3绿色玩家
orange4橙色玩家
purple5紫色玩家
teal6青色玩家
pink7粉色玩家
neutral-1无归属——用于未被占领的城镇、矿场和巢穴

PlayerStatus —— 玩家游戏状态

玩家是否仍在游戏中、已获胜或已失败。C++ 定义在 lib/constants/Enumerations.h(WRONG = -1为内部哨兵值,未导出)。

KeyValue说明
ingame0玩家仍在游戏中
loser1玩家已被击败并退出游戏
winner2玩家已赢得游戏

导出实现见 luascript/api/Enums.cpp。


八、英雄成长:PrimarySkill

四大英雄主技能,常与 HeroInstance 相关 API 配合使用。C++ 定义在 lib/constants/EntityIdentifiers.h(PrimarySkill::ATTACK、DEFENSE、SPELL_POWER、KNOWLEDGE)。

KeyValue说明
attack0攻击技能,提高英雄部队造成的伤害
defence1防御技能,降低英雄部队受到的伤害
spellpower2法术力量,放大英雄法术的效果
knowledge3知识,提高英雄的最大法力值

导出实现见 luascript/api/Enums.cpp。该枚举经常作为"子类型"与BonusSource.heroBaseSkill搭配出现,例如 scripts/combat/ballistaDamage.lua 中{ type = "PRIMARY_SKILL", subtype = "attack", ... }的subtype = "attack"即对应ENUM.PrimarySkill.attack的语义。


九、分组速查与使用建议

全部 15 个分组的官方文档索引

分组用途官方子文档
HealLevel治疗结果类别(治疗/复活/过量)HealLevel.md
HealPower治疗效果存续性(仅本场/永久)HealPower.md
SpellCastProblem施法校验错误码SpellCastProblem.md
AimType法术目标模式AimType.md
BonusDuration奖励存续期(位掩码)BonusDuration.md
BonusSource奖励来源类别BonusSource.md
BonusValueType奖励数值组合规则BonusValueType.md
BonusLimitEffect奖励的战斗适用范围BonusLimitEffect.md
ObstacleType障碍物类别ObstacleType.md
WallPart城墙区段WallPart.md
BattleSide战场阵营BattleSide.md
Difficulty游戏难度Difficulty.md
PrimarySkill四大主技能PrimarySkill.md
PlayerColor玩家颜色与中立归属PlayerColor.md
PlayerStatus玩家游戏状态PlayerStatus.md

使用要点

  1. 无需记忆数字:所有场景一律通过ENUM.<Group>.<key>引用常量;数字只是文档对照值,脚本中不应硬编码。
  2. 注意位掩码分组:BonusDuration的值是 2 的幂,可组合;其余分组大多为互斥的普通枚举。
  3. 拼写以文档为准:个别键名沿用了引擎源码的拼写(如ongoinTacticPhase),请以本文表格与官方子文档为准。
  4. 与 Bonus API 联动:BonusDuration、BonusSource、BonusValueType、BonusLimitEffect四组分别对应 BonusDescriptor 的duration、sourceType、valueType、effectRange字段,构造奖励表时直接引用即可获得编辑器补全与悬停说明。
  5. 类型提示支持:ENUM表由 DocExport.cpp 生成的注解驱动,支持 LuaLS 自动补全与悬停文档,建议在带 LuaLS 的编辑器中编写 mod 脚本。

延伸阅读:完整的全局 API 概览见 API.md;奖励结构见 Bonus.md 与 BonusDescriptor.md;战斗接口见 Battle.md 与 BattleServer.md。

  • 游戏开发

【免费下载链接】vcmi

Open-source engine for Heroes of Might and Magic III

项目地址:https://gitcode.com/gh_mirrors/vc/vcmi
点击查看免费下载
上一篇:网盘直链下载助手完整指南:九大网盘高速下载免费解决方案
下一篇:LinkSwift:九大网盘直链下载助手,告别限速烦恼

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询