太陽神三國殺,是一款基於C++ QT GUI框架的三國殺非官方開源軟體,開發者:Moligaloo(太陽神上),現由Mogara團隊,繼續更新。擁有智慧AI可以實現聯機和單機的兩種遊戲方式,並能通過DIY介面進行自由的個性化修改和新增更多元素。本經驗教你編寫屬於自己的lua!(基礎教程)適用於1217(V2)版。
本教程廢話不多說,直接程式碼!
ps:手牌教程。
工具/原料
太陽神三國殺
Notepad ++
步驟
製作一張基本牌的結構為:
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)。
製作一張類似“殺”可以主動使用的基本牌。
模板:
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)
--在對所有目標生效前執行的語句
...
–-注意,這裡的
for _, target in ipairs(
room:cardEffect(self, source, target)
end
--在對所有目標生效後執行的語句
...
end,
on_effect = function(self, effect)
--對每名目標的效果
...
end,
製作一張【閃】
因為它只能在【殺】或【萬箭齊發】需要響應的時候打出,反倒是在出牌階段不能使用。
程式碼:
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
其含義是“將這張卡牌當做一張普通的【殺】來使用”。至於這張【殺】會造成什麼樣的效果,就只有靠另外的一個觸發技能了。觸發技能全文如下:
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)。
製作一張裝備牌
相比之下,裝備牌的程式碼就不是特別複雜了,因為許多東西是已經預定好了的。
首先我們看武器:
name,
class_name,
suit,
number,
range,
on_install,
on_uninstall,
}
其中一些成員已經在前面介紹過了,我們說一下與之前不同的成員含義。
range:整數型別,顧名思義,是用來指定武器攻擊範圍的。值為1,就是攻擊範圍為1,以此類推。不要考慮什麼動態的攻擊範圍(比如回合開始判定,並以判定牌點數作為攻擊範圍),那在lua裡面只用range是無法實現的。
on_install:當該裝備進入裝備區時所執行的函式。這是裝備牌的核心成員,有了它,才能賦予卡牌對應的裝備技能。後面我們將會詳細地說明一些典型的用法。
on_uninstall:當該裝備離開裝備區時所執行的函式。比如【白銀獅子】的回覆體力效果。
然後以下是防具的建立形式:
name,
class_name,
suit,
number,
on_install,
on_uninstall,
}
與武器牌幾乎一樣,只不過少了個range而已。
騎乘牌沒有對應的建立函式,但是可以通過另外的方式來建立。
其建立的程式碼如下:
local
這是建立防禦馬(也就是常說的+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。
如此的觸發技如下:
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++最好!!