lua应用

7.8k 词

关于Lua脚本语言在太阳神三国杀中的应用

关于神杀

太阳神三国杀,是一款基于C++ QT GUI框架的三国杀非官方开源软件,开发者:Moligaloo(太阳神上),现在由Mogara团队继续维护源码。在实现游卡三国杀游戏规则的基础上,还拥有自己独特的功能和元素:原创技能卡牌配音;原创扩展包倚天、欢乐;原创扩展模式双将、剧情、闯关、国战等。拥有智能AI可以实现联机和单机的两种游戏方式,并能通过DIY接口进行自由的个性化修改和添加更多元素。

关于Lua

Lua是一个小巧的脚本语言。它是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个由Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo三人所组成的研究小组于1993年开发的。 其设计目的是为了通过灵活嵌入应用程序中从而为应用程序提供灵活的扩展和定制功能。Lua由标准C编写而成,几乎在所有操作系统和平台上都可以编译,运行。Lua并没有提供强大的库,这是由它的定位决定的。所以Lua不适合作为开发独立应用程序的语言。Lua 有一个同时进行的JIT项目,提供在特定平台上的即时编译功能。
Lua脚本可以很容易的被C/C++ 代码调用,也可以反过来调用C/C++的函数,这使得Lua在应用程序中可以被广泛应用。不仅仅作为扩展脚本,也可以作为普通的配置文件,代替XML,ini等文件格式,并且更容易理解和维护。Lua由标准C编写而成,代码简洁优美,几乎在所有操作系统和平台上都可以编译,运行。一个完整的Lua解释器不过200k,在目前所有脚本引擎中,Lua的速度是最快的。这一切都决定了Lua是作为嵌入式脚本的最佳选择。
而神杀就可以通过lua来进行扩展包的添加

上手操作

创建自己的扩展包

创建扩展包

在三国杀中,有许许多多的包,标准,风林火山,一将成名……那么我们能不能自己创建一个包呢?答案是可以的,我们新建一个lua文件。命名为自己想要的名称就可以了。例如,这次我们以迫害王强为例,建立一个王强包吧。为了方便,我们把包名定为wangqiang.lua,将文件建立在extensions文件夹中,并在里面添上以下代码:

1
2
module("extensions.wangqiang", package.seeall)
extension = sgs.Package("wangqiang")

这两句的通用格式为:

1
2
module("extensions.扩展包名称",package.seeall)
extension = sgs.Package("扩展包名称")

但是,游戏可不知道wangqiang代表什么文字,它只会一五一十的把wangqiang写上去,这可怎么办?这时候便需要一个翻译表,使用sgs.LoadTranslationTable来进行创建。格式如下:
[“原文”] = “译文”
两句之间用英文逗号分隔开来,例如:

1
2
3
sgs.LoadTranslationTable {
['wangqiang'] = '王强包',
}

保存之后我们进入游戏康康。
王强包效果
哈哈快看,王强包在右下角看的清清楚楚。这样一来,王强包的添加就到此完成了。

创建自己的新武将

包有了,但里面是空的啊。这个时候,我们就要向里面装东西了。既然这次我们是以王强为例进行迫害。那么新武将就决定是王强了,和变量命名规则一样,我们以wangqiang作为其名称进行创建:

1
wangqiang = sgs.General(extension,"wangqiang","ri","4") 

创建武将的函数原型如下:

1
2
3
4
5
6
7
8
9
sgs.General(
package,
name,
kingdom,
max_hp=4,
male=true,
hidden=false,
never_shown=false,
)

翻译过来即是:
sgs.General(扩展包,姓名,国籍,体力上限,性别,是否隐藏,是否完全隐藏)
相关参数:

  1. package(扩展包):统一写extension就行了。
  2. name(姓名):没忘刚才好不容易起的名字吧?wangqiang,就它了!另外,如果是主公武将,需要在名字后面加上$符号,来显示这个武将”超凡脱俗”的气质。比如曹操就是caocao$什么的。
  3. kingdom(所属势力):列在下面了——
    ☆魏势力:wei
    ☆蜀势力:shu
    ☆吴势力:wu
    ☆群雄势力:qun
    ☆神武将:god
    当然,出于特殊的迫害目的,王强的国籍是自定义的,这个不在这里讨论。
  4. max_hp(体力上限):就是勾玉的数目,不写的话默认是4。
  5. male(性别):填true就是男性,填false就是女性,……什么都不写也是男性!(函数默认参数的知识点!)
  6. hidden(是否隐藏):填true就是隐藏了,填false就是不隐藏,默认当然是不隐藏了~
  7. never_shown(是否完全隐藏):依然是填true隐藏、填false或者默认是不隐藏。上面那个隐藏只是在游戏中不会被系统列在选将名单里,开自由选将的话还是能选到的(比如测试包里的五星诸葛亮);这个完全隐藏可是连自由选将都没希望找到的,当然现在我们的游戏中还没有这样的武将就是了……

建立完成之后,不要忘记向翻译表里添加东西啊:

1
2
3
4
5
6
7
8
sgs.LoadTranslationTable {
['wangqiang'] = '王强包',
['Wangqiang'] = '王强',
['&Wangqiang'] = '王强',
['designer:Wangqiang'] = '王建军',
['illustrator:Wangqiang'] = '王强',
['#Wangqiang'] = '猪精',
}

其中:
[“武将名称”] 就是对武将本身名字的翻译;
[“&武将名称”]是游戏中显示的武将名字,比如☆SP貂蝉在游戏中显示为“貂蝉”而没有那些前缀,就是这里规定的;
[“#武将名称”] 则是对武将称号的翻译,像☆SP貂蝉的称号“暗黑的傀儡师”之类的都是在这里添加的。
另外,武将设计者的名字由 [“designer:武将名称”] 提供,不写的话默认为“官方”。还不快快签上自己的大名?
武将台词配音者的名字由 [“cv:武将名称”] 提供,默认为”官方”;武将插画绘制者的名字由 [“illustrator:武将名称”] 提供,默认为”KayaK”。可以在武将一览的右上角看到这三部分的信息。
添加完毕,我们进入游戏康康吧:
王强包效果
好,王强就这样被安排的明明白白。

武将美化

额,关于武将牌面的制作,这里就不多讲了。

添加技能

王强总归现在是个白板,玩起来No行,怎么办呢,我们可以添加技能进去。这样一来有2个方案:1. 添加游戏中已有技能 2. 自己动手写技能。我们首先介绍一下如何为武将添加技能,添加技能有一个函数,它有两个原型:

1
2
addSkill(skill)
addSkill(skill_name)

其中skill是一个技能对象,而skill_name是一个字符串,引用一个已存在的技能,例如,为我们的王强添加一个技能“耀武”的代码如下:

1
wangqiang:addSkill("yaowu")

耀武: 锁定技,当一名角色使用【杀】对你伤害时,若之为:红色,来源选择是否回复1点体力,若其选择否,其摸一张牌;不为红色,你摸一张牌。
因为是引用的别人的技能(华雄),所以使用带引号的技能名称,但是终究不过瘾,我们能不能自己写技能呢,答案当然是可以的。

创建自己的技能

技能类型

神杀中的技能类型大约有以下几类:

  • 视为技(ViewAsSkill)
  • 触发技(TriggerSkill)
  • 禁止技(ProhibitSkill)
  • 距离修改技(DistanceSkill)
  • 手牌上限技(MaxCardsSkill)
  • 锁定视为技(FilterSkill)
  • 目标模式技(TargetModSkill)
视为技

简单来讲,视为技能就是将X张手牌视作X技能的技能卡使用,实现相关的效果。以界孙权的技能制衡为例:
制衡: 出牌阶段限一次,你可以弃置至少一张牌,然后若你以此法弃置了所有手牌,你摸X+1张牌,否则你摸X张牌。(X为你以此法弃置的牌数)
翻译过来就是:出牌阶段限一次,你可以弃置X张牌,视作你使用了1张“制衡技能卡”。而技能卡则是这样的:
制衡技能卡:你摸Y张牌(若你以此法弃置了所有手牌,则Y=X+1,否则Y=X)
如此一来,许多技能即是由这种技能卡所堆积而成的了。

触发技

触发技其实在三国杀里比较常见,通常是以阶段触发技为主,例如貂蝉的“闭月”:
闭月: 结束阶段开始时,你可以摸X张牌。(若你没有手牌,X为2,否则X为1)
这就是一个典型的触发技,当进入到你的结束阶段时,触发技能效果。你摸X张牌,而X的值由你的手牌数而定。

禁止技

禁止技的效果是不能成为某种牌的目标。例如:贾诩的“帷幕”:
帷幕: 锁定技,你不是黑色锦囊牌的合法目标。
通常来讲,在确定目标时,就已经进行了判断,与于禁的“毅重”不同,于禁的“毅重”是成为了黑色杀的目标之后,再触发技能。也就是说,假如于禁和贾诩单挑,于禁有一张黑色的【过河拆桥】,那么于禁是不能使用这张牌的(因为没有合法目标),而如果贾诩有黑色的【杀】则可以指定于禁为目标,但是会触发“毅重”的效果,终止【杀】的结算。
毅重: 锁定技,若你的装备区里没有防具牌,黑色【杀】对你无效。

距离修改技

距离修改技即是修改玩家之间的技能,熟悉的便是“马术”“飞影”以及白马公孙瓒的“义从”了:
马术: 锁定技,你与其他角色的距离-1。
飞影: 锁定技,其他角色与你的距离+1
义从: 锁定技,若你的体力值:大于2,你与其他角色的距离-1;不大于2,其他角色与你的距离+1。

手牌上限技

顾名思义,这个技能可以修改你的手牌上限。
有增加上限的,例如刘表的“宗室”:
宗室: 锁定技,你的手牌上限+X。(X为势力数)
也有固定上限的,例如新版周泰的“不屈”:
不屈: 锁定技,当你处于濒死状态时,你将牌堆顶的一张牌置于武将牌上,称为“创”,然后若此“创”与其他“创”点数均不同,你将体力值回复至1点,否则你将此“创”置入弃牌堆;锁定技,若有“创”,你的手牌上限为X(X为“创”数)。
技能的第二个效果即是固定手牌上限了。

锁定视为技

这个其实与视为技类似,只不过多了强制性,例如神关羽的“武神”:
武神: 锁定技,你的红桃手牌视为【杀】;锁定技,你使用红桃【杀】无距离限制。
对比普通关羽的“武圣”:
武圣: 你可以将一张红色牌当【杀】使用或打出。
很明显,这是锁定技,也就是说,神关羽的红桃手牌不能自由地控制它的属性,只能当作【杀】打出,而关羽则可以根据需要将红色手牌以不同的牌面进行使用。

目标模式技

这个技能类型即是规定了使用牌的目标,例如:虎牢关吕布的“神戟”既是一个典型的例子:
神戟: 若你的装备区里没有武器牌,你使用【杀】的额外目标数上限+2。
平常的【杀】只能杀1个人,但是通过这种技能,我们可以指定多名角色为目标。

实例-王强技能

那么我们为王强设计的技能如下:
为情所困: 锁定技,限定技,准备阶段开始时,若场上存在女性角色且你的体力上限大于2,则你减体力上限至2。锁定技,你不是【决斗】【南蛮入侵】【万箭齐发】【杀】的合法目标。
将技能拆开来看,既是一个触发技和一个禁止技的结合。于是我们可以创建两个技能,再将其中一个进行隐藏。我们首先进行禁止技的创建吧:

1
2
3
4
5
6
7
8
9
LuaWeiqingsuokunPro = sgs.CreateProhibitSkill {
name = 'LuaWeiqingsuokunPro',
is_prohibited = function(self, from, to, card)
if to:hasSkill('LuaWeiqingsuokunPro') then
return card:isKindOf('Duel') or card:isKindOf('Slash') or card:isKindOf('SavageAssault') or
card:isKindOf('ArcheryAttack')
end
end
}

看起来似乎有点长啊,但实际上并不复杂,类似的我们以C/C++的语法也能理解它。首先是创建了一个名为LuaWeiqingsuokunPro的禁止技技能对象。它的名称(name)是LuaWeiqingsuokunPro,而is_prohibited即是判断准则,card是目标卡牌,return card:isKindOf(“卡牌类型”)即是不能成为此种卡牌的目标。具体的卡牌名称可以在lang文件夹下进行查阅,注意要把下划线去掉并且做好大小写区分。例如,当一名角色准备使用【决斗】指定目标时,这个时候card就是Duel,而这个function返回的值就是true,那么is_prohibited就是true了。王强就不能被选中成为这张【决斗】的目标了。
那么接下来,我们实现第一个功能吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
LuaWeiqingsuokun = sgs.CreateTriggerSkill {
name = '#LuaWeiqingsuokun',
frequency = sgs.Skill_Compulsory,
events = {sgs.EventPhaseStart},
on_trigger = function(self, event, player, data)
local room = player:getRoom()
if player:getPhase() == sgs.Player_Start then
if player:getMark('LuaQingkun') == 0 then
if player:getMaxHp() > 2 then
local others = room:getOtherPlayers(player)
for _, target in sgs.qlist(others) do
if target:isFemale() then
room:sendCompulsoryTriggerLog(player, self:objectName())
room:loseMaxHp(player, player:getMaxHp() - 2)
break
end
end
room:addPlayerMark(player, 'LuaQingkun')
end
end
end
end
}

唔,看起来有点长,我们进行一下拆分吧

1
2
3
4
5
6
7
8
LuaWeiqingsuokun = sgs.CreateTriggerSkill {
name = '#LuaWeiqingsuokun',
frequency = sgs.Skill_Compulsory,
events = {sgs.EventPhaseStart},
on_trigger = function(self, event, player, data)
-- statements
end
}

这些是触发技的基本要素:名称(name)、类型(发动频率/frequency)、响应事件(events)、触发的动作(on_trigger)还有就是省略的
能否触发(can_trigger),它的默认值是存活且拥有本技能,所以未列出。这里的技能名称前面加了#符号,代表不会占用技能格,也就是没有技能按钮。
好了,王强的技能是锁定技,那么使用枚举类型sgs.Skill_Compulsory进行定义。查看源码可以看到所有的技能类型:

1
2
3
4
5
6
7
8
9
10
11
...
class : public QObject
{
enum Frequency{
Frequent, // 默认触发技
NotFrequent, // 询问触发技
Compulsory, // 锁定技
Limited, // 限定技
Wake // 觉醒技
};
...

即是对应的,默认触发技(符合条件时自动选择触发,可以更改为每次询问),每次询问的触发技,锁定技(符合条件直接执行),限定技,觉醒技
那么王强的是一个限定技,我们只需要加入触发一次之后不再次触发的条件就好了。我们使用标签来判断王强有没有执行过判断吧。不过首先要判断是否处于准备阶段,我们使用getPhase()来获取玩家当前的阶段。同样,它也有一些枚举值,我在此列出:

1
2
3
4
5
6
7
class Player : public QObject
{
...
public:
enum Phase {RoundStart, Start, Judge, Draw, Play, Discard, Finish, NotActive, PhaseNone};
...
}

分别对应:回合开始、准备阶段、判定阶段、摸牌阶段、出牌阶段、弃牌阶段、结束阶段、回合外、空阶段
但是光有玩家还是不够的,我们需要帮手来完成一系列的操作,它就是游戏的“房主”房间room。我们首先获取玩家的room,再利用room为所欲为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
on_trigger = function(self, event, player, data)
local room = player:getRoom()
if player:getPhase() == sgs.Player_Start then -- 如果是准备阶段
if player:getMark('LuaQingkun') == 0 then -- 如果LuaQingkun标记数为0
if player:getMaxHp() > 2 then -- 体力上限大于2才接着进行下面的操作
local others = room:getOtherPlayers(player) -- 获取其他的玩家列表
for _, target in sgs.qlist(others) do -- 遍历其他的玩家
if target:isFemale() then -- 如果玩家性别为女
room:sendCompulsoryTriggerLog(player, self:objectName()) -- 显示消息(X玩家的“为情所困”被触发)
room:loseMaxHp(player, player:getMaxHp() - 2) -- 失去对应的体力上限值
break -- 因为已经找到了女性,没有继续遍历的必要了,跳出循环
end
end
room:addPlayerMark(player, 'LuaQingkun') -- 添加一个LuaQingkun标记,防止重复进行
end
end
end
end

好了,我们进入游戏看看效果吧。
看这个袁绍用的【万箭齐发】根本伤不到王强,真的太逊了。(王强:这个袁绍就是逊啦)
为情所困效果
士兵(女):听你这么说,你很勇哦。
士兵女
哦豁,场上有女角色出现了!到了王强的回合……
为情所困效果
噔 噔 咚 ,王强现在只有2点体力上限了,真是太不幸了。
那么,到这里为止,本次的解说迫害也就到此结束了。