太阳神三国杀Lua编写高级教程

时间:2024-10-21 01:25:59

1、制作一张基本牌的结构为:<card> = sgs.CreateBasicCard{ name, class_name, subtype, target_fixed, can_recast, suit, number, filter,feasible, available,about_to_use, on_use, on_effect,}

2、下面介绍每个成员的含义。 name:字符串类型,卡牌的显示名称。函数objectName()所获得的名称就是这个。而在LoadTranslationTable中会瓴烊椹舟将字符串翻译成所对应的新字符串。 target_fixed:布尔类型,目标是否已固定。如果为true,那么使用该牌时无法指定目标,直接使用。否则的话还要经历选择目标的过程。 filter:布尔类型,目标筛选器。函数的形式为function(self, targets, to_select),返回值的真假决定着被筛选目标(to_select)是否可选。该成员不可省略,但若target_fixed为true,则可以省略。feasible:布尔类型,卡牌可用与否。函数的形式为function(self, targets),返回值决定了卡牌在目前情况下是否可以使用(即点击“确定”)。缺省值在target_fixed=false时,为return #targets > 0。 on_use:卡牌使用后的执行函数。函数的形式为function(self, room, source, targets)。缺省时为对targets中每个目标角色执行一次效果,亦即on_effect。 on_effect:卡牌生效时的执行函数。函数的形式为function(self, effect)。缺省值为什么也不执行。注意卡牌没有will_throw成员,也就是说,不存在使用后不弃置的卡牌。 而另外一些就是全新的了—— class_name:字符串类型,卡牌的类名,我们平常在Lua中见到的isKindOf()的参数就是这个。和name不同在于,几种卡牌可以使用同一个class_name,比如不同属性的【杀】,它们的class_name都是“Slash”。 subtype:字符串类型,卡牌的子类名。实际上一般情况下该成员并没有实际意义,仅仅是在“卡牌一览”中显示其为“攻击牌”或者“防御牌”来用的;不过用getSubType()函数仍然可以获得之。这个和name一样可以被翻译。 can_recast:布尔类型,能否重铸。重铸和铁索连环的第二个用法一样,不属于使用、打出、弃置。缺省值为false,即不可重铸。suit:整型,卡牌的花色。如果该牌的花色是唯一的,那么便可以在Lua中写上此成员。 number:整型,卡牌的点数。如果该牌的点数是唯一的,那么便可以在Lua中写上此成员。正常情况下点数是1~13,但是其他的值也是可以使用的,不过在卡牌上面没有点数文字罢了。 available:布尔类型,是否可直接使用。函数的形式为function(self, player),这决定了该卡牌是否可以在出牌阶段中使用,类似于ViewAsSkill中的enabled_at_play。about_to_use:卡牌在点击“确定”的时候所执行的函数。函数的形式为function(self, room, use)。

3、制作一张类似“杀”可以主动使用的基本牌。模板:<card> = sgs.CreateBasicCard{ name = "cardname", class_name = "classname", subtype = "attack_card", target_fixed = false, can_recast = false, suit = <suit>, number = <number>, filter = function(self, targets, to_select) ... end, available = function(self, player) ... end, on_effect = function(self, effect) ... end,}其中 on_effect中写入执行效果。例如要让目标角色摸一张牌,那么就将on_effect写成:on_effect = function(self, effect) effect.to:drawCards(1) end,effect.to是目标角色,而effect.from则是效果来源。比如我要让目标摸一张牌,我摸三张牌,那么就写成:on_effect = function(self, effect) effect.to:drawCards(1) effect.from:drawCards(3) end,如果是对目标角色造成一点伤害,那就是: on_effect = function(self, effect) local room = effect.from:getRoom() room:damage(sgs.DamageStruct(card, effect.from, effect.to,1, sgs. DamageStruct_Normal)) end,

4、我说几点跟锦囊牌编程不同的地方。 首先,注意on_use和on_effect。在编写锦囊牌的时候,因为几乎没有哪个技能有“锦囊牌对你无效”之类的语句,所以两者可以随便互换用。但是现在编写的不是锦囊牌,而是基本牌,所以要考虑卡牌生效的问题了。于是,我们需要这样子写:on_use = function(self, room, source, targets) --在对所有目标生效前执行的语句 ...–-注意,这里的<targets>不一定就是上面的targets参数 for _, target in ipairs(<targets>) do room:cardEffect(self, source, target) end --在对所有目标生效后执行的语句 ... end, on_effect = function(self, effect) --对每名目标的效果 ... end,

5、制作一张【闪】因为它只能在【杀】或【万箭齐发】需要响应的时候打出,反倒是在出牌阶段不能使用。代码:<card> = sgs.CreateBasicCard{ name = "cardname", class_name = "Jink", subtype = "defense_card", target_fixed = true, can_recast = false, suit = <suit>, number = <number>, available = function(self, player) return false end,其中 available下的 "return false"表示使用后返回“假”值,如果改为return true表示使用后返回“真”值,还是会造成伤害。这种卡牌没有使用能力,所以省略了on_use和on_effect,而且available返回值恒为false。至于如何像【闪】一样能在【杀】或【万箭齐发】需要响应的时候打出,很简单——class_name="Jink"即可。这样的话,能够打出【闪】的时机,也能打出这张牌。类似的,为Slash时可以在【决斗】【南蛮入侵】时打出此牌。打出和使用并不矛盾,你可以制作既能使用又能打出的牌。值得注意的是,尽管【桃】在濒死时候使用是“打出”,但仍旧会触发on_use和on_effect效果,类似的还有【借刀杀人】、挑衅时候的【杀】。如果想制作一张“抵消【杀】的效果并摸一张牌”的卡牌,那么光靠CreateBasicCard就不够了,还得使用一个触发技来实现额外的效果。

6、制作【杀】类卡牌说到【杀】类卡牌,我就要在这里床痤迷茸额外谈论一段了。因为【杀】不同于其他的基本牌,它有专门的触发事件。也就是sgs.SlashEffect,sgs.SlashEffected,s爿讥旌护gs.SlashProceed,sgs.SlsahHit,sgs.SlashMissed等。而如果单纯地写效果,则显然不会触发这些,如:on_effect = function(self, effect) local source = effect.from local target = effect.to local room = source:getRoom() room:setEmotion(source, "killer"); if not room:askForCard(target, "jink", "slash-jink:"..source:objectName(), sgs.QVariant(), sgs.Card_MethodResponse) then local damage = sgs.DamageStruct() damage.from = source damage.to = target damage.damage = 1 damage.card = self room:damage(damage)end这是简单地描述【杀】的过程的on_effect。它只有要求出闪与造成伤害的作用,但没有触发任何与【杀】本身相关的事件。比如技能【铁骑】,虽然可以在此牌指定目标后询问是否发动,但不能在判定为红后使其不可闪避。此外,该代码中询问【闪】的部分给AI使用的data为空,使得AI不能通过【杀】的特征来决定是否出【闪】。要让卡牌能够触发【杀】相关的事件,你可能会想到在语句中插入相应的trigger()函数。但是问题在于:①事件的参数和返回值太复杂,②即使能够正确地插入trigger(),但是在触发技中常见的slashResult()函数并不会按照预想的效果执行。而笔者之前试过直接使用slashEffect()的on_effect,即:on_effect = function(self, effect) local room = effect.from:getRoom() local slasheffect = sgs.SlashEffectStruct() slasheffect.from = effect.from slasheffect.slash = self slasheffect.to = effect.to room:slashEffect(slasheffect)end与此配套的还有一个全局的触发技能,用于设定【杀】在造成伤害后的效果。但是这样写之后,并没有让这张【杀】类卡牌触发相关的技能。和前面的一样,“不可被闪避”仍旧无效。所以我们只能想到这种描述方式:on_use = function(self, room, source, targets)local slash = sgs.Sanguosha:cloneCard("slash", self:getSuit(), self:getNumber()) slash:addSubcard(self) room:setCardFlag(slash, "special_flag") local use = sgs.CardUseStruct() use.card = slash use.from = source for _, target in ipairs(targets) do use.to:append(target) end room:useCard(use)end 其含义是“将这张卡牌当做一张普通的【杀】来使用”。至于这张【杀】会造成什么样的效果,就只有靠另外的一个触发技能了。触发技能全文如下:<skill> = sgs.CreateTriggerSkill{ frequency = sgs.Skill_Compulsory, events = {sgs.Predamage}, name = "skillname", can_trigger = function(self, target) return target end, global = true, priority = 10, on_trigger=function(self,event,player,data) local damage = data:toDamage() if damage.card:hasFlag("special_flag") then --在这里填写卡牌造成伤害时执行的语句--如用data:setValue(damage)设置伤害值... end end}--将技能直接添加到sgs.Sanguosha中local skillList=sgs.SkillList()if not sgs.Sanguosha:getSkill("skillname") then skillList:append(<skill>)endsgs.Sanguosha:addSkills(skillList) 用以上的方式所写的卡牌,可以触发【杀】类的事件。但是会存在其他的问题,就是在使用的时候会显示“玩家将XX当做【杀】使用”。

7、单体锦囊牌基本的上个教程已经说了,所以来制作延时锦囊。当subclass为sgs.LuaTrickCard_TypeDelayedTrick时会默认一些符合延时锦囊效果的成员。这里我们详细地解释一下。on_use会将锦囊移动到目标角色的判定区内。on_nullified会像标准的延时锦囊一样作出决定。如果判定阶段被【无懈可击】,那么根据是否为传递型延时锦囊(由一个额外的成员movable决定),决定是弃置或者移动到下家。事实上,延时锦囊弃置的语句为:local reason = sgs.CardMoveReason(sgs.CardMoveReason_S_REASON_NATURAL_ENTER, to:objectName())room:throwCard(self, reason, nil) 以上两个成员便可以省略不写。这里还会多出一个成员movable。它的取值为布尔类型,true代表传递型延时锦囊(如【闪电】),false代表非传递型延时锦囊(如【乐不思蜀】)。另外,由于锦囊位于判定区,on_effect会在判定阶段才执行,其中也就是延时锦囊的判定效果。这与subclass无关。在on_effect中也可以调用on_nullified的效果,同样无需额外代码,方法是使用函数self.on_nullified(self, player)。

8、制作一张装备牌 相比之下,装备牌的代码就不是特别复杂了,因为许多东西是已经预定好了的。 首先我们看武器:<weapon> = sgs.CreateWeapon{ name, class_name, suit, number, range, on_install, on_uninstall,}其中一些成员已经在前面介绍过了,我们说一下与之前不同的成员含义。range:整数类型,顾名思义,是用来指定武器攻击范围的。值为1,就是攻击范围为1,以此类推。不要考虑什么动态的攻击范围(比如回合开始判定,并以判定牌点数作为攻击范围),那在lua里面只用range是无法实现的。on_install:当该装备进入装备区时所执行的函数。这是装备牌的核心成员,有了它,才能赋予卡牌对应的装备技能。后面我们将会详细地说明一些典型的用法。on_uninstall:当该装备离开装备区时所执行的函数。比如【白银狮子】的回复体力效果。然后以下是防具的创建形式:<armor> = sgs.CreateArmor{ name, class_name, suit, number, on_install, on_uninstall,}与武器牌几乎一样,只不过少了个range而已。骑乘牌没有对应的创建函数,但是可以通过另外的方式来创建。其创建的代码如下:local <horse> = sgs.Sanguosha:cloneCard("DefensiveHorse", <suit>, <number>)<horse>:setObjectName("name")这是创建防御马(也就是常说的+1马)的代码,如果创建-1马,那么就把"DefensiveHorse"换成"OffensiveHorse"就可以了。为装备牌赋予技能 和武将技能类似,装备技能也分为视为技(如丈八蛇矛)、触发技(如官方包的所有防具)、手牌上限技(民间包的【圣光白衣】)、距离技、禁止技等。 由于手牌上限技、距离技、禁止技本身就是全局技能,所以无需在创建函数中再写入相关代码。 如【圣光白衣】的手牌上限+2效果:light_coatKeep = sgs.CreateMaxCardsSkill{ name = "lightcoatKeep", extra_func = function(self, target) local armor = target:getArmor() if armor and armor:isKindOf("LightCoat") then return 2 end end}记得,因为这些技能并未给予任何武将,所以技能都需要添加进sgs.Sanguosha中,才能有效。圣光白衣的类名为LightCoat,便能让所有在装备区装有该装备的角色手牌上限+2。如果是武器,那条件就是target:getWeapon() and target:getWeapon():isKindOf(<class_name>)或target:getWeapon () and target: getWeapon ():objectName() == <name>但如果是防御马,那就只能是target:getDefensiveHorse() and target: getDefensiveHorse():objectName() == <name>为什么不用isKindOf(),这是因为所有的防御马的class_name都是"DefensiveHorse",所以isKindOf()在这里不起作用。其次是触发技。虽然可以通过global=true来实现,但其实有一种方法可以减少运行时候的计算量,这是通过以下的语句实现的:on_install = function(self,player) local room = player:getRoom() local skill = sgs.Sanguosha:getTriggerSkill("skillname") room:getThread():addTriggerSkill(skill)end那么,只有当一名玩家装备了该装备,系统才会开始执行这个触发技。这样做的话即使没有global=true也会生效。注意技能仍旧需要加进sgs.Sanguosha中。最后是视为技和锁定视为技。视为技不仅要在on_install中执行,也要在on_uninstall中设置。如下所示:on_install = function(self,player) local room = player:getRoom() room:attachSkillToPlayer(player, "skillname")end,on_uninstall = function(self,player) local room = player:getRoom() room:detachSkillFromPlayer(player, "skillname")end,这样做时,如果skillname和装备牌的名称相同,则右下方不会出现相应的技能按钮,但是可以通过直接点击装备牌来发动技能。如果不同的话,那么就只能通过点击右下方的技能按钮来进行了。锁定视为技也是类似的,只不过没有点击发动的动作。对于骑乘的触发技,就只有通过global成员来进行了。而视为技和锁定视为技的添加,则也需要通过触发技来实现。亦即事件为sgs.CardsMoveOneTime,在move.from_places中骑乘对应的位置,以及move.to为sgs.Player_PlaceEquip的时候分别失去和添加技能,这两者就相当于on_uninstall和on_install。如此的触发技如下:<skill> = sgs.CreateTriggerSkill{name = "skillname",events = sgs.CardsMoveOneTime,global = true,can_trigger = function(self, target) return targetend,on_trigger = function(self, event, player, data) local move = data:toMoveOneTime() local l = move.card_ids:length() - 1 if move.from andmove.from:objectName() == player:objectName() then for i=0, l, 1 do if move.from_places:at(i) == sgs.Player_PlaceEquip then local card = sgs.Sanguosha:getCard(move.card_ids:at(i)) if card:objectName() == "horsename" then --填失去骑乘horse时候执行的内容,注意目标为player end end end end if move.to andmove.to:objectName() == player:objectName() then if move.to_place == sgs.Player_PlaceEquip then for i=0, l, 1 do local card = sgs.Sanguosha:getCard(move.card_ids:at(i)) if card:objectName() == "horsename" then --填装入骑乘horse时候执行的内容,注意目标为player end end end endend}如果有多个骑乘需要加效果,那么最好使用同一个触发技,而在判断card的objectName的时候产生分支,这样可以提高性能。事实上,通过这种方式所写的触发可以有比on_install/uninstall更强大的效果,只不过后者比较简易而已。

© 手抄报圈