资讯详情

资讯详情

VCMI Lua 脚本权威接口:AdventureServer 冒险地图修改接口全解析

游戏开发【免费下载链接】vcmiOpen-source engine for Heroes of Might and Magic III项目地址https://gitcode.com/gh_mirrors/vc/vcmi点击查看免费下载AdventureServer 是 VCMIHeroes of Might and Magic III 的开源引擎Lua 脚本系统中负责修改冒险地图状态的权威接口覆盖奖励发放、对话框展示、地图对象移除与脚本变量持久化四类核心能力。本文将结合该接口的 C 绑定源码与自动生成的 API 文档逐方法讲解其参数语义、底层实现与实战写法帮助你写出可在存档与联机对战中保持一致结果的地图脚本。AdventureServer 是什么AdventureServer 是权威的冒险地图修改接口the authoritative adventure-map mutation interface。其核心约束有三条仅服务端可用它只暴露给运行在服务器上的脚本。对照源码其 C 代理类 AdventureServerProxy 包装的是IGameEventCallback回调而 LuaMapEventDispatcher.cpp 正是把IGameEventCallback server作为server参数传给地图事件处理函数的。每次调用都会发出网络包所有状态变更都通过IGameEventCallback的方法如removeObject、giveResource、setObjPropertyValue下发客户端通过接收网络包同步结果因此多人游戏中所有客户端看到的状态一致。改动是持久的它还能存储脚本变量setMapVariable这些变量写入存档文件可跨访问、跨存档保留。参考文档为 docs/modders/Lua_Reference/AdventureServer.md与其查询侧的搭档 Game只读查询相对Game 负责读世界AdventureServer 负责改世界。文档从哪来docs/modders/Lua_Reference/下的页面全部由vcmiserver --export-lua-docs从 C 绑定自动生成luascript/AGENTS.md 明确要求不要手工编辑。生成的 Lua 类型定义在 api.lua 中包含---param/---return注解可供 Lua Language Server 提供补全与类型检查。开发者在修改绑定后运行vcmiserver --export-lua-docs docs/modders/Lua_Reference重新生成即可。脚本变量持久化setMapVariable---param name string ---param value any function AdventureServer:setMapVariable(name, value) end在指定名字下存储一个值该值保存在存档文件中之后可用Game:getMapVariable(name)读回。典型的用途是记住进度——例如记录某一次性奖励是否已经发放过避免玩家反复访问同一事件反复领取。namestring变量名。valueany任意能序列化为 JSON 的值——number、string、boolean或由这些组成的 table。写入同名变量会覆盖旧值。底层实现AdventureServer.cpp调用object.setScriptVariable(ModScope::scopeMap(), name, value)即变量按当前地图作用域mod 作用域存储。查询侧提供两个配套方法见 Game.mdGame:getMapVariable(name)读取已存变量从未存储过则返回nil。Game:hasMapVariable(name)判断某名字是否被设置过用于区分未设置与存了 0 或 false。实战片段function Base:onVisit(game, server, object, hero) if game:hasMapVariable(towerRewardGiven) then -- 已经领过直接显示进度文本 return end server:setMapVariable(towerRewardGiven, true) server:giveResource(hero:getOwner(), LIBRARY:getResourceByName(core:gold), 5000) end地图对象与任务系统这一组方法围绕任务源quest sourceseer hut 先知小屋 / quest guard 任务守卫与地图对象本身展开。涉及的实体类型见 MapObject。removeObjectfunction AdventureServer:removeObject(target) end从冒险地图上永久移除一个地图对象。对象对所有玩家消失若目标是城镇或英雄其所有权与驻军一并失去没有撤销操作。底层为object.removeObject(target, target.getOwner())AdventureServer.cpp。适用于普通事件plain events和 Pandoras Box 等需要直接消失的对象。finishQuestOrRemoveObject结束给定对象的当前任务行为因对象类型而异先知小屋SeerHut把当前任务标记为完成并清空切换到无任务状态但不删除小屋本身。底层通过setObjPropertyValue(SEERHUT_COMPLETE, true)实现。任务守卫QuestGuard从地图上移除removeObject。任务门QuestGate当前不受支持源码会记录警告并忽略AdventureServer.cpp。其他对象抛出LuaApiException提示不是任务源此时应改用removeObject处理普通事件与 Pandora。markQuestProposedregisterQuest的内部管道记录某玩家已经看过这个任务的提出文本这样下次访问时显示的是进度文本而非再次重复提出。底层设置SEERHUT_VISITED属性AdventureServer.cpp。查询侧对应Game:wasQuestProposed(target, player)。脚本一般通过registerQuest帮助函数使用不直接调用。addToQuestLog把对象的当前任务加入玩家的游戏内任务日志quest log。重复添加已在日志中的任务不会产生效果。底层要求对象实现IQuestSource接口AdventureServer.cpp否则记录错误并返回。setQuestHintTextsetQuestHint帮助函数的内部管道修改对象当前任务的悬停提示 / 任务日志文本。参数text是 MetaString支持翻译与%s、%d占位符。脚本应调用setQuestHint而非本方法。确定性随机random---return integer function AdventureServer:random(lower, upper) end返回[lower, upper]闭区间内的一个随机整数。关键点它从游戏自身的随机生成器取数底层是object.getRandomGenerator().nextInt(lower, upper)见 AdventureServer.cpp因此结果与存档、联机对战的随机序列一致可回放、可同步。涉及游戏玩法决策时不要使用 Lua 的math.random。-- 有 1/3 概率获得随机 100~300 金币否则一无所获 if server:random(1, 3) 1 then server:giveResource(player, LIBRARY:getResourceByName(core:gold), server:random(100, 300)) end英雄成长与属性giveExperiencefunction AdventureServer:giveExperience(hero, amount) end给英雄增加经验值若经验总量跨过升级阈值会自动升级人类玩家会收到升级对话框。amount为要增加的经验点数int64_t。底层object.giveExperience(hero, amount)。grantPrimarySkill / grantSecondarySkillfunction AdventureServer:grantPrimarySkill(hero, skill, amount) end function AdventureServer:grantSecondarySkill(hero, skill, level) endgrantPrimarySkill永久提升或降低四项主技能之一skill使用ENUM.PrimarySkillattack0、defence1、spellpower2、knowledge3见 PrimarySkill.md。amount为增减点数取负值即降低技能值被钳制在 0 以上。底层为相对变更changePrimSkill(..., ChangeValueMode::RELATIVE)。grantSecondarySkill教授一项副技能或把它改为指定等级1basic、2advanced、3expert。技能对象由Services:getSecondarySkillByName如core:wisdom取得。英雄没有空闲技能槽时不会学会新技能。底层为绝对设定changeSecSkill(..., ChangeValueMode::ABSOLUTE)。server:grantPrimarySkill(hero, ENUM.PrimarySkill.attack, 2) -- 攻击 2 server:grantSecondarySkill(hero, LIBRARY:getSecondarySkillByName(core:wisdom), 2) -- 智慧→高级资源与经济控制giveResourcefunction AdventureServer:giveResource(player, resource, amount) end为某玩家增加或减少一种资源。player为玩家索引0–7neutral-1见 PlayerColor.mdresource由Services:getResourceByName取得如core:gold、core:wood见 Services.mdamount取负值表示扣除国库被钳制在 0不会变成负数。底层object.giveResource(player, resource.getId(), amount)。setOwner转移地图对象城镇、矿井、巢穴等的所有权。对城镇或矿井而言收入与控制权立即转移但不会移动驻军或访问中的英雄。传入neutral-1可使对象变为无主。底层object.setOwner(target, owner)。-- 让蓝色玩家1接管某座矿井 server:setOwner(mine, 1) -- 让矿井变成无主 server:setOwner(mine, -1)grantCreaturesToHire修改地图上某城镇某一级tier可招募生物的库存数量。townTownInstancelevel生物等级 0–7count额外可招募数量取负值减少库存钳制在 0底层构造SetAvailableCreatures网络包并应用AdventureServer.cpp等级越界时会记录错误并返回。constructBuilding在城镇中免费建造一座建筑无视常规造价与前置条件。building为建筑标识符BuildingID。底层object.buildStructureForced(town.id, building)。魔法系统操作方法作用底层调用grantSpell(hero, spell)写入英雄法术书已会则无操作changeSpells(hero, true, {id})takeSpell(hero, spell)从法术书移除本就不会则无操作changeSpells(hero, false, {id})grantScroll(hero, spell)给予法术卷轴携带时无需法术书也能施放占据神器槽giveHeroNewScrollgrantSpellbook(hero)给予法术书已有则无操作没有法术书则已学法术无法在战斗中施放giveHeroNewArtifact(SPELLBOOK)takeSpellbook(hero)移除法术书英雄保留已学法术列表但无法施放直至再次获得removeHeroArtifact(SPELLBOOK)grantSpellPoints(hero, amount, mode)修改剩余法力值mode0加、1减钳制为 0、2直接设为 amountsetManaPoints法术对象统一由Services:getSpellByName取得如core:magicArrow。local spell LIBRARY:getSpellByName(core:magicArrow) server:grantSpell(hero, spell) server:grantSpellbook(hero) -- 保证可施放 server:grantSpellPoints(hero, 10, 1) -- 扣 10 点法力神器与战争机器grantArtifact / takeArtifactgrantArtifact把神器交给英雄。会装备到匹配的空槽位没有合适空槽时放入背包。只有装备时英雄才获得其加成。底层giveHeroNewArtifact(..., ArtifactPosition::FIRST_AVAILABLE)。takeArtifact移除英雄的神器无论是装备中还是在背包。若神器是组合神器combination artifact的部件会先拆解组合其余部件仍留在英雄身上英雄本就不拥有该神器则无操作。底层removeHeroArtifactAdventureServer.cpp先检测getCombinedArtWithPart并发送DisassembledArtifact拆解包再removeArtifact。grantWarMachine / takeWarMachine战争机器弩车、弹药车、帐篷本质是特殊神器占用其专属装备槽grantWarMachine放入专属槽已有则无操作takeWarMachine移除未携带则无操作。参数machine同样用Services:getArtifactByName取得。军队管理grantCreaturesfunction AdventureServer:grantCreatures(hero, creature, count) end向英雄军队添加生物。同种生物并入已有部队stack否则占据新军队槽位若军队已被其他生物占满新增生物会丢失。底层先构造临时CCreatureSet再giveCreaturesAdventureServer.cpp。生物对象由Services:getCreatureByName取得如core:pikeman。takeCreatures从军队移除指定数量的一种生物英雄拥有数量不足时全部移除清空的部队槽位消失。底层object.takeCreatures(hero.id, {CStackBasicDescriptor(...)})。server:grantCreatures(hero, LIBRARY:getCreatureByName(core:archer), 10) server:takeCreatures(hero, LIBRARY:getCreatureByName(core:archer), 3)临时状态与移动grantMorale / grantLuckfunction AdventureServer:grantMorale(hero, amount) end function AdventureServer:grantLuck(hero, amount) end给予英雄持续到下一场战斗结束的临时士气 / 幸运加成amount取负值降低。重复调用会叠加多个独立加成而不是替换前一个。底层为Bonus(BonusDuration::ONE_BATTLE, BonusType::MORALE/LUCK, ...)并通过GiveBonus网络包应用AdventureServer.cpp。grantMovementPoints修改英雄当前回合的剩余移动力。mode与法力一致0加、1减钳制为 0、2直接设为 amount。底层读取hero.movementPointsRemaining()后setMovePoints。server:grantMovementPoints(hero, 100, 2) -- 直接把移动力设为 100玩家交互消息与阻塞对话框showMessagefunction AdventureServer:showMessage(player, text, components, soundID, windowType) end为单个玩家弹出一个消息框。该调用不等待玩家反应——脚本立即继续消息框在下一个合适时机显示因此只适合做通知不适合做提问。参数textMetaString可翻译并可嵌入名字与数字components可选图标列表如奖励的资源、神器省略则为纯文本框soundID可选打开时播放的声音windowType可选窗口外观省略则由引擎根据内容自动选择底层构造InfoWindow网络包AdventureServer.cpp。components中每个 Component 由type图标种类、subType具体实体索引、value数量正为获得负为失去描述。local text MetaString:new() text:append(你获得了 %d 金币) text:replaceNumbers({100}) server:showMessage(player, text)spawnDialog 与阻塞助手spawnDialog是阻塞型showQuestion/showRewardsMessage帮助函数的内部管道显示一个模态对话框并注册问题玩家回答后恢复被暂停的脚本。mode0为纯确认框其他值显示是/否问题。底层构造BlockingDialogAdventureServer.cpp。脚本应调用阻塞助手而非直接调用 spawnDialog。这些助手由运行时层 scripts/mapEventRuntime.lua 提供它把原始的 AdventureServer proxy 装饰成支持协程的server对象并用协程让脚本在玩家回答前暂停mapEventRuntime.luafunction server:showQuestion(opts) -- opts.text / opts.images / opts.onYes / opts.onNo / opts.onCancel raw:spawnDialog(player, opts.text, 1, components) local answer coroutine.yield() -- 暂停直到玩家回答 -- answer: 1是 0否 其他取消 end function server:showRewardsMessage(messagePlayer, text, reward) raw:spawnDialog(messagePlayer, text, 0, {}) coroutine.yield() if reward then reward() end end事件分发器 LuaMapEventDispatcher.cpp 负责把run返回的协程句柄保存起来玩家应答后经resume(coroutineHandle, answer)恢复脚本。因此阻塞式对话在服务端暂停脚本、等玩家输入、再继续执行并保持对存档的一致性。典型事件脚本function Base:onVisit(game, server, object, hero) server:showQuestion{ text 你是否接受这个任务, onYes function() server:giveResource(hero:getOwner(), LIBRARY:getResourceByName(core:gold), 1000) end, onNo function() server:showRewardsMessage(hero:getOwner(), 你放弃了任务。, nil) end } end强制战斗spawnCombat 与 startCombatspawnCombat是startCombat帮助函数的内部管道把被访问对象事件 / Pandora的驻军替换为给定军队并让访问英雄就地开战。host被访问的事件 / Pandora 对象必须能承载军队即实现CArmedInstancehero参战英雄army描述敌方军队的{count, creatureKey}对列表底层流程AdventureServer.cpp先清空宿主对象全部军队槽位再按army逐槽插入新生物CreatureID::decode解析creatureKey字符串最后object.startBattle(hero, armedHost)。若宿主不能承载军队会记录错误并返回。-- 事件脚本中召唤 5 只剑士作为守军 server:startCombat(hero, { {count 5, creatureKey core:swordsman} })同样地协程运行时让startCombat阻塞到战斗结束mapEventRuntime.lua。综合实战示例一次性奖励任务把以上能力组合成一个完整的地图事件脚本演示一次性奖励 任务日志 阻塞对话 资源发放 对象移除的典型闭环function Base:onVisit(game, server, object, hero) local player hero:getOwner() -- 1. 一次性判定用地图变量防止重复领取 if game:hasMapVariable(dragonHoardTaken) then return -- 已领取静默离开 end -- 2. 先记录再询问 server:setMapVariable(dragonHoardTaken, true) server:addToQuestLog(object, player) server:showQuestion{ text 你发现了龙之宝藏要打开它吗, onYes function() server:giveResource(player, LIBRARY:getResourceByName(core:gold), 5000) server:giveExperience(hero, 2000) server:grantArtifact(hero, LIBRARY:getArtifactByName(core:goldenBow)) server:finishQuestOrRemoveObject(object) -- 完成任务并让守卫消失 server:showRewardsMessage(player, 你获得了 5000 金币和一把金弓) end, onNo function() -- 玩家放弃撤销记录以便下次再来 server:setMapVariable(dragonHoardTaken, false) end } end使用要点与常见陷阱随机必须走server:randommath.random不参与游戏随机序列会破坏存档回放与联机一致性。区分通知与提问showMessage不阻塞、不等待需要玩家决策时使用showQuestion/showRewardsMessage经由spawnDialog它们在服务端以协程暂停脚本直至应答。finishQuestOrRemoveObject有对象类型限制仅先知小屋与任务守卫受支持任务门会被忽略普通事件 / Pandora 请用removeObject。removeObject无撤销对城镇 / 英雄调用会连所有权与驻军一起抹掉务必先做状态检查。负数即减少giveResource、grantPrimarySkill、grantMorale、grantLuck、grantCreaturesToHire均接受负amount且各自的钳制规则不同资源 / 技能钳制在 0士气 / 幸运没有下限说明。模式参数grantSpellPoints与grantMovementPoints的mode语义一致——0加、1减钳制为 0、2设为总量。军队满载会丢生物grantCreatures在军队全是他种生物时新生物直接丢失调用前可用Game查询接口确认槽位。文档自动生成docs/modders/Lua_Reference/由vcmiserver --export-lua-docs生成API 定义以 luascript/api/callback/AdventureServer.cpp 的registerMethods注册为准需要类型补全的编辑器可加载 api.lua。延伸阅读接口全貌与类索引API.md只读查询侧接口Game.md实体类型MapObject、HeroInstance、TownInstance可翻译文本构造MetaString内容目录查询Services.md运行时协程装饰层scripts/mapEventRuntime.lua绑定实现luascript/api/callback/AdventureServer.cpp赞分享游戏开发【免费下载链接】vcmiOpen-source engine for Heroes of Might and Magic III项目地址https://gitcode.com/gh_mirrors/vc/vcmi点击查看免费下载相关推荐VCMI Lua 脚本 API 详解Game 冒险地图查询接口getHero / getObj / getMapVariable / wasQuestProposed 等VCMI Lua 脚本 API 详解Game 冒险地图查询接口getHero / getObj / getMapVariable / wasQuestPro游戏开发VCMI 战斗服务端权威接口 BattleServer 完全指南Lua 脚本如何安全地改写战场状态VCMI 战斗服务端权威接口 BattleServer 完全指南Lua 脚本如何安全地改写战场状态 导读 本文以 VCMI 仓库中的 BattleServe游戏开发VCMI Lua 脚本实战Battle 战场查询接口Battle.md完全解析VCMI Lua 脚本实战Battle 战场查询接口Battle.md完全解析 VCMI 的 Lua 战斗脚本战斗事件脚本、法术效果脚本等在获得战斗上游戏开发上一篇Laya-MLX 实战指南在 Apple Silicon 上以毫秒级延迟原生运行 Laya 类型化决策模型下一篇深入解析 Fast Weights Transformer线性自注意力与快速权重记忆系统的 PyTorch 实现创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →