太陽神三國殺Lua編寫高階教程?

太陽神三國殺,是一款基於C++ QT GUI框架的三國殺非官方開源軟體,開發者:Moligaloo(太陽神上),現由Mogara團隊,繼續更新。擁有智慧AI可以實現聯機和單機的兩種遊戲方式,並能通過DIY介面進行自由的個性化修改和新增更多元素。本經驗教你編寫屬於自己的lua!(基礎教程)適用於1217(V2)版。

本教程廢話不多說,直接程式碼!

ps:手牌教程。

工具/原料

太陽神三國殺

Notepad ++

步驟

製作一張基本牌的結構為:

= sgs.CreateBasicCard{

name,

class_name,

subtype,

target_fixed,

can_recast,

suit,

number,

filter,

feasible,

available,

about_to_use,

on_use,

on_effect,

}

下面介紹每個成員的含義。

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)。

製作一張類似“殺”可以主動使用的基本牌。

模板:

= sgs.CreateBasicCard{

name = "cardname",

class_name = "classname",

subtype = "attack_card",

target_fixed = false,

can_recast = false,

suit = ,

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,

我說幾點跟錦囊牌程式設計不同的地方。

首先,注意on_use和on_effect。在編寫錦囊牌的時候,因為幾乎沒有哪個技能有“錦囊牌對你無效”之類的語句,所以兩者可以隨便互換用。但是現在編寫的不是錦囊牌,而是基本牌,所以要考慮卡牌生效的問題了。於是,我們需要這樣子寫:

on_use = function(self, room, source, targets)

--在對所有目標生效前執行的語句

...

–-注意,這裡的 不一定就是上面的targets引數

for _, target in ipairs( ) do

room:cardEffect(self, source, target)

end

--在對所有目標生效後執行的語句

...

end,

on_effect = function(self, effect)

--對每名目標的效果

...

end,

製作一張【閃】

因為它只能在【殺】或【萬箭齊發】需要響應的時候打出,反倒是在出牌階段不能使用。

程式碼:

= sgs.CreateBasicCard{

name = "cardname",

class_name = "Jink",

subtype = "defense_card",

target_fixed = true,

can_recast = false,

suit = ,

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就不夠了,還得使用一個觸發技來實現額外的效果。

製作【殺】類卡牌

說到【殺】類卡牌,我就要在這裡額外談論一段了。因為【殺】不同於其他的基本牌,它有專門的觸發事件。也就是sgs.SlashEffect,sgs.SlashEffected,sgs.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

其含義是“將這張卡牌當做一張普通的【殺】來使用”。至於這張【殺】會造成什麼樣的效果,就只有靠另外的一個觸發技能了。觸發技能全文如下:

= 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( )

end

sgs.Sanguosha:addSkills(skillList)

用以上的方式所寫的卡牌,可以觸發【殺】類的事件。但是會存在其他的問題,就是在使用的時候會顯示“玩家將XX當做【殺】使用”。

單體錦囊牌基本的上個教程已經說了,所以來製作延時錦囊。

當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)。

製作一張裝備牌

相比之下,裝備牌的程式碼就不是特別複雜了,因為許多東西是已經預定好了的。

首先我們看武器:

= sgs.CreateWeapon{

name,

class_name,

suit,

number,

range,

on_install,

on_uninstall,

}

其中一些成員已經在前面介紹過了,我們說一下與之前不同的成員含義。

range:整數型別,顧名思義,是用來指定武器攻擊範圍的。值為1,就是攻擊範圍為1,以此類推。不要考慮什麼動態的攻擊範圍(比如回合開始判定,並以判定牌點數作為攻擊範圍),那在lua裡面只用range是無法實現的。

on_install:當該裝備進入裝備區時所執行的函式。這是裝備牌的核心成員,有了它,才能賦予卡牌對應的裝備技能。後面我們將會詳細地說明一些典型的用法。

on_uninstall:當該裝備離開裝備區時所執行的函式。比如【白銀獅子】的回覆體力效果。

然後以下是防具的建立形式:

= sgs.CreateArmor{

name,

class_name,

suit,

number,

on_install,

on_uninstall,

}

與武器牌幾乎一樣,只不過少了個range而已。

騎乘牌沒有對應的建立函式,但是可以通過另外的方式來建立。

其建立的程式碼如下:

local = sgs.Sanguosha:cloneCard("DefensiveHorse", , )

: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( )

或target:getWeapon () and target: getWeapon ():objectName() ==

但如果是防禦馬,那就只能是

target:getDefensiveHorse() and target: getDefensiveHorse():objectName() ==

為什麼不用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。

如此的觸發技如下:

= sgs.CreateTriggerSkill{

name = "skillname",

events = sgs.CardsMoveOneTime,

global = true,

can_trigger = function(self, target)

return target

end,

on_trigger = function(self, event, player, data)

local move = data:toMoveOneTime()

local l = move.card_ids:length() - 1

if move.from and

move.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 and

move.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

end

end

}

如果有多個騎乘需要加效果,那麼最好使用同一個觸發技,而在判斷card的objectName的時候產生分支,這樣可以提高效能。

事實上,通過這種方式所寫的觸發可以有比on_install/uninstall更強大的效果,只不過後者比較簡易而已。

注意事項

不要使用記事本,NOTEPAD++最好!!

相關問題答案