Merge pull request #1 from luazyxs/develop

Develop
This commit is contained in:
luazyxs 2023-09-23 14:13:45 +08:00 committed by GitHub
commit 044d81b622
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 5710 additions and 3171 deletions

View File

@ -139,7 +139,7 @@ Item {
Text { Text {
width: parent.width width: parent.width
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: Backend.translate("Room List").arg(roomModel.count) text: Backend.translate("Room List")
} }
ListView { ListView {
id: roomList id: roomList

View File

@ -59,7 +59,7 @@ Item {
id: bgm id: bgm
source: config.bgmFile source: config.bgmFile
loops: MediaPlayer.Infinite // loops: MediaPlayer.Infinite
onPlaybackStateChanged: { onPlaybackStateChanged: {
if (playbackState == MediaPlayer.StoppedState && roomScene.isStarted) if (playbackState == MediaPlayer.StoppedState && roomScene.isStarted)
play(); play();

View File

@ -1070,31 +1070,6 @@ callbacks["AskForCardsChosen"] = (jsonData) => {
}); });
} }
callbacks["AskForPoxi"] = (jsonData) => {
const { type, data } = JSON.parse(jsonData);
roomScene.state = "replying";
roomScene.popupBox.sourceComponent = Qt.createComponent("../RoomElement/PoxiBox.qml");
const box = roomScene.popupBox.item;
box.poxi_type = type;
box.card_data = data;
for (let d of data) {
const arr = [];
const ids = d[1];
ids.forEach(id => {
const card_data = JSON.parse(Backend.callLuaFunction("GetCardData", [id]));
arr.push(card_data);
});
box.addCustomCards(d[0], arr);
}
roomScene.popupBox.moveToCenter();
box.cardsSelected.connect((ids) => {
replyToServer(JSON.stringify(ids));
});
}
callbacks["AskForMoveCardInBoard"] = (jsonData) => { callbacks["AskForMoveCardInBoard"] = (jsonData) => {
const data = JSON.parse(jsonData); const data = JSON.parse(jsonData);
const { cards, cardsPosition, generalNames, playerIds } = data; const { cards, cardsPosition, generalNames, playerIds } = data;

View File

@ -62,12 +62,7 @@ Item {
} }
if (mark_name.startsWith('@$')) { if (mark_name.startsWith('@$')) {
let data = mark_extra.split(','); params.cardNames = mark_extra.split(',');
if (!Object.is(parseInt(data[0]), NaN)) {
params.ids = data.map(s => parseInt(s));
} else {
params.cardNames = data;
}
} else { } else {
let data = JSON.parse(Backend.callLuaFunction("GetPile", [root.parent.playerid, mark_name])); let data = JSON.parse(Backend.callLuaFunction("GetPile", [root.parent.playerid, mark_name]));
data = data.filter((e) => e !== -1); data = data.filter((e) => e !== -1);

View File

@ -31,13 +31,11 @@ Item {
property string color: "" // only use when suit is empty property string color: "" // only use when suit is empty
property string footnote: "" // footnote, e.g. "A use card to B" property string footnote: "" // footnote, e.g. "A use card to B"
property bool footnoteVisible: false property bool footnoteVisible: false
property string prohibitReason: ""
property bool known: true // if false it only show a card back property bool known: true // if false it only show a card back
property bool enabled: true // if false the card will be grey property bool enabled: true // if false the card will be grey
property alias card: cardItem property alias card: cardItem
property alias glow: glowItem property alias glow: glowItem
property var mark: ({}) property var mark: ({})
property alias chosenInBox: chosen.visible
function getColor() { function getColor() {
if (suit != "") if (suit != "")
@ -90,7 +88,6 @@ Item {
visible: false visible: false
} }
Image { Image {
id: cardItem id: cardItem
source: known ? SkinBank.getCardPicture(cid || name) source: known ? SkinBank.getCardPicture(cid || name)
@ -220,15 +217,6 @@ Item {
} }
} }
Image {
id: chosen
visible: false
source: SkinBank.CARD_DIR + "chosen"
anchors.horizontalCenter: parent.horizontalCenter
y: 90
scale: 1.25
}
Rectangle { Rectangle {
visible: !root.selectable visible: !root.selectable
anchors.fill: parent anchors.fill: parent
@ -236,24 +224,6 @@ Item {
opacity: 0.7 opacity: 0.7
} }
Text {
id: prohibitText
visible: !root.selectable
anchors.centerIn: parent
font.family: fontLibian.name
font.pixelSize: 18
opacity: 0.9
horizontalAlignment: Text.AlignHCenter
lineHeight: 18
lineHeightMode: Text.FixedHeight
color: "snow"
width: 20
wrapMode: Text.WrapAnywhere
style: Text.Outline
styleColor: "red"
text: prohibitReason
}
TapHandler { TapHandler {
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.NoButton acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.NoButton
gesturePolicy: TapHandler.WithinBounds gesturePolicy: TapHandler.WithinBounds

View File

@ -166,35 +166,17 @@ RowLayout {
const ids = []; const ids = [];
let cards = handcardAreaItem.cards; let cards = handcardAreaItem.cards;
for (let i = 0; i < cards.length; i++) { for (let i = 0; i < cards.length; i++) {
cards[i].prohibitReason = "";
if (cardValid(cards[i].cid, cname)) { if (cardValid(cards[i].cid, cname)) {
ids.push(cards[i].cid); ids.push(cards[i].cid);
} else {
const prohibitReason = Backend.callLuaFunction(
"GetCardProhibitReason",
[cards[i].cid, roomScene.respond_play ? "response" : "use", cname]
);
if (prohibitReason) {
cards[i].prohibitReason = prohibitReason;
}
} }
} }
cards = self.equipArea.getAllCards(); cards = self.equipArea.getAllCards();
cards.forEach(c => { cards.forEach(c => {
c.prohibitReason = "";
if (cardValid(c.cid, cname)) { if (cardValid(c.cid, cname)) {
ids.push(c.cid); ids.push(c.cid);
if (!expanded_piles["_equip"]) { if (!expanded_piles["_equip"]) {
expandPile("_equip"); expandPile("_equip");
} }
} else {
const prohibitReason = Backend.callLuaFunction(
"GetCardProhibitReason",
[c.cid, roomScene.respond_play ? "response" : "use", cname]
);
if (prohibitReason) {
c.prohibitReason = prohibitReason;
}
} }
}); });
@ -220,7 +202,6 @@ RowLayout {
const ids = [], cards = handcardAreaItem.cards; const ids = [], cards = handcardAreaItem.cards;
for (let i = 0; i < cards.length; i++) { for (let i = 0; i < cards.length; i++) {
cards[i].prohibitReason = "";
if (JSON.parse(Backend.callLuaFunction("CanUseCard", [cards[i].cid, Self.id]))) { if (JSON.parse(Backend.callLuaFunction("CanUseCard", [cards[i].cid, Self.id]))) {
ids.push(cards[i].cid); ids.push(cards[i].cid);
} else { } else {
@ -233,14 +214,6 @@ RowLayout {
break; break;
} }
} }
// still cannot use? show message on card
if (!ids.includes(cards[i].cid)) {
const prohibitReason = Backend.callLuaFunction("GetCardProhibitReason", [cards[i].cid, "play"]);
if (prohibitReason) {
cards[i].prohibitReason = prohibitReason;
}
}
} }
} }
handcardAreaItem.enableCards(ids) handcardAreaItem.enableCards(ids)
@ -399,11 +372,6 @@ RowLayout {
item.enabled = item.pressed; item.enabled = item.pressed;
} }
const cards = handcardAreaItem.cards;
for (let i = 0; i < cards.length; i++) {
cards[i].prohibitReason = "";
}
updatePending(); updatePending();
} }

View File

@ -48,7 +48,6 @@ Item {
card.selectable = false; card.selectable = false;
card.showDetail = false; card.showDetail = false;
card.selectedChanged.disconnect(adjustCards); card.selectedChanged.disconnect(adjustCards);
card.prohibitReason = "";
} }
return result; return result;
} }

View File

@ -78,10 +78,10 @@ GraphicsBox {
} }
onSelectedChanged: { onSelectedChanged: {
if (selected) { if (selected) {
chosenInBox = true; virt_name = "$Selected";
root.selected_ids.push(cid); root.selected_ids.push(cid);
} else { } else {
chosenInBox = false; virt_name = "";
root.selected_ids.splice(root.selected_ids.indexOf(cid), 1); root.selected_ids.splice(root.selected_ids.indexOf(cid), 1);
} }
root.selected_ids = root.selected_ids; root.selected_ids = root.selected_ids;
@ -122,6 +122,38 @@ GraphicsBox {
return ret; return ret;
} }
function addHandcards(cards) {
let handcards = findAreaModel('$Hand').areaCards;
if (cards instanceof Array) {
for (let i = 0; i < cards.length; i++)
handcards.append(cards[i]);
} else {
handcards.append(cards);
}
}
function addEquips(cards)
{
let equips = findAreaModel('$Equip').areaCards;
if (cards instanceof Array) {
for (let i = 0; i < cards.length; i++)
equips.append(cards[i]);
} else {
equips.append(cards);
}
}
function addDelayedTricks(cards)
{
let delayedTricks = findAreaModel('$Judge').areaCards;
if (cards instanceof Array) {
for (let i = 0; i < cards.length; i++)
delayedTricks.append(cards[i]);
} else {
delayedTricks.append(cards);
}
}
function addCustomCards(name, cards) { function addCustomCards(name, cards) {
let area = findAreaModel(name).areaCards; let area = findAreaModel(name).areaCards;
if (cards instanceof Array) { if (cards instanceof Array) {

View File

@ -23,17 +23,9 @@ Item {
x: -13 - 120 * 0.166 x: -13 - 120 * 0.166
y: -6 - 55 * 0.166 y: -6 - 55 * 0.166
scale: 0.66 scale: 0.66
source: { source: type === "notactive" ? ""
if (type === "notactive") { : AppPath + "/image/button/skill/" + type + "/"
return ""; + (enabled ? (pressed ? "pressed" : "normal") : "disabled")
}
let ret = AppPath + "/image/button/skill/" + type + "/";
let suffix = enabled ? (pressed ? "pressed" : "normal") : "disabled";
if (enabled && type === "active" && orig.endsWith("&")) {
suffix += "-attach";
}
return ret + suffix;
}
} }
Image { Image {

View File

@ -63,9 +63,3 @@ ___
## 许可证 ## 许可证
本仓库使用GPLv3作为许可证。详见`LICENSE`文件。 本仓库使用GPLv3作为许可证。详见`LICENSE`文件。
___
## 点一下小星星呗!
[![Star History Chart](https://api.star-history.com/svg?repos=Qsgs-Fans/FreeKill&type=Date)](https://star-history.com/#Qsgs-Fans/FreeKill&Date)

14
lua/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,14 @@
{
"editor.renderLineHighlight": "none",
"Lua.diagnostics.disable": [
"undefined-field",
"inject-field",
"return-type-mismatch",
"cast-local-type",
"param-type-mismatch",
"invisible",
"missing-fields",
"assign-type-mismatch",
"undefined-doc-name"
]
}

View File

@ -1,14 +1,14 @@
-- SPDX-License-Identifier: GPL-3.0-or-later -- SPDX-License-Identifier: GPL-3.0-or-later
-- All functions in this file are used by Qml -- All functions in this file are used by Qml
function Translate(src) function Translate(src)
return Fk:translate(src) return Fk:translate(src)
end end
function GetGeneralData(name) function GetGeneralData(name)
local general = Fk.generals[name] local general = Fk.generals[name]
if general == nil then general = Fk.generals["diaochan"] end if general == nil then
general = Fk.generals["diaochan"]
end
return json.encode { return json.encode {
package = general.package.name, package = general.package.name,
extension = general.package.extensionName, extension = general.package.extensionName,
@ -18,13 +18,15 @@ function GetGeneralData(name)
maxHp = general.maxHp, maxHp = general.maxHp,
shield = general.shield, shield = general.shield,
hidden = general.hidden, hidden = general.hidden,
total_hidden = general.total_hidden, total_hidden = general.total_hidden
} }
end end
function GetGeneralDetail(name) function GetGeneralDetail(name)
local general = Fk.generals[name] local general = Fk.generals[name]
if general == nil then general = Fk.generals["diaochan"] end if general == nil then
general = Fk.generals["diaochan"]
end
local ret = { local ret = {
package = general.package.name, package = general.package.name,
extension = general.package.extensionName, extension = general.package.extensionName,
@ -79,20 +81,23 @@ local cardSubtypeStrings = {
[Card.SubtypeArmor] = "armor", [Card.SubtypeArmor] = "armor",
[Card.SubtypeDefensiveRide] = "defensive_horse", [Card.SubtypeDefensiveRide] = "defensive_horse",
[Card.SubtypeOffensiveRide] = "offensive_horse", [Card.SubtypeOffensiveRide] = "offensive_horse",
[Card.SubtypeTreasure] = "treasure", [Card.SubtypeTreasure] = "treasure"
} }
function GetCardData(id, virtualCardForm) function GetCardData(id, virtualCardForm)
local card = Fk:getCardById(id) local card = Fk:getCardById(id)
if card == nil then return json.encode{ if card == nil then
return json.encode {
cid = id, cid = id,
known = false known = false
} end }
end
local mark = {} local mark = {}
for k, v in pairs(card.mark) do for k, v in pairs(card.mark) do
if k and k:startsWith("@") and v and v ~= 0 then if k and k:startsWith("@") and v and v ~= 0 then
table.insert(mark, { table.insert(mark, {
k = k, v = v, k = k,
v = v
}) })
end end
end end
@ -162,7 +167,9 @@ end
function SearchGenerals(pack_name, word) function SearchGenerals(pack_name, word)
local ret = {} local ret = {}
if word == "" then return GetGenerals(pack_name) end if word == "" then
return GetGenerals(pack_name)
end
for _, g in ipairs(Fk.packages[pack_name].generals) do for _, g in ipairs(Fk.packages[pack_name].generals) do
if not g.total_hidden and string.find(Fk:translate(g.name), word) then if not g.total_hidden and string.find(Fk:translate(g.name), word) then
table.insert(ret, g.name) table.insert(ret, g.name)
@ -190,8 +197,7 @@ function GetAvailableGeneralsNum()
local availableGenerals = {} local availableGenerals = {}
for _, general in pairs(generalPool) do for _, general in pairs(generalPool) do
if not table.contains(except, general.name) then if not table.contains(except, general.name) then
if (not general.hidden and not general.total_hidden) and if (not general.hidden and not general.total_hidden) and #table.filter(availableGenerals, function(g)
#table.filter(availableGenerals, function(g)
return g.trueName == general.trueName return g.trueName == general.trueName
end) == 0 then end) == 0 then
ret = ret + 1 ret = ret + 1
@ -243,7 +249,7 @@ function GetPlayerSkills(id)
return json.encode(table.map(p.player_skills, function(s) return json.encode(table.map(p.player_skills, function(s)
return s.visible and { return s.visible and {
name = s.name, name = s.name,
description = Fk:getDescription(s.name), description = Fk:getDescription(s.name)
} or nil } or nil
end)) end))
end end
@ -367,7 +373,9 @@ end
function GetSkillData(skill_name) function GetSkillData(skill_name)
local skill = Fk.skills[skill_name] local skill = Fk.skills[skill_name]
if not skill then return "null" end if not skill then
return "null"
end
local freq = "notactive" local freq = "notactive"
if skill:isInstanceOf(ActiveSkill) or skill:isInstanceOf(ViewAsSkill) then if skill:isInstanceOf(ActiveSkill) or skill:isInstanceOf(ViewAsSkill) then
freq = "active" freq = "active"
@ -380,14 +388,14 @@ function GetSkillData(skill_name)
elseif skill.frequency == Skill.Quest then elseif skill.frequency == Skill.Quest then
frequency = "quest" frequency = "quest"
end end
return json.encode{ return json.encode {
skill = Fk:translate(skill_name), skill = Fk:translate(skill_name),
orig_skill = skill_name, orig_skill = skill_name,
extension = skill.package.extensionName, extension = skill.package.extensionName,
freq = freq, freq = freq,
frequency = frequency, frequency = frequency,
switchSkillName = skill.switchSkillName, switchSkillName = skill.switchSkillName,
isViewAsSkill = skill:isInstanceOf(ViewAsSkill), isViewAsSkill = skill:isInstanceOf(ViewAsSkill)
} }
end end
@ -414,7 +422,9 @@ function ActiveCanUse(skill_name)
local c = Fk:cloneCard(n) local c = Fk:cloneCard(n)
c.skillName = skill_name c.skillName = skill_name
ret = c.skill:canUse(Self, c) ret = c.skill:canUse(Self, c)
if ret then break end if ret then
break
end
end end
end end
end end
@ -562,10 +572,12 @@ end
function GetVirtualEquip(player, cid) function GetVirtualEquip(player, cid)
local c = ClientInstance:getPlayerById(player):getVirualEquip(cid) local c = ClientInstance:getPlayerById(player):getVirualEquip(cid)
if not c then return "null" end if not c then
return json.encode{ return "null"
end
return json.encode {
name = c.name, name = c.name,
cid = c.subcards[1], cid = c.subcards[1]
} }
end end
@ -581,10 +593,12 @@ function GetGameModes()
name = Fk:translate(v.name), name = Fk:translate(v.name),
orig_name = v.name, orig_name = v.name,
minPlayer = v.minPlayer, minPlayer = v.minPlayer,
maxPlayer = v.maxPlayer, maxPlayer = v.maxPlayer
}) })
end end
table.sort(ret, function(a, b) return a.name > b.name end) table.sort(ret, function(a, b)
return a.name > b.name
end)
return json.encode(ret) return json.encode(ret)
end end
@ -604,20 +618,17 @@ function SetInteractionDataOfSkill(skill_name, data)
end end
function ChangeSelf(pid) function ChangeSelf(pid)
local c = ClientInstance ClientInstance.client:changeSelf(pid) -- for qml
c.client:changeSelf(pid) -- for qml Self = ClientInstance:getPlayerById(pid)
Self = c:getPlayerById(pid)
end end
function GetPlayerHandcards(pid) function GetPlayerHandcards(pid)
local c = ClientInstance local p = ClientInstance:getPlayerById(pid)
local p = c:getPlayerById(pid)
return json.encode(p.player_cards[Player.Hand]) return json.encode(p.player_cards[Player.Hand])
end end
function GetPlayerEquips(pid) function GetPlayerEquips(pid)
local c = ClientInstance local p = ClientInstance:getPlayerById(pid)
local p = c:getPlayerById(pid)
return json.encode(p.player_cards[Player.Equip]) return json.encode(p.player_cards[Player.Equip])
end end
@ -647,9 +658,10 @@ function GetRoomConfig()
end end
function GetPlayerGameData(pid) function GetPlayerGameData(pid)
local c = ClientInstance local p = ClientInstance:getPlayerById(pid)
local p = c:getPlayerById(pid) if not p then
if not p then return "[0, 0, 0]" end return "[0, 0, 0]"
end
local raw = p.player:getGameData() local raw = p.player:getGameData()
local ret = {} local ret = {}
for _, i in fk.qlist(raw) do for _, i in fk.qlist(raw) do
@ -659,8 +671,7 @@ function GetPlayerGameData(pid)
end end
function SetPlayerGameData(pid, data) function SetPlayerGameData(pid, data)
local c = ClientInstance local p = ClientInstance:getPlayerById(pid)
local p = c:getPlayerById(pid)
p.player:setGameData(table.unpack(data)) p.player:setGameData(table.unpack(data))
table.insert(data, 1, pid) table.insert(data, 1, pid)
ClientInstance:notifyUI("UpdateGameData", json.encode(data)) ClientInstance:notifyUI("UpdateGameData", json.encode(data))
@ -680,6 +691,10 @@ function CheckSurrenderAvailable(playedTime)
end end
function SaveRecord() function SaveRecord()
<<<<<<< HEAD
local c = ClientInstance
c.client:saveRecord(json.encode(c.record), c.record[2])
=======
local c = ClientInstance local c = ClientInstance
c.client:saveRecord(json.encode(c.record), c.record[2]) c.client:saveRecord(json.encode(c.record), c.record[2])
end end
@ -732,6 +747,7 @@ function PoxiFeasible(poxi_type, selected, data)
local poxi = Fk.poxi_methods[poxi_type] local poxi = Fk.poxi_methods[poxi_type]
if not poxi then return "false" end if not poxi then return "false" end
return json.encode(poxi.feasible(selected, data)) return json.encode(poxi.feasible(selected, data))
>>>>>>> 30b363ef775598c6c6510ceb17e70ca01bd2f677
end end
dofile "lua/client/i18n/init.lua" dofile "lua/client/i18n/init.lua"

View File

@ -2,7 +2,7 @@
Fk:loadTranslationTable({ Fk:loadTranslationTable({
-- Lobby -- Lobby
["Room List"] = "Room List (currently have %1 rooms)", -- ["Room List"] = "房间列表",
-- ["Enter"] = "进入", -- ["Enter"] = "进入",
-- ["Observe"] = "旁观", -- ["Observe"] = "旁观",

View File

@ -2,7 +2,7 @@
Fk:loadTranslationTable{ Fk:loadTranslationTable{
-- Lobby -- Lobby
["Room List"] = "房间列表 (共%1个房间)", ["Room List"] = "房间列表",
["Enter"] = "进入", ["Enter"] = "进入",
["Observe"] = "旁观", ["Observe"] = "旁观",

View File

@ -25,7 +25,6 @@
---@field public filtered_cards table<integer, Card> @ 被锁视技影响的卡牌 ---@field public filtered_cards table<integer, Card> @ 被锁视技影响的卡牌
---@field public printed_cards table<integer, Card> @ 被某些房间现场打印的卡牌id都是负数且从-2开始 ---@field public printed_cards table<integer, Card> @ 被某些房间现场打印的卡牌id都是负数且从-2开始
---@field private _custom_events any[] @ 自定义事件列表 ---@field private _custom_events any[] @ 自定义事件列表
---@field public poxi_methods table<string, PoxiSpec> @ “魄袭”框操作方法表
local Engine = class("Engine") local Engine = class("Engine")
--- Engine的构造函数。 --- Engine的构造函数。
@ -56,7 +55,6 @@ function Engine:initialize()
self.game_mode_disabled = {} self.game_mode_disabled = {}
self.kingdoms = {} self.kingdoms = {}
self._custom_events = {} self._custom_events = {}
self.poxi_methods = {}
self:loadPackages() self:loadPackages()
self:loadDisabled() self:loadDisabled()
@ -336,16 +334,6 @@ function Engine:addGameEvent(name, pfunc, mfunc, cfunc, efunc)
table.insert(self._custom_events, { name = name, p = pfunc, m = mfunc, c = cfunc, e = efunc }) table.insert(self._custom_events, { name = name, p = pfunc, m = mfunc, c = cfunc, e = efunc })
end end
---@param spec PoxiSpec
function Engine:addPoxiMethod(spec)
assert(type(spec.name) == "string")
assert(type(spec.card_filter) == "function")
assert(type(spec.feasible) == "function")
self.poxi_methods[spec.name] = spec
spec.default_choice = spec.default_choice or function() return {} end
spec.post_select = spec.post_select or function(s) return s end
end
--- 从已经开启的拓展包中,随机选出若干名武将。 --- 从已经开启的拓展包中,随机选出若干名武将。
--- ---
--- 对于同名武将不会重复选取。 --- 对于同名武将不会重复选取。

View File

@ -136,8 +136,10 @@ function Player:setGeneral(general, setHp, addSkills)
end end
function Player:getGeneralMaxHp() function Player:getGeneralMaxHp()
local general = Fk.generals[type(self:getMark("__heg_general")) == "string" and self:getMark("__heg_general") or self.general] local general = Fk.generals
local deputy = Fk.generals[type(self:getMark("__heg_deputy")) == "string" and self:getMark("__heg_deputy") or self.deputyGeneral] [type(self:getMark("__heg_general")) == "string" and self:getMark("__heg_general") or self.general]
local deputy = Fk.generals
[type(self:getMark("__heg_deputy")) == "string" and self:getMark("__heg_deputy") or self.deputyGeneral]
if not deputy then if not deputy then
return general.maxHp + general.mainMaxHpAdjustedValue return general.maxHp + general.mainMaxHpAdjustedValue
@ -329,12 +331,18 @@ end
function Player:getCardIds(playerAreas, specialName) function Player:getCardIds(playerAreas, specialName)
local rightAreas = { Player.Hand, Player.Equip, Player.Judge } local rightAreas = { Player.Hand, Player.Equip, Player.Judge }
playerAreas = playerAreas or rightAreas playerAreas = playerAreas or rightAreas
local cardIds = {}
if type(playerAreas) == "string" then if type(playerAreas) == "string" then
local str = playerAreas local str = playerAreas
playerAreas = {} playerAreas = {}
if str:find("h") then if str:find("h") then
table.insert(playerAreas, Player.Hand) table.insert(playerAreas, Player.Hand)
end end
if str:find("&") then
for k, v in pairs(self.special_cards) do
if k:endsWith("&") then table.insertTable(cardIds, v) end
end
end
if str:find("e") then if str:find("e") then
table.insert(playerAreas, Player.Equip) table.insert(playerAreas, Player.Equip)
end end
@ -346,7 +354,6 @@ function Player:getCardIds(playerAreas, specialName)
local areas = type(playerAreas) == "table" and playerAreas or { playerAreas } local areas = type(playerAreas) == "table" and playerAreas or { playerAreas }
local rightAreas = { Player.Hand, Player.Equip, Player.Judge, Player.Special } local rightAreas = { Player.Hand, Player.Equip, Player.Judge, Player.Special }
local cardIds = {}
for _, area in ipairs(areas) do for _, area in ipairs(areas) do
assert(table.contains(rightAreas, area)) assert(table.contains(rightAreas, area))
assert(area ~= Player.Special or type(specialName) == "string") assert(area ~= Player.Special or type(specialName) == "string")
@ -510,7 +517,8 @@ function Player:distanceTo(other, mode, ignore_dead)
if temp ~= other then if temp ~= other then
print("Distance malfunction: start and end does not matched.") print("Distance malfunction: start and end does not matched.")
end end
local left = #(ignore_dead and Fk:currentRoom().players or Fk:currentRoom().alive_players) - right - #table.filter(Fk:currentRoom().alive_players, function(p) return p:isRemoved() end) local left = #(ignore_dead and Fk:currentRoom().players or Fk:currentRoom().alive_players) - right -
#table.filter(Fk:currentRoom().alive_players, function(p) return p:isRemoved() end)
local ret = 0 local ret = 0
if mode == "left" then if mode == "left" then
ret = left ret = left
@ -586,7 +594,7 @@ function Player:addCardUseHistory(cardName, num)
num = num or 1 num = num or 1
assert(type(num) == "number" and num ~= 0) assert(type(num) == "number" and num ~= 0)
self.cardUsedHistory[cardName] = self.cardUsedHistory[cardName] or {0, 0, 0, 0} self.cardUsedHistory[cardName] = self.cardUsedHistory[cardName] or { 0, 0, 0, 0 }
local t = self.cardUsedHistory[cardName] local t = self.cardUsedHistory[cardName]
for i, _ in ipairs(t) do for i, _ in ipairs(t) do
t[i] = t[i] + num t[i] = t[i] + num
@ -623,7 +631,7 @@ function Player:addSkillUseHistory(skill_name, num)
num = num or 1 num = num or 1
assert(type(num) == "number" and num ~= 0) assert(type(num) == "number" and num ~= 0)
self.skillUsedHistory[skill_name] = self.skillUsedHistory[skill_name] or {0, 0, 0, 0} self.skillUsedHistory[skill_name] = self.skillUsedHistory[skill_name] or { 0, 0, 0, 0 }
local t = self.skillUsedHistory[skill_name] local t = self.skillUsedHistory[skill_name]
for i, _ in ipairs(t) do for i, _ in ipairs(t) do
t[i] = t[i] + num t[i] = t[i] + num
@ -648,7 +656,7 @@ function Player:setSkillUseHistory(skill_name, num, scope)
return return
end end
self.skillUsedHistory[skill_name] = self.skillUsedHistory[skill_name] or {0, 0, 0, 0} self.skillUsedHistory[skill_name] = self.skillUsedHistory[skill_name] or { 0, 0, 0, 0 }
self.skillUsedHistory[skill_name][scope] = num self.skillUsedHistory[skill_name][scope] = num
end end
@ -681,7 +689,7 @@ end
--- 获取玩家是否无手牌及装备牌。 --- 获取玩家是否无手牌及装备牌。
function Player:isNude() function Player:isNude()
return #self:getCardIds{Player.Hand, Player.Equip} == 0 return #self:getCardIds { Player.Hand, Player.Equip } == 0
end end
--- 获取玩家所有区域是否无牌。 --- 获取玩家所有区域是否无牌。
@ -731,7 +739,6 @@ function Player:hasSkill(skill, ignoreNullified, ignoreAlive)
if self:isInstanceOf(ServerPlayer) and -- isInstanceOf(nil) will return false if self:isInstanceOf(ServerPlayer) and -- isInstanceOf(nil) will return false
table.contains(self._fake_skills, skill) and table.contains(self._fake_skills, skill) and
table.contains(self.prelighted_skills, skill) then table.contains(self.prelighted_skills, skill) then
return true return true
end end
@ -751,7 +758,7 @@ end
function Player:addSkill(skill, source_skill) function Player:addSkill(skill, source_skill)
skill = getActualSkill(skill) skill = getActualSkill(skill)
local toget = {table.unpack(skill.related_skills)} local toget = { table.unpack(skill.related_skills) }
table.insert(toget, skill) table.insert(toget, skill)
local room = Fk:currentRoom() local room = Fk:currentRoom()
@ -824,7 +831,7 @@ end
--- 获取对应玩家所有技能。 --- 获取对应玩家所有技能。
-- return all skills that xxx:hasSkill() == true -- return all skills that xxx:hasSkill() == true
function Player:getAllSkills() function Player:getAllSkills()
local ret = {table.unpack(self.player_skills)} local ret = { table.unpack(self.player_skills) }
for _, t in pairs(self.derivative_skills) do for _, t in pairs(self.derivative_skills) do
for _, s in ipairs(t) do for _, s in ipairs(t) do
table.insertIfNeed(ret, s) table.insertIfNeed(ret, s)
@ -844,8 +851,7 @@ end
---@param to Player @ 特定玩家 ---@param to Player @ 特定玩家
---@param card Card @ 特定牌 ---@param card Card @ 特定牌
function Player:isProhibited(to, card) function Player:isProhibited(to, card)
local r = Fk:currentRoom() if type(card) == "number" then card = Fk:getCardById(card) end
if card.type == Card.TypeEquip and #to:getAvailableEquipSlots(card.sub_type) == 0 then if card.type == Card.TypeEquip and #to:getAvailableEquipSlots(card.sub_type) == 0 then
return true return true
end end
@ -855,7 +861,12 @@ function Player:isProhibited(to, card)
return true return true
end end
local status_skills = r.status_skills[ProhibitSkill] or Util.DummyTable if fk.useMustTargets and
not table.contains(fk.useMustTargets, to.id) then
return true
end
local status_skills = Fk:currentRoom().status_skills[ProhibitSkill] or Util.DummyTable
for _, skill in ipairs(status_skills) do for _, skill in ipairs(status_skills) do
if skill:isProhibited(self, to, card) then if skill:isProhibited(self, to, card) then
return true return true
@ -928,9 +939,11 @@ fk.SwitchYin = 1
---@return number|string @ 转换技状态 ---@return number|string @ 转换技状态
function Player:getSwitchSkillState(skillName, afterUse, inWord) function Player:getSwitchSkillState(skillName, afterUse, inWord)
if afterUse then if afterUse then
return self:getMark(MarkEnum.SwithSkillPreName .. skillName) < 1 and (inWord and "yin" or fk.SwitchYin) or (inWord and "yang" or fk.SwitchYang) return self:getMark(MarkEnum.SwithSkillPreName .. skillName) < 1 and (inWord and "yin" or fk.SwitchYin) or
(inWord and "yang" or fk.SwitchYang)
else else
return self:getMark(MarkEnum.SwithSkillPreName .. skillName) < 1 and (inWord and "yang" or fk.SwitchYang) or (inWord and "yin" or fk.SwitchYin) return self:getMark(MarkEnum.SwithSkillPreName .. skillName) < 1 and (inWord and "yang" or fk.SwitchYang) or
(inWord and "yin" or fk.SwitchYin)
end end
end end

View File

@ -588,13 +588,3 @@ function fk.CreateGameMode(spec)
end end
return ret return ret
end end
-- other
---@class PoxiSpec
---@field name string
---@field card_filter fun(to_select: int, selected: int[], data: any): bool
---@field feasible fun(selected: int[], data: any): bool
---@field post_select nil | fun(selected: int[], data: any): int[]
---@field default_choice nil | fun(data: any): int[]
---@field prompt nil | string | fun(data: any): string

View File

@ -7,7 +7,6 @@
---@alias null nil ---@alias null nil
---@alias bool boolean | nil ---@alias bool boolean | nil
---@alias int integer
---@class fk ---@class fk
---FreeKill's lua API ---FreeKill's lua API

View File

@ -0,0 +1,45 @@
拓展包ai文件写法
按照包文件夹名+_ai例如standard_cards是standard_cards_ai.lua直接放在init.lua同位置下。
分辨敌我:
设置反值内值同时设置技能和牌的身份值例如杀的值为100主公或跳身份的忠被杀时来源反值+100当一名角色反值大于50时将识别为反贼当跳反者杀反贼或跳忠者杀主忠他内值就+100内值大于50的将被识别为内奸当然标记身份是以最高值开始标记身份例如场上两名角色为400和300的最高反值但是反贼只剩下一个那么就只将400的标记为反贼其余身份也是如此。优先针对虚弱相同虚弱再优先仇恨。
要让身份值加给角色就需要在使用技能或牌时接入ai接口所以我在触发时机函数的最后加了接口用于获取此时触发时机的数据例如1号位杀2号位此时触发了指定目标时机就可以通过接口接入ai然后让来源反值+100。但是N神不想这样增加接口但我还不了解怎么通过其他方式同步获取使用技能或牌的数据......
空闲点ai出牌
先定义阶段技或卡牌的优先度然后获取角色所有可用的阶段技和卡牌按照优先度进行排序再进行for列表逐一检测是否可使用可使用则检测是否有使用函数有则执行使用函数在使用函数里进行定义self.use_id和给self.use_tos添加角色id然后系统检测有self.use_id则输出使用如果是使用卡则self.use_id=卡id阶段技是self.use_id=子卡表{})同时可以给self.use_tos添加角色id做为牌或技能的目标会同步输出。
请求ai使用牌
包含请求无懈,请求桃,借刀请求杀等
请求无懈有正无懈和反无懈系统根据生效锦囊牌名定位请求代码之后可以增加来源和目标技能名来修正是否使用例如要通过伤害锦囊卖血时可以通过技能名阻止友方使用无懈同时请求代码会带有正反无懈的参数根据这个参数进行分类讨论请求代码需定义self.use_id为将要使用的无懈id当然也可以定义self.use_id=true这样就会根据卡牌优先度选择第一个无懈使用。因为要区分正反无懈我在room里的请求无懈时增加了fk.askNullification和fk.askNullificationData前者用来区分正反无懈后者记录要无懈的锦囊数据因为直接获取使用数据可能会是上个无懈的数据而不是最初锦囊的数据当然我并不知道其他的识别正反无懈和获取源锦囊生效数据的方法只能是手动增加记录的这个样子来实现.....
请求桃默认给友军使用,但是可以通过来源和目标的技能来修正来源是否要使用桃
剩下的请求牌根据提示信息名进行决策,兜底决策是套用空闲点出牌代码,所以说请求使用牌和空闲点使用牌的的函数是相通的,如果请求使用牌没有定义函数,就会调用空闲点使用牌的函数来兜底。
技能转化牌:
急救桃、倾国闪等
也是通过技能名定位转化代码也需要定义self.use_id为技能子卡表{}就行,如果没有子卡就定义空表{},如果不定义就表示不使用转化技。
请求ai打出牌
杀响应决斗南蛮等
根据牌名或技能名来分别决策同时优先以技能名决策用于卖血技兜底决策是默认不响应因为有部分是技能请求响应例如鬼才改判打出。响应牌依旧是将要响应的卡的id定义为self.use_id懒得再定义个新参数
请求ai弃置牌
给每个牌名定义保留值,然后排序并优先弃置低保留值的牌。
请求ai发动技能
直接按照技能名分别决策。
技能请求选择角色是给self.use_tos添加目标id例如突袭
既请求选择角色又请求选择牌就再增加定义self.use_id为技能子卡表{}
请求ai选择角色区域牌
按照提示信息名分别决策,同时设置兜底决策,对敌军优先选择其重要的牌。
依旧是定义self.use_id为技能子卡表{}不论选择一张牌还是多张牌都是将牌id添加到表然后定义给self.use_id。
请求ai选择选项
按照提示信息名分别决策,同时兜底决策是随机选择。
这个就直接返回需要选择的选项就行。

View File

@ -4,24 +4,3 @@ AI = require "server.ai.ai"
TrustAI = require "server.ai.trust_ai" TrustAI = require "server.ai.trust_ai"
RandomAI = require "server.ai.random_ai" RandomAI = require "server.ai.random_ai"
SmartAI = require "server.ai.smart_ai" SmartAI = require "server.ai.smart_ai"
-- load ai module from packages
local directories = FileIO.ls("packages")
require "packages.standard.ai"
require "packages.standard_cards.ai"
require "packages.maneuvering.ai"
table.removeOne(directories, "standard")
table.removeOne(directories, "standard_cards")
table.removeOne(directories, "maneuvering")
local _disable_packs = json.decode(fk.GetDisabledPacks())
for _, dir in ipairs(directories) do
if (not string.find(dir, ".disabled")) and not table.contains(_disable_packs, dir)
and FileIO.isDir("packages/" .. dir)
and FileIO.exists("packages/" .. dir .. "/ai/init.lua") then
require(string.format("packages.%s.ai", dir))
end
end

View File

@ -1,5 +1,4 @@
-- SPDX-License-Identifier: GPL-3.0-or-later -- SPDX-License-Identifier: GPL-3.0-or-later
---@class RandomAI: AI ---@class RandomAI: AI
local RandomAI = AI:subclass("RandomAI") local RandomAI = AI:subclass("RandomAI")
@ -12,7 +11,9 @@ local function useActiveSkill(self, skill, card)
local filter_func = skill.cardFilter local filter_func = skill.cardFilter
if card then if card then
filter_func = function() return false end filter_func = function()
return false
end
end end
if self.command == "PlayCard" and (not skill:canUse(player, card) or player:prohibitUse(card)) then if self.command == "PlayCard" and (not skill:canUse(player, card) or player:prohibitUse(card)) then
@ -27,9 +28,11 @@ local function useActiveSkill(self, skill, card)
local min_card = skill:getMinCardNum() local min_card = skill:getMinCardNum()
local max_card = skill:getMaxCardNum() local max_card = skill:getMaxCardNum()
for _ = 0, max_try_times do for _ = 0, max_try_times do
if skill:feasible(selected_targets, selected_cards, self.player, card) then break end if skill:feasible(selected_targets, selected_cards, self.player, card) then
break
end
local avail_targets = table.filter(self.room:getAlivePlayers(), function(p) local avail_targets = table.filter(self.room:getAlivePlayers(), function(p)
local ret = skill:targetFilter(p.id, selected_targets, selected_cards, card or Fk:cloneCard'zixing') local ret = skill:targetFilter(p.id, selected_targets, selected_cards, card or Fk:cloneCard 'zixing')
if ret and card then if ret and card then
if player:prohibitUse(card) then if player:prohibitUse(card) then
ret = false ret = false
@ -37,22 +40,26 @@ local function useActiveSkill(self, skill, card)
end end
return ret return ret
end) end)
avail_targets = table.map(avail_targets, function(p) return p.id end) avail_targets = table.map(avail_targets, function(p)
local avail_cards = table.filter(player:getCardIds{ Player.Hand, Player.Equip }, function(id) return p.id
end)
local avail_cards = table.filter(player:getCardIds{Player.Hand, Player.Equip}, function(id)
return filter_func(skill, id, selected_cards, selected_targets) return filter_func(skill, id, selected_cards, selected_targets)
end) end)
if #avail_targets == 0 and #avail_cards == 0 then break end if #avail_targets == 0 and #avail_cards == 0 then
break
end
table.insertIfNeed(selected_targets, table.random(avail_targets)) table.insertIfNeed(selected_targets, table.random(avail_targets))
table.insertIfNeed(selected_cards, table.random(avail_cards)) table.insertIfNeed(selected_cards, table.random(avail_cards))
end end
if skill:feasible(selected_targets, selected_cards, self.player, card) then if skill:feasible(selected_targets, selected_cards, self.player, card) then
local ret = json.encode{ local ret = json.encode {
card = card and card.id or json.encode{ card = card and card.id or json.encode {
skill = skill.name, skill = skill.name,
subcards = selected_cards, subcards = selected_cards
}, },
targets = selected_targets, targets = selected_targets
} }
-- print(ret) -- print(ret)
return ret return ret
@ -69,39 +76,51 @@ local function useVSSkill(self, skill, pattern, cancelable, extra_data)
if self.command == "PlayCard" then if self.command == "PlayCard" then
precondition = skill:enabledAtPlay(player) precondition = skill:enabledAtPlay(player)
if not precondition then return nil end if not precondition then
return nil
end
local exp = Exppattern:Parse(skill.pattern) local exp = Exppattern:Parse(skill.pattern)
local cnames = {} local cnames = {}
for _, m in ipairs(exp.matchers) do for _, m in ipairs(exp.matchers) do
if m.name then table.insertTable(cnames, m.name) end if m.name then
table.insertTable(cnames, m.name)
end
end end
for _, n in ipairs(cnames) do for _, n in ipairs(cnames) do
local c = Fk:cloneCard(n) local c = Fk:cloneCard(n)
precondition = c.skill:canUse(Self, c) precondition = c.skill:canUse(Self, c)
if precondition then break end if precondition then
break
end
end end
else else
precondition = skill:enabledAtResponse(player) precondition = skill:enabledAtResponse(player)
if not precondition then return nil end if not precondition then
return nil
end
local exp = Exppattern:Parse(pattern) local exp = Exppattern:Parse(pattern)
precondition = exp:matchExp(skill.pattern) precondition = exp:matchExp(skill.pattern)
end end
if (not precondition) or math.random() < 0.2 then return nil end if (not precondition) or math.random() < 0.2 then
return nil
end
local selected_cards = {} local selected_cards = {}
local max_try_time = 100 local max_try_time = 100
for _ = 0, max_try_time do for _ = 0, max_try_time do
local avail_cards = table.filter(player:getCardIds{ Player.Hand, Player.Equip }, function(id) local avail_cards = table.filter(player:getCardIds{Player.Hand, Player.Equip}, function(id)
return skill:cardFilter(id, selected_cards) return skill:cardFilter(id, selected_cards)
end) end)
if #avail_cards == 0 then break end if #avail_cards == 0 then
break
end
table.insert(selected_cards, table.random(avail_cards)) table.insert(selected_cards, table.random(avail_cards))
if skill:viewAs(selected_cards) then if skill:viewAs(selected_cards) then
return { return {
skill = skill.name, skill = skill.name,
subcards = selected_cards, subcards = selected_cards
} }
end end
end end
@ -114,7 +133,9 @@ random_cb.AskForUseActiveSkill = function(self, jsonData)
local data = json.decode(jsonData) local data = json.decode(jsonData)
local skill = Fk.skills[data[1]] local skill = Fk.skills[data[1]]
local cancelable = data[3] local cancelable = data[3]
if cancelable and math.random() < 0.25 then return "" end if cancelable and math.random() < 0.25 then
return ""
end
local extra_data = json.decode(data[4]) local extra_data = json.decode(data[4])
for k, v in pairs(extra_data) do for k, v in pairs(extra_data) do
skill[k] = v skill[k] = v
@ -123,7 +144,7 @@ random_cb.AskForUseActiveSkill = function(self, jsonData)
end end
random_cb.AskForSkillInvoke = function(self, jsonData) random_cb.AskForSkillInvoke = function(self, jsonData)
return table.random{"1", ""} return table.random {"1", ""}
end end
random_cb.AskForUseCard = function(self, jsonData) random_cb.AskForUseCard = function(self, jsonData)
@ -138,9 +159,14 @@ random_cb.AskForUseCard = function(self, jsonData)
return exp:match(Fk:getCardById(id)) and not player:prohibitUse(Fk:getCardById(id)) return exp:match(Fk:getCardById(id)) and not player:prohibitUse(Fk:getCardById(id))
end) end)
if #avail_cards > 0 then if #avail_cards > 0 then
if math.random() < 0.25 then return "" end if math.random() < 0.25 then
return ""
end
avail_cards = table.map(avail_cards, function(id)
return Fk:getCardById(id)
end)
for _, card in ipairs(avail_cards) do for _, card in ipairs(avail_cards) do
local skill = Fk:getCardById(card).skill local skill = card.skill
local max_try_times = 100 local max_try_times = 100
local selected_targets = {} local selected_targets = {}
local min = skill:getMinTargetNum() local min = skill:getMinTargetNum()
@ -148,21 +174,26 @@ random_cb.AskForUseCard = function(self, jsonData)
local min_card = skill:getMinCardNum() local min_card = skill:getMinCardNum()
local max_card = skill:getMaxCardNum() local max_card = skill:getMaxCardNum()
for _ = 0, max_try_times do for _ = 0, max_try_times do
if skill:feasible(selected_targets, {card}, self.player, card) then break end if skill:feasible(selected_targets, {card.id}, self.player, card) then
break
end
local avail_targets = table.filter(self.room:getAlivePlayers(), function(p) local avail_targets = table.filter(self.room:getAlivePlayers(), function(p)
local ret = skill:targetFilter(p.id, selected_targets, {card}, card or Fk:cloneCard'zixing') local ret = skill:targetFilter(p.id, selected_targets, {card}, card or Fk:cloneCard 'zixing')
return ret return ret
end) end)
avail_targets = table.map(avail_targets, function(p) return p.id end) avail_targets = table.map(avail_targets, function(p)
return p.id
end)
if #avail_targets == 0 and #avail_cards == 0 then break end if #avail_targets + #avail_cards < 1 then
table.insertIfNeed(selected_targets, table.random(avail_targets)) break
table.insertIfNeed({card}, table.random(avail_cards))
end end
if skill:feasible(selected_targets, {card}, self.player, card) then table.insertIfNeed(selected_targets, table.random(avail_targets))
return json.encode{ end
if skill:feasible(selected_targets, {card.id}, self.player, card) then
return json.encode {
card = table.random(avail_cards), card = table.random(avail_cards),
targets = selected_targets, targets = selected_targets
} }
end end
end end
@ -176,21 +207,24 @@ random_cb.AskForResponseCard = function(self, jsonData)
local pattern = data[2] local pattern = data[2]
local cancelable = true local cancelable = true
local exp = Exppattern:Parse(pattern) local exp = Exppattern:Parse(pattern)
local avail_cards = table.filter(self.player:getCardIds{ Player.Hand, Player.Equip }, function(id) local avail_cards = table.filter(self.player:getCardIds{Player.Hand, Player.Equip}, function(id)
return exp:match(Fk:getCardById(id)) return exp:match(Fk:getCardById(id))
end) end)
if #avail_cards > 0 then return json.encode{ if #avail_cards > 0 then
return json.encode {
card = table.random(avail_cards), card = table.random(avail_cards),
targets = {}, targets = {}
} end }
end
-- TODO: vs skill -- TODO: vs skill
return "" return ""
end end
---@param self RandomAI ---@param self RandomAI
random_cb.PlayCard = function(self, jsonData) random_cb.PlayCard = function(self, jsonData)
local cards = table.map(self.player:getCardIds(Player.Hand), local cards = table.map(self.player:getCardIds(Player.Hand), function(id)
function(id) return Fk:getCardById(id) end) return Fk:getCardById(id)
end)
local actives = table.filter(self.player:getAllSkills(), function(s) local actives = table.filter(self.player:getAllSkills(), function(s)
return s:isInstanceOf(ActiveSkill) return s:isInstanceOf(ActiveSkill)
end) end)
@ -207,7 +241,9 @@ random_cb.PlayCard = function(self, jsonData)
local skill = card.skill ---@type ActiveSkill local skill = card.skill ---@type ActiveSkill
if math.random() > 0.15 then if math.random() > 0.15 then
local ret = useActiveSkill(self, skill, card) local ret = useActiveSkill(self, skill, card)
if ret ~= "" then return ret end if ret ~= "" then
return ret
end
table.removeOne(cards, card) table.removeOne(cards, card)
else else
table.removeOne(cards, card) table.removeOne(cards, card)
@ -216,7 +252,9 @@ random_cb.PlayCard = function(self, jsonData)
local active = sth local active = sth
if math.random() > 0.30 then if math.random() > 0.30 then
local ret = useActiveSkill(self, active, nil) local ret = useActiveSkill(self, active, nil)
if ret ~= "" then return ret end if ret ~= "" then
return ret
end
end end
table.removeOne(cards, active) table.removeOne(cards, active)
else else

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,5 @@
-- SPDX-License-Identifier: GPL-3.0-or-later -- SPDX-License-Identifier: GPL-3.0-or-later
-- Trust AI -- Trust AI
---@class TrustAI: AI ---@class TrustAI: AI
local TrustAI = AI:subclass("TrustAI") local TrustAI = AI:subclass("TrustAI")
@ -10,6 +8,328 @@ local trust_cb = {}
function TrustAI:initialize(player) function TrustAI:initialize(player)
AI.initialize(self, player) AI.initialize(self, player)
self.cb_table = trust_cb self.cb_table = trust_cb
<<<<<<< HEAD
=======
self.player = player
self.room = RoomInstance or ClientInstance
fk.ai_role[player.id] = "neutral"
fk.roleValue[player.id] = {
lord = 0,
loyalist = 0,
rebel = 0,
renegade = 0
}
self:updatePlayers()
end
function TrustAI:isRolePredictable()
return self.room.settings.gameMode ~= "aaa_role_mode"
end
local function aliveRoles(room)
fk.alive_roles = {
lord = 0,
loyalist = 0,
rebel = 0,
renegade = 0
}
for _, ap in ipairs(room:getAllPlayers(false)) do
fk.alive_roles[ap.role] = 0
end
for _, ap in ipairs(room:getAlivePlayers(false)) do
fk.alive_roles[ap.role] = fk.alive_roles[ap.role] + 1
end
return fk.alive_roles
end
function TrustAI:objectiveLevel(to)
if self.player.id == to.id then
return -2
elseif #self.room:getAlivePlayers(false) < 3 then
return 5
end
local ars = aliveRoles(self.room)
if self:isRolePredictable() then
fk.ai_role[self.player.id] = self.role
fk.roleValue[self.player.id][self.role] = 666
if self.role == "renegade" then
fk.explicit_renegade = true
end
for _, p in ipairs(self.room:getAlivePlayers()) do
if
p.role == self.role or p.role == "lord" and self.role == "loyalist" or
p.role == "loyalist" and self.role == "lord"
then
table.insert(self.friends, p)
if p.id ~= self.player.id then
table.insert(self.friends_noself, p)
end
else
table.insert(self.enemies, p)
end
end
elseif self.role == "renegade" then
if to.role == "lord" then
return -1
elseif ars.rebel < 1 then
return 4
elseif fk.ai_role[to.id] == "loyalist" then
return ars.lord + ars.loyalist - ars.rebel
elseif fk.ai_role[to.id] == "rebel" then
local r = ars.rebel - ars.lord + ars.loyalist
if r >= 0 then
return 3
else
return r
end
end
elseif self.role == "lord" or self.role == "loyalist" then
if fk.ai_role[to.id] == "rebel" then
return 5
elseif to.role == "lord" then
return -2
elseif ars.rebel < 1 then
if self.role == "lord" then
return fk.explicit_renegade and fk.ai_role[to.id] == "renegade" and 4 or to.hp > 1 and 2 or 0
elseif fk.explicit_renegade then
return fk.ai_role[to.id] == "renegade" and 4 or -1
else
return 3
end
elseif fk.ai_role[to.id] == "loyalist" then
return -2
elseif fk.ai_role[to.id] == "renegade" then
local r = ars.lord + ars.loyalist - ars.rebel
if r <= 0 then
return r
else
return 3
end
end
elseif self.role == "rebel" then
if to.role == "lord" then
return 5
elseif fk.ai_role[to.id] == "loyalist" then
return 4
elseif fk.ai_role[to.id] == "rebel" then
return -2
elseif fk.ai_role[to.id] == "renegade" then
local r = ars.rebel - ars.lord + ars.loyalist
if r > 0 then
return 1
else
return r
end
end
end
return 0
end
function TrustAI:updatePlayers(update)
self.role = self.player.role
local neutrality = {}
self.enemies = {}
self.friends = {}
self.friends_noself = {}
local aps = self.room:getAlivePlayers()
local function compare_func(a, b)
local v1 = fk.roleValue[a.id].rebel
local v2 = fk.roleValue[b.id].rebel
if v1 == v2 then
v1 = fk.roleValue[a.id].renegade
v2 = fk.roleValue[b.id].renegade
end
return v1 > v2
end
table.sort(aps, compare_func)
fk.explicit_renegade = false
local ars = aliveRoles(self.room)
local rebel, renegade, loyalist = 0, 0, 0
for _, ap in ipairs(aps) do
if ap.role == "lord" then
fk.ai_role[ap.id] = "loyalist"
elseif fk.roleValue[ap.id].rebel > 50 and ars.rebel > rebel then
rebel = rebel + 1
fk.ai_role[ap.id] = "rebel"
elseif fk.roleValue[ap.id].renegade > 50 and ars.renegade > renegade then
renegade = renegade + 1
fk.ai_role[ap.id] = "renegade"
fk.explicit_renegade = fk.roleValue[ap.id].renegade > 100
elseif fk.roleValue[ap.id].rebel < -50 and ars.loyalist > loyalist then
loyalist = loyalist + 1
fk.ai_role[ap.id] = "loyalist"
else
fk.ai_role[ap.id] = "neutral"
end
end
for n, p in ipairs(self.room:getAlivePlayers(false)) do
n = self:objectiveLevel(p)
if n < 0 then
table.insert(self.friends, p)
if p.id ~= self.player.id then
table.insert(self.friends_noself, p)
end
elseif n > 0 then
table.insert(self.enemies, p)
else
table.insert(neutrality, p)
end
end
self:assignValue()
--[[
if self.enemies<1 and #neutrality>0
and#self.toUse<3 and self:getOverflow()>0
then
function compare_func(a,b)
return sgs.getDefense(a)<sgs.getDefense(b)
end
table.sort(neutrality,compare_func)
table.insert(self.enemies,neutrality[1])
end-]]
end
local function updateIntention(player, to, intention)
if player.id == to.id then
return
elseif player.role == "lord" then
fk.roleValue[to.id].rebel = fk.roleValue[to.id].rebel + intention * (200 - fk.roleValue[to.id].rebel) / 200
else
if to.role == "lord" or fk.ai_role[to.id] == "loyalist" then
fk.roleValue[player.id].rebel = fk.roleValue[player.id].rebel +
intention * (200 - fk.roleValue[player.id].rebel) / 200
elseif fk.ai_role[to.id] == "rebel" then
fk.roleValue[player.id].rebel = fk.roleValue[player.id].rebel -
intention * (fk.roleValue[player.id].rebel + 200) / 200
end
if fk.roleValue[player.id].rebel < 0 and intention > 0 or fk.roleValue[player.id].rebel > 0 and intention < 0 then
fk.roleValue[player.id].renegade = fk.roleValue[player.id].renegade +
intention * (100 - fk.roleValue[player.id].renegade) / 200
end
local aps = player.room:getAlivePlayers()
local function compare_func(a, b)
local v1 = fk.roleValue[a.id].rebel
local v2 = fk.roleValue[b.id].rebel
if v1 == v2 then
v1 = fk.roleValue[a.id].renegade
v2 = fk.roleValue[b.id].renegade
end
return v1 > v2
end
table.sort(aps, compare_func)
fk.explicit_renegade = false
local ars = aliveRoles(player.room)
local rebel, renegade, loyalist = 0, 0, 0
for _, ap in ipairs(aps) do
if ap.role == "lord" then
fk.ai_role[ap.id] = "loyalist"
elseif fk.roleValue[ap.id].rebel > 50 and ars.rebel > rebel then
rebel = rebel + 1
fk.ai_role[ap.id] = "rebel"
elseif fk.roleValue[ap.id].renegade > 50 and ars.renegade > renegade then
renegade = renegade + 1
fk.ai_role[ap.id] = "renegade"
fk.explicit_renegade = fk.roleValue[ap.id].renegade > 100
elseif fk.roleValue[ap.id].rebel < -50 and ars.loyalist > loyalist then
loyalist = loyalist + 1
fk.ai_role[ap.id] = "loyalist"
else
fk.ai_role[ap.id] = "neutral"
end
end
fk.qWarning(
player.general ..
" " ..
intention ..
" " ..
fk.ai_role[player.id] ..
" rebelValue:" .. fk.roleValue[player.id].rebel .. " renegadeValue:" .. fk.roleValue[player.id].renegade
) --]]
end
end
function TrustAI:filterEvent(event, player, data)
if event == fk.TargetSpecified then
local callback = fk.ai_card[data.card.name]
callback = callback and callback.intention
if type(callback) == "function" then
for _, p in ipairs(TargetGroup:getRealTargets(data.tos)) do
p = self.room:getPlayerById(p)
local intention = callback(p.ai, data.card, self.room:getPlayerById(data.from))
if type(intention) == "number" then
updateIntention(self.room:getPlayerById(data.from), p, intention)
end
end
elseif type(callback) == "number" then
for _, p in ipairs(TargetGroup:getRealTargets(data.tos)) do
p = self.room:getPlayerById(p)
updateIntention(self.room:getPlayerById(data.from), p, callback)
end
end
elseif event == fk.StartJudge then
fk.trick_judge[data.reason] = data.pattern
elseif event == fk.AfterCardsMove then
end
end
function TrustAI:isWeak(player, getAP)
player = player or self.player
if type(player) == "number" then
player = self.room:getPlayerById(player)
end
return player.hp < 2 or player.hp <= 2 and #player:getCardIds("&h") <= 2
end
function TrustAI:isFriend(pid, tid)
if tid then
local bt = self:isFriend(pid)
return bt ~= nil and bt == self:isFriend(tid)
end
if type(pid) == "number" then
pid = self.room:getPlayerById(pid)
end
local ve = self:objectiveLevel(pid)
if ve < 0 then
return true
elseif ve > 0 then
return false
end
end
function TrustAI:isEnemie(pid, tid)
if tid then
local bt = self:isFriend(pid)
return bt ~= nil and bt ~= self:isFriend(tid)
end
if type(pid) == "number" then
pid = self.room:getPlayerById(pid)
end
local ve = self:objectiveLevel(pid)
if ve > 0 then
return true
elseif ve < 0 then
return false
end
end
function TrustAI:eventData(game_event)
local event = self.room.logic:getCurrentEvent():findParent(GameEvent[game_event], true)
return event and event.data[1]
end
for _, n in ipairs(FileIO.ls("packages")) do
if FileIO.isDir("packages/" .. n) and FileIO.exists("packages/" .. n .. "/" .. n .. "_ai.lua") then
dofile("packages/" .. n .. "/" .. n .. "_ai.lua")
end
end
-- 加载两次拓展是为了能够引用,例如属性杀的使用直接套入普通杀的使用
for _, n in ipairs(FileIO.ls("packages")) do
if FileIO.isDir("packages/" .. n) and FileIO.exists("packages/" .. n .. "/" .. n .. "_ai.lua") then
dofile("packages/" .. n .. "/" .. n .. "_ai.lua")
end
>>>>>>> 79d3213cc0aa996c9072ae5696e387a9bda210d8
end end
return TrustAI return TrustAI

View File

@ -1,15 +1,18 @@
-- SPDX-License-Identifier: GPL-3.0-or-later -- SPDX-License-Identifier: GPL-3.0-or-later
local playCardEmotionAndSound = function(room, player, card) local playCardEmotionAndSound = function(room, player, card)
if card.type ~= Card.TypeEquip then if card.type ~= Card.TypeEquip then
local anim_path = "./packages/" .. card.package.extensionName .. "/image/anim/" .. card.name local anim_path = "./packages/" .. card.package.extensionName .. "/image/anim/" .. card.name
if not FileIO.exists(anim_path) then if not FileIO.exists(anim_path) then
for _, dir in ipairs(FileIO.ls("./packages/")) do for _, dir in ipairs(FileIO.ls("./packages/")) do
anim_path = "./packages/" .. dir .. "/image/anim/" .. card.name anim_path = "./packages/" .. dir .. "/image/anim/" .. card.name
if FileIO.exists(anim_path) then break end if FileIO.exists(anim_path) then
break
end end
end end
if FileIO.exists(anim_path) then room:setEmotion(player, anim_path) end end
if FileIO.exists(anim_path) then
room:setEmotion(player, anim_path)
end
end end
local soundName local soundName
@ -25,13 +28,15 @@ local playCardEmotionAndSound = function(room, player, card)
soundName = "./audio/card/common/" .. subTypeStr soundName = "./audio/card/common/" .. subTypeStr
else else
soundName = "./packages/" .. card.package.extensionName .. "/audio/card/" soundName = "./packages/" .. card.package.extensionName .. "/audio/card/" ..
.. (player.gender == General.Male and "male/" or "female/") .. card.name (player.gender == General.Male and "male/" or "female/") .. card.name
if not FileIO.exists(soundName .. ".mp3") then if not FileIO.exists(soundName .. ".mp3") then
for _, dir in ipairs(FileIO.ls("./packages/")) do for _, dir in ipairs(FileIO.ls("./packages/")) do
soundName = "./packages/" .. dir .. "/audio/card/" soundName = "./packages/" .. dir .. "/audio/card/" ..
.. (player.gender == General.Male and "male/" or "female/") .. card.name (player.gender == General.Male and "male/" or "female/") .. card.name
if FileIO.exists(soundName .. ".mp3") then break end if FileIO.exists(soundName .. ".mp3") then
break
end
end end
end end
end end
@ -49,17 +54,19 @@ local sendCardEmotionAndLog = function(room, cardUseEvent)
local card = _card local card = _card
---[[ ---[[
if not _card:isVirtual() then if not _card:isVirtual() then
local temp = { card = _card } local temp = {
card = _card
}
Fk:filterCard(_card.id, room:getPlayerById(from), temp) Fk:filterCard(_card.id, room:getPlayerById(from), temp)
card = temp.card card = temp.card
end end
cardUseEvent.card = card cardUseEvent.card = card
--]] -- ]]
playCardEmotionAndSound(room, room:getPlayerById(from), card) playCardEmotionAndSound(room, room:getPlayerById(from), card)
room:doAnimate("Indicate", { room:doAnimate("Indicate", {
from = from, from = from,
to = cardUseEvent.tos or Util.DummyTable, to = cardUseEvent.tos or Util.DummyTable
}) })
local useCardIds = card:isVirtual() and card.subcards or { card.id } local useCardIds = card:isVirtual() and card.subcards or { card.id }
@ -71,23 +78,23 @@ local sendCardEmotionAndLog = function(room, cardUseEvent)
if card:isVirtual() or (card ~= _card) then if card:isVirtual() or (card ~= _card) then
if #useCardIds == 0 then if #useCardIds == 0 then
room:sendLog{ room:sendLog {
type = "#UseV0CardToTargets", type = "#UseV0CardToTargets",
from = from, from = from,
to = to, to = to,
arg = card:toLogString(), arg = card:toLogString()
} }
else else
room:sendLog{ room:sendLog {
type = "#UseVCardToTargets", type = "#UseVCardToTargets",
from = from, from = from,
to = to, to = to,
card = useCardIds, card = useCardIds,
arg = card:toLogString(), arg = card:toLogString()
} }
end end
else else
room:sendLog{ room:sendLog {
type = "#UseCardToTargets", type = "#UseCardToTargets",
from = from, from = from,
to = to, to = to,
@ -97,74 +104,78 @@ local sendCardEmotionAndLog = function(room, cardUseEvent)
for _, t in ipairs(cardUseEvent.tos) do for _, t in ipairs(cardUseEvent.tos) do
if t[2] then if t[2] then
local temp = {table.unpack(t)} local temp = { table.unpack(t) }
table.remove(temp, 1) table.remove(temp, 1)
room:sendLog{ room:sendLog {
type = "#CardUseCollaborator", type = "#CardUseCollaborator",
from = t[1], from = t[1],
to = temp, to = temp,
arg = card.name, arg = card.name
} }
end end
end end
elseif cardUseEvent.toCard then elseif cardUseEvent.toCard then
if card:isVirtual() or (card ~= _card) then if card:isVirtual() or (card ~= _card) then
if #useCardIds == 0 then if #useCardIds == 0 then
room:sendLog{ room:sendLog {
type = "#UseV0CardToCard", type = "#UseV0CardToCard",
from = from, from = from,
arg = cardUseEvent.toCard.name, arg = cardUseEvent.toCard.name,
arg2 = card:toLogString(), arg2 = card:toLogString()
} }
else else
room:sendLog{ room:sendLog {
type = "#UseVCardToCard", type = "#UseVCardToCard",
from = from, from = from,
card = useCardIds, card = useCardIds,
arg = cardUseEvent.toCard.name, arg = cardUseEvent.toCard.name,
arg2 = card:toLogString(), arg2 = card:toLogString()
} }
end end
else else
room:sendLog{ room:sendLog {
type = "#UseCardToCard", type = "#UseCardToCard",
from = from, from = from,
card = useCardIds, card = useCardIds,
arg = cardUseEvent.toCard.name, arg = cardUseEvent.toCard.name
} }
end end
else else
if card:isVirtual() or (card ~= _card) then if card:isVirtual() or (card ~= _card) then
if #useCardIds == 0 then if #useCardIds == 0 then
room:sendLog{ room:sendLog {
type = "#UseV0Card", type = "#UseV0Card",
from = from, from = from,
arg = card:toLogString(), arg = card:toLogString()
} }
else else
room:sendLog{ room:sendLog {
type = "#UseVCard", type = "#UseVCard",
from = from, from = from,
card = useCardIds, card = useCardIds,
arg = card:toLogString(), arg = card:toLogString()
} }
end end
else else
room:sendLog{ room:sendLog {
type = "#UseCard", type = "#UseCard",
from = from, from = from,
card = useCardIds, card = useCardIds
} }
end end
end end
if #useCardIds == 0 then return end if #useCardIds == 0 then
return
end
if cardUseEvent.tos and #cardUseEvent.tos > 0 and #cardUseEvent.tos <= 2 then if cardUseEvent.tos and #cardUseEvent.tos > 0 and #cardUseEvent.tos <= 2 then
local tos = table.map(cardUseEvent.tos, function(e) return e[1] end) local tos = table.map(cardUseEvent.tos, function(e)
return e[1]
end)
room:sendFootnote(useCardIds, { room:sendFootnote(useCardIds, {
type = "##UseCardTo", type = "##UseCardTo",
from = from, from = from,
to = tos, to = tos
}) })
if card:isVirtual() then if card:isVirtual() then
room:sendCardVirtName(useCardIds, card.name) room:sendCardVirtName(useCardIds, card.name)
@ -172,7 +183,7 @@ local sendCardEmotionAndLog = function(room, cardUseEvent)
else else
room:sendFootnote(useCardIds, { room:sendFootnote(useCardIds, {
type = "##UseCard", type = "##UseCard",
from = from, from = from
}) })
if card:isVirtual() then if card:isVirtual() then
room:sendCardVirtName(useCardIds, card.name) room:sendCardVirtName(useCardIds, card.name)
@ -185,16 +196,18 @@ GameEvent.functions[GameEvent.UseCard] = function(self)
local room = self.room local room = self.room
local logic = room.logic local logic = room.logic
room:moveCardTo(cardUseEvent.card, Card.Processing, nil, fk.ReasonUse)
if cardUseEvent.card.skill then if cardUseEvent.card.skill then
cardUseEvent.card.skill:onUse(room, cardUseEvent) cardUseEvent.card.skill:onUse(room, cardUseEvent)
end end
if logic:trigger(fk.PreCardUse, room:getPlayerById(cardUseEvent.from), cardUseEvent) then if logic:trigger(fk.PreCardUse, room:getPlayerById(cardUseEvent.from), cardUseEvent) then
cardUseEvent.breakEvent = true
self.data = { cardUseEvent }
logic:breakEvent() logic:breakEvent()
end end
room:moveCardTo(cardUseEvent.card, Card.Processing, nil, fk.ReasonUse)
sendCardEmotionAndLog(room, cardUseEvent) sendCardEmotionAndLog(room, cardUseEvent)
if not cardUseEvent.extraUse then if not cardUseEvent.extraUse then
@ -205,7 +218,6 @@ GameEvent.functions[GameEvent.UseCard] = function(self)
cardUseEvent.responseToEvent.cardsResponded = cardUseEvent.responseToEvent.cardsResponded or {} cardUseEvent.responseToEvent.cardsResponded = cardUseEvent.responseToEvent.cardsResponded or {}
table.insertIfNeed(cardUseEvent.responseToEvent.cardsResponded, cardUseEvent.card) table.insertIfNeed(cardUseEvent.responseToEvent.cardsResponded, cardUseEvent.card)
end end
for _, event in ipairs({ fk.AfterCardUseDeclared, fk.AfterCardTargetDeclared, fk.CardUsing }) do for _, event in ipairs({ fk.AfterCardUseDeclared, fk.AfterCardTargetDeclared, fk.CardUsing }) do
if not cardUseEvent.toCard and #TargetGroup:getRealTargets(cardUseEvent.tos) == 0 then if not cardUseEvent.toCard and #TargetGroup:getRealTargets(cardUseEvent.tos) == 0 then
break break
@ -229,7 +241,7 @@ GameEvent.cleaners[GameEvent.UseCard] = function(self)
room:moveCards({ room:moveCards({
ids = leftRealCardIds, ids = leftRealCardIds,
toArea = Card.DiscardPile, toArea = Card.DiscardPile,
moveReason = fk.ReasonUse, moveReason = fk.ReasonUse
}) })
end end
end end
@ -238,50 +250,54 @@ GameEvent.functions[GameEvent.RespondCard] = function(self)
local cardResponseEvent = table.unpack(self.data) local cardResponseEvent = table.unpack(self.data)
local room = self.room local room = self.room
local logic = room.logic local logic = room.logic
if logic:trigger(fk.PreCardRespond, room:getPlayerById(cardResponseEvent.from), cardResponseEvent) then
cardResponseEvent.breakEvent = true
self.data = { cardResponseEvent }
logic:breakEvent()
end
local from = cardResponseEvent.customFrom or cardResponseEvent.from local from = cardResponseEvent.customFrom or cardResponseEvent.from
local card = cardResponseEvent.card local card = cardResponseEvent.card
local cardIds = room:getSubcardsByRule(card) local cardIds = room:getSubcardsByRule(card)
if card:isVirtual() then if card:isVirtual() then
if #cardIds == 0 then if #cardIds == 0 then
room:sendLog{ room:sendLog {
type = "#ResponsePlayV0Card", type = "#ResponsePlayV0Card",
from = from, from = from,
arg = card:toLogString(), arg = card:toLogString()
} }
else else
room:sendLog{ room:sendLog {
type = "#ResponsePlayVCard", type = "#ResponsePlayVCard",
from = from, from = from,
card = cardIds, card = cardIds,
arg = card:toLogString(), arg = card:toLogString()
} }
end end
else else
room:sendLog{ room:sendLog {
type = "#ResponsePlayCard", type = "#ResponsePlayCard",
from = from, from = from,
card = cardIds, card = cardIds
} }
end end
room:moveCardTo(card, Card.Processing, nil, fk.ReasonResonpse) room:moveCardTo(card, Card.Processing, nil, fk.ReasonResonpse)
if #cardIds > 0 then if #cardIds > 0 then
room:sendFootnote(cardIds, { room:sendFootnote(cardIds, {
type = "##ResponsePlayCard", type = "##ResponsePlayCard",
from = from, from = from
}) })
if card:isVirtual() then if card:isVirtual() then
room:sendCardVirtName(cardIds, card.name) room:sendCardVirtName(cardIds, card.name)
end end
end end
if cardResponseEvent.retrial ~= true then
if logic:trigger(fk.PreCardRespond, room:getPlayerById(cardResponseEvent.from), cardResponseEvent) then
logic:breakEvent()
end
playCardEmotionAndSound(room, room:getPlayerById(from), card) playCardEmotionAndSound(room, room:getPlayerById(from), card)
end
logic:trigger(fk.CardResponding, room:getPlayerById(cardResponseEvent.from), cardResponseEvent) logic:trigger(fk.CardResponding, room:getPlayerById(cardResponseEvent.from), cardResponseEvent)
self.data = { cardResponseEvent }
end end
GameEvent.cleaners[GameEvent.RespondCard] = function(self) GameEvent.cleaners[GameEvent.RespondCard] = function(self)
@ -295,7 +311,7 @@ GameEvent.cleaners[GameEvent.RespondCard] = function(self)
room:moveCards({ room:moveCards({
ids = realCardIds, ids = realCardIds,
toArea = Card.DiscardPile, toArea = Card.DiscardPile,
moveReason = fk.ReasonResonpse, moveReason = fk.ReasonResonpse
}) })
end end
end end
@ -315,13 +331,9 @@ GameEvent.functions[GameEvent.CardEffect] = function(self)
end end
end end
if if not cardEffectEvent.toCard and
not cardEffectEvent.toCard and (not (room:getPlayerById(cardEffectEvent.to):isAlive() and cardEffectEvent.to) or
( #room:deadPlayerFilter(TargetGroup:getRealTargets(cardEffectEvent.tos)) == 0) then
not (room:getPlayerById(cardEffectEvent.to):isAlive() and cardEffectEvent.to)
or #room:deadPlayerFilter(TargetGroup:getRealTargets(cardEffectEvent.tos)) == 0
)
then
logic:breakEvent() logic:breakEvent()
end end

View File

@ -73,13 +73,6 @@ function GameEvent:addExitFunc(f)
table.insert(self.extra_exit_funcs, f) table.insert(self.extra_exit_funcs, f)
end end
function GameEvent:prependExitFunc(f)
if self.extra_exit_funcs == Util.DummyTable then
self.extra_exit_funcs = {}
end
table.insert(self.extra_exit_funcs, 1, f)
end
function GameEvent:findParent(eventType, includeSelf) function GameEvent:findParent(eventType, includeSelf)
if includeSelf and self.event == eventType then return self end if includeSelf and self.event == eventType then return self end
local e = self.parent local e = self.parent

View File

@ -1,5 +1,4 @@
-- SPDX-License-Identifier: GPL-3.0-or-later -- SPDX-License-Identifier: GPL-3.0-or-later
---@class GameLogic: Object ---@class GameLogic: Object
---@field public room Room ---@field public room Room
---@field public skill_table table<Event, TriggerSkill[]> ---@field public skill_table table<Event, TriggerSkill[]>
@ -24,16 +23,11 @@ function GameLogic:initialize(room)
self.event_recorder = {} self.event_recorder = {}
self.current_event_id = 0 self.current_event_id = 0
self.role_table = { self.role_table = {{"lord"}, {"lord", "rebel"}, {"lord", "rebel", "renegade"},
{ "lord" }, {"lord", "loyalist", "rebel", "renegade"}, {"lord", "loyalist", "rebel", "rebel", "renegade"},
{ "lord", "rebel" }, {"lord", "loyalist", "rebel", "rebel", "rebel", "renegade"},
{ "lord", "rebel", "renegade" }, {"lord", "loyalist", "loyalist", "rebel", "rebel", "rebel", "renegade"},
{ "lord", "loyalist", "rebel", "renegade" }, {"lord", "loyalist", "loyalist", "rebel", "rebel", "rebel", "rebel", "renegade"}}
{ "lord", "loyalist", "rebel", "rebel", "renegade" },
{ "lord", "loyalist", "rebel", "rebel", "rebel", "renegade" },
{ "lord", "loyalist", "loyalist", "rebel", "rebel", "rebel", "renegade" },
{ "lord", "loyalist", "loyalist", "rebel", "rebel", "rebel", "rebel", "renegade" },
}
end end
function GameLogic:run() function GameLogic:run()
@ -62,7 +56,6 @@ local function execGameEvent(type, ...)
return ret return ret
end end
function GameLogic:assignRoles() function GameLogic:assignRoles()
local room = self.room local room = self.room
local n = #room.players local n = #room.players
@ -95,11 +88,9 @@ function GameLogic:chooseGenerals()
local lordpools = {} local lordpools = {}
if room.settings.gameMode == "aaa_role_mode" then if room.settings.gameMode == "aaa_role_mode" then
for _, general in pairs(Fk:getAllGenerals()) do for _, general in pairs(Fk:getAllGenerals()) do
if (not general.hidden and not general.total_hidden) and if (not general.hidden and not general.total_hidden) and table.find(general.skills, function(s)
table.find(general.skills, function(s)
return s.lordSkill return s.lordSkill
end) and end) and not table.find(lordlist, function(g)
not table.find(lordlist, function(g)
return g.trueName == general.trueName return g.trueName == general.trueName
end) then end) then
table.insert(lordlist, general) table.insert(lordlist, general)
@ -108,13 +99,17 @@ function GameLogic:chooseGenerals()
lordlist = table.random(lordlist, 3) or {} lordlist = table.random(lordlist, 3) or {}
end end
table.insertTable(generals, Fk:getGeneralsRandomly(generalNum, Fk:getAllGenerals(), nil, function(g) table.insertTable(generals, Fk:getGeneralsRandomly(generalNum, Fk:getAllGenerals(), nil, function(g)
return table.contains(table.map(lordlist, function(g) return g.trueName end), g.trueName) return table.contains(table.map(lordlist, function(g)
return g.trueName
end), g.trueName)
end)) end))
for i = 1, #generals do for i = 1, #generals do
generals[i] = generals[i].name generals[i] = generals[i].name
end end
lordpools = table.simpleClone(generals) lordpools = table.simpleClone(generals)
table.insertTable(lordpools, table.map(lordlist, function(g) return g.name end)) table.insertTable(lordpools, table.map(lordlist, function(g)
return g.name
end))
lord_generals = room:askForGeneral(lord, lordpools, n) lord_generals = room:askForGeneral(lord, lordpools, n)
local lord_general, deputy local lord_general, deputy
if type(lord_generals) == "table" then if type(lord_generals) == "table" then
@ -141,7 +136,7 @@ function GameLogic:chooseGenerals()
for i = 1, generalNum do for i = 1, generalNum do
table.insert(arg, table.remove(generals, 1).name) table.insert(arg, table.remove(generals, 1).name)
end end
p.request_data = json.encode{ arg, n } p.request_data = json.encode {arg, n}
p.default_reply = table.random(arg, n) p.default_reply = table.random(arg, n)
end end
@ -269,7 +264,9 @@ function GameLogic:prepareForStart()
self:addTriggerSkill(trig) self:addTriggerSkill(trig)
end end
self.room:sendLog{ type = "$GameStart" } self.room:sendLog{
type = "$GameStart"
}
end end
function GameLogic:action() function GameLogic:action()
@ -280,7 +277,9 @@ function GameLogic:action()
while true do while true do
execGameEvent(GameEvent.Round) execGameEvent(GameEvent.Round)
if room.game_finished then break end if room.game_finished then
break
end
end end
end end
@ -324,16 +323,16 @@ function GameLogic:addTriggerSkill(skill)
end end
end end
if not table.contains(self.skill_priority_table[event], if not table.contains(self.skill_priority_table[event], skill.priority_table[event]) then
skill.priority_table[event]) then
table.insert(self.skill_priority_table[event], table.insert(self.skill_priority_table[event], skill.priority_table[event])
skill.priority_table[event])
end end
end end
if skill.visible then if skill.visible then
if (Fk.related_skills[skill.name] == nil) then return end if (Fk.related_skills[skill.name] == nil) then
return
end
for _, s in ipairs(Fk.related_skills[skill.name]) do for _, s in ipairs(Fk.related_skills[skill.name]) do
if (s.class == TriggerSkill) then if (s.class == TriggerSkill) then
self:addTriggerSkill(s) self:addTriggerSkill(s)
@ -352,8 +351,9 @@ function GameLogic:trigger(event, target, data, refresh_only)
local skills_to_refresh = self.refresh_skill_table[event] or Util.DummyTable local skills_to_refresh = self.refresh_skill_table[event] or Util.DummyTable
local _target = room.current -- for iteration local _target = room.current -- for iteration
local player = _target local player = _target
if #skills_to_refresh > 0 then
if #skills_to_refresh > 0 then repeat do repeat
do
-- refresh skills. This should not be broken -- refresh skills. This should not be broken
for _, skill in ipairs(skills_to_refresh) do for _, skill in ipairs(skills_to_refresh) do
if skill:canRefresh(event, target, player, data) then if skill:canRefresh(event, target, player, data) then
@ -361,56 +361,70 @@ function GameLogic:trigger(event, target, data, refresh_only)
end end
end end
player = player.next player = player.next
end until player == _target end end
until player == _target
end
if #skills == 0 or refresh_only then return end if #skills == 0 or refresh_only then
return
end
local prio_tab = self.skill_priority_table[event] local prio_tab = self.skill_priority_table[event]
local prev_prio = math.huge local prev_prio = math.huge
for _, prio in ipairs(prio_tab) do for _, prio in ipairs(prio_tab) do
if broken then break end if broken then
break
end
if prio >= prev_prio then if prio >= prev_prio then
-- continue -- continue
goto trigger_loop_continue goto trigger_loop_continue
end end
repeat do repeat
local invoked_skills = {} do
local filter_func = function(skill) local triggerables = table.filter(skills, function(skill)
return skill.priority_table[event] == prio and return skill.priority_table[event] == prio and skill:triggerable(event, target, player, data)
not table.contains(invoked_skills, skill) and end)
skill:triggerable(event, target, player, data)
end
local skill_names = table.map(table.filter(skills, filter_func), Util.NameMapper) local skill_names = table.map(triggerables, function(skill)
return skill.name
end)
while #skill_names > 0 do while #skill_names > 0 do
local skill_name = prio <= 0 and table.random(skill_names) or local skill_name = prio <= 0 and table.random(skill_names) or
room:askForChoice(player, skill_names, "trigger", "#choose-trigger") room:askForChoice(player, skill_names, "trigger", "#choose-trigger")
local skill = skill_name == "game_rule" and GameRule local skill = skill_name == "game_rule" and GameRule or Fk.skills[skill_name]
or Fk.skills[skill_name]
table.insert(invoked_skills, skill) local len = #skills
broken = skill:trigger(event, target, player, data) broken = skill:trigger(event, target, player, data)
skill_names = table.map(table.filter(skills, filter_func), Util.NameMapper)
broken = broken or (event == fk.AskForPeaches table.insertTable(skill_names,
and room:getPlayerById(data.who).hp > 0) table.map(table.filter(table.slice(skills, len - #skills), function(s)
return s.priority_table[event] == prio and s:triggerable(event, target, player, data)
end), function(s)
return s.name
end))
if broken then break end broken = broken or (event == fk.AskForPeaches and room:getPlayerById(data.who).hp > 0)
if broken then
break
end
table.removeOne(skill_names, skill_name)
end
if broken then
break
end end
if broken then break end
player = player.next player = player.next
end until player == _target end
until player == _target
prev_prio = prio prev_prio = prio
::trigger_loop_continue:: ::trigger_loop_continue::
end end
_target.ai:filterEvent(event, target, data)
return broken return broken
end end
@ -419,18 +433,6 @@ function GameLogic:getCurrentEvent()
return self.game_event_stack.t[self.game_event_stack.p] return self.game_event_stack.t[self.game_event_stack.p]
end end
--- 如果当前事件刚好是技能生效事件,就返回那个技能名,否则返回空串。
function GameLogic:getCurrentSkillName()
local skillEvent = self:getCurrentEvent()
local ret = ""
if skillEvent.event == GameEvent.SkillEffect then
local _, _, _skill = table.unpack(skillEvent.data)
local skill = _skill.main_skill and _skill.main_skill or _skill
ret = skill.name
end
return ret
end
-- 在指定历史范围中找至多n个符合条件的事件 -- 在指定历史范围中找至多n个符合条件的事件
---@param eventType integer @ 要查找的事件类型 ---@param eventType integer @ 要查找的事件类型
---@param n integer @ 最多找多少个 ---@param n integer @ 最多找多少个
@ -458,10 +460,14 @@ function GameLogic:dumpEventStack(detailed)
local top = self:getCurrentEvent() local top = self:getCurrentEvent()
local i = self.game_event_stack.p local i = self.game_event_stack.p
local inspect = p local inspect = p
if not top then return end if not top then
return
end
print("===== Start of event stack dump =====") print("===== Start of event stack dump =====")
if not detailed then print("") end if not detailed then
print("")
end
repeat repeat
local printable_data local printable_data
@ -475,9 +481,9 @@ function GameLogic:dumpEventStack(detailed)
print("Stack level #" .. i .. ": " .. tostring(top)) print("Stack level #" .. i .. ": " .. tostring(top))
else else
print("\nStack level #" .. i .. ":") print("\nStack level #" .. i .. ":")
inspect{ inspect {
eventId = GameEvent:translate(top.event), eventId = GameEvent:translate(top.event),
data = printable_data or "nil", data = printable_data or "nil"
} }
end end
@ -511,6 +517,7 @@ function GameLogic:dumpAllEvents(from, to)
end end
function GameLogic:breakEvent(ret) function GameLogic:breakEvent(ret)
self.room.breakEvent = true
coroutine.yield("__breakEvent", ret) coroutine.yield("__breakEvent", ret)
end end

File diff suppressed because it is too large Load Diff

46
lua/server/room.lua.rej Normal file
View File

@ -0,0 +1,46 @@
diff a/lua/server/room.lua b/lua/server/room.lua (rejected hunks)
@@ -1482,41 +1482,18 @@
local result = self:doRequest(chooser, command, json.encode(data))
if result == "" then
- local areas = {}
- local handcards
+ local handcards = {}
if type(flag) == "string" then
- if string.find(flag, "h") then
- table.insert(areas, Player.Hand)
- end
- if string.find(flag, "e") then
- table.insert(areas, Player.Equip)
- end
- if string.find(flag, "j") then
- table.insert(areas, Player.Judge)
- end
- handcards = target:getCardIds(areas)
+ handcards = target:getCardIds(flag)
else
- handcards = {}
for _, t in ipairs(flag.card_data) do
table.insertTable(handcards, t[2])
end
end
- if #handcards == 0 then
- return
- end
- result = handcards[math.random(1, #handcards)]
+ result = handcards[math.random(1, #handcards)] or -1
else
result = tonumber(result)
end
-
- if result == -1 then
- local handcards = target:getCardIds(Player.Hand)
- if #handcards == 0 then
- return
- end
- result = table.random(handcards)
- end
-
return result
end

View File

@ -45,7 +45,7 @@ function ServerPlayer:initialize(_self)
self._prelighted_skills = {} self._prelighted_skills = {}
self._timewaste_count = 0 self._timewaste_count = 0
self.ai = RandomAI:new(self) self.ai = SmartAI:new(self)
end end
---@param command string ---@param command string
@ -284,25 +284,25 @@ function ServerPlayer:marshal(player, observe)
end end
for k, v in pairs(self.mark) do for k, v in pairs(self.mark) do
player:doNotify("SetPlayerMark", json.encode{self.id, k, v}) player:doNotify("SetPlayerMark", json.encode { self.id, k, v })
end end
for _, s in ipairs(self.player_skills) do for _, s in ipairs(self.player_skills) do
player:doNotify("AddSkill", json.encode{self.id, s.name}) player:doNotify("AddSkill", json.encode { self.id, s.name })
end end
for k, v in pairs(self.cardUsedHistory) do for k, v in pairs(self.cardUsedHistory) do
if v[1] > 0 then if v[1] > 0 then
player:doNotify("AddCardUseHistory", json.encode{k, v[1]}) player:doNotify("AddCardUseHistory", json.encode { k, v[1] })
end end
end end
for k, v in pairs(self.skillUsedHistory) do for k, v in pairs(self.skillUsedHistory) do
if v[4] > 0 then if v[4] > 0 then
player:doNotify("SetSkillUseHistory", json.encode{self.id, k, v[1], 1}) player:doNotify("SetSkillUseHistory", json.encode { self.id, k, v[1], 1 })
player:doNotify("SetSkillUseHistory", json.encode{self.id, k, v[2], 2}) player:doNotify("SetSkillUseHistory", json.encode { self.id, k, v[2], 2 })
player:doNotify("SetSkillUseHistory", json.encode{self.id, k, v[3], 3}) player:doNotify("SetSkillUseHistory", json.encode { self.id, k, v[3], 3 })
player:doNotify("SetSkillUseHistory", json.encode{self.id, k, v[4], 4}) player:doNotify("SetSkillUseHistory", json.encode { self.id, k, v[4], 4 })
end end
end end
@ -319,13 +319,13 @@ function ServerPlayer:reconnect()
local room = self.room local room = self.room
self.serverplayer:setState(fk.Player_Online) self.serverplayer:setState(fk.Player_Online)
self:doNotify("Setup", json.encode{ self:doNotify("Setup", json.encode {
self.id, self.id,
self._splayer:getScreenName(), self._splayer:getScreenName(),
self._splayer:getAvatar(), self._splayer:getAvatar(),
}) })
self:doNotify("EnterLobby", "") self:doNotify("EnterLobby", "")
self:doNotify("EnterRoom", json.encode{ self:doNotify("EnterRoom", json.encode {
#room.players, room.timeout, room.settings, #room.players, room.timeout, room.settings,
}) })
self:doNotify("StartGame", "") self:doNotify("StartGame", "")
@ -333,7 +333,7 @@ function ServerPlayer:reconnect()
-- send player data -- send player data
for _, p in ipairs(room:getOtherPlayers(self, false, true)) do for _, p in ipairs(room:getOtherPlayers(self, false, true)) do
self:doNotify("AddPlayer", json.encode{ self:doNotify("AddPlayer", json.encode {
p.id, p.id,
p._splayer:getScreenName(), p._splayer:getScreenName(),
p._splayer:getAvatar(), p._splayer:getAvatar(),
@ -350,13 +350,13 @@ function ServerPlayer:reconnect()
for i = -2, -math.huge, -1 do for i = -2, -math.huge, -1 do
local c = Fk.printed_cards[i] local c = Fk.printed_cards[i]
if not c then break end if not c then break end
self:doNotify("PrintCard", json.encode{ c.name, c.suit, c.number }) self:doNotify("PrintCard", json.encode { c.name, c.suit, c.number })
end end
-- send card marks -- send card marks
for id, marks in pairs(room.card_marks) do for id, marks in pairs(room.card_marks) do
for k, v in pairs(marks) do for k, v in pairs(marks) do
self:doNotify("SetCardMark", json.encode{ id, k, v }) self:doNotify("SetCardMark", json.encode { id, k, v })
end end
end end
@ -371,9 +371,9 @@ function ServerPlayer:reconnect()
-- send fake skills -- send fake skills
for _, s in ipairs(self._manually_fake_skills) do for _, s in ipairs(self._manually_fake_skills) do
self:doNotify("AddSkill", json.encode{ self.id, s.name, true }) self:doNotify("AddSkill", json.encode { self.id, s.name, true })
if table.contains(self.prelighted_skills, s) then if table.contains(self.prelighted_skills, s) then
self:doNotify("PrelightSkill", json.encode{ s.name, true }) self:doNotify("PrelightSkill", json.encode { s.name, true })
end end
end end
@ -392,7 +392,7 @@ function ServerPlayer:turnOver()
self.faceup = not self.faceup self.faceup = not self.faceup
self.room:broadcastProperty(self, "faceup") self.room:broadcastProperty(self, "faceup")
self.room:sendLog{ self.room:sendLog {
type = "#TurnOver", type = "#TurnOver",
from = self.id, from = self.id,
arg = self.faceup and "face_up" or "face_down", arg = self.faceup and "face_up" or "face_down",
@ -408,12 +408,12 @@ function ServerPlayer:showCards(cards)
end end
local room = self.room local room = self.room
room:sendLog{ room:sendLog {
type = "#ShowCard", type = "#ShowCard",
from = self.id, from = self.id,
card = cards, card = cards,
} }
room:doBroadcastNotify("ShowCard", json.encode{ room:doBroadcastNotify("ShowCard", json.encode {
from = self.id, from = self.id,
cards = cards, cards = cards,
}) })
@ -469,7 +469,7 @@ function ServerPlayer:gainAnExtraPhase(phase, delay)
local logic = room.logic local logic = room.logic
local turn = logic:getCurrentEvent():findParent(GameEvent.Phase, true) local turn = logic:getCurrentEvent():findParent(GameEvent.Phase, true)
if turn then if turn then
turn:prependExitFunc(function() self:gainAnExtraPhase(phase, false) end) turn:addExitFunc(function() self:gainAnExtraPhase(phase, false) end)
return return
end end
end end
@ -478,12 +478,13 @@ function ServerPlayer:gainAnExtraPhase(phase, delay)
self.phase = phase self.phase = phase
room:broadcastProperty(self, "phase") room:broadcastProperty(self, "phase")
room:sendLog{ room:sendLog {
type = "#GainAnExtraPhase", type = "#GainAnExtraPhase",
from = self.id, from = self.id,
arg = phase_name_table[phase], arg = phase_name_table[phase],
} }
GameEvent(GameEvent.Phase, self, self.phase):exec() GameEvent(GameEvent.Phase, self, self.phase):exec()
self.phase = current self.phase = current
@ -552,7 +553,7 @@ function ServerPlayer:play(phase_table)
if (not skip) or (cancel_skip) then if (not skip) or (cancel_skip) then
GameEvent(GameEvent.Phase, self, self.phase):exec() GameEvent(GameEvent.Phase, self, self.phase):exec()
else else
room:sendLog{ room:sendLog {
type = "#PhaseSkipped", type = "#PhaseSkipped",
from = self.id, from = self.id,
arg = phase_name_table[self.phase], arg = phase_name_table[self.phase],
@ -579,7 +580,6 @@ function ServerPlayer:skip(phase)
end end
end end
--- 当进行到出牌阶段空闲点时,结束出牌阶段。
function ServerPlayer:endPlayPhase() function ServerPlayer:endPlayPhase()
self._play_phase_end = true self._play_phase_end = true
-- TODO: send log -- TODO: send log
@ -592,44 +592,22 @@ function ServerPlayer:gainAnExtraTurn(delay)
local logic = room.logic local logic = room.logic
local turn = logic:getCurrentEvent():findParent(GameEvent.Turn, true) local turn = logic:getCurrentEvent():findParent(GameEvent.Turn, true)
if turn then if turn then
turn:prependExitFunc(function() self:gainAnExtraTurn(false) end) turn:addExitFunc(function() self:gainAnExtraTurn(false) end)
return return
end end
end end
room:sendLog{ room:sendLog {
type = "#GainAnExtraTurn", type = "#GainAnExtraTurn",
from = self.id from = self.id
} }
local current = room.current local current = room.current
room.current = self room.current = self
self.tag["_extra_turn_count"] = self.tag["_extra_turn_count"] or {}
local ex_tag = self.tag["_extra_turn_count"]
local skillName = room.logic:getCurrentSkillName()
table.insert(ex_tag, skillName)
GameEvent(GameEvent.Turn, self):exec() GameEvent(GameEvent.Turn, self):exec()
table.remove(ex_tag)
room.current = current room.current = current
end end
function ServerPlayer:insideExtraTurn()
return self.tag["_extra_turn_count"] and #self.tag["_extra_turn_count"] > 0
end
---@return string
function ServerPlayer:getCurrentExtraTurnReason()
local ex_tag = self.tag["_extra_turn_count"]
if (not ex_tag) or #ex_tag == 0 then
return "game_rule"
end
return ex_tag[#ex_tag]
end
function ServerPlayer:drawCards(num, skillName, fromPlace) function ServerPlayer:drawCards(num, skillName, fromPlace)
return self.room:drawCards(self, num, skillName, fromPlace) return self.room:drawCards(self, num, skillName, fromPlace)
end end
@ -686,7 +664,7 @@ end
function ServerPlayer:addVirtualEquip(card) function ServerPlayer:addVirtualEquip(card)
Player.addVirtualEquip(self, card) Player.addVirtualEquip(self, card)
self.room:doBroadcastNotify("AddVirtualEquip", json.encode{ self.room:doBroadcastNotify("AddVirtualEquip", json.encode {
player = self.id, player = self.id,
name = card.name, name = card.name,
subcards = card.subcards, subcards = card.subcards,
@ -695,7 +673,7 @@ end
function ServerPlayer:removeVirtualEquip(cid) function ServerPlayer:removeVirtualEquip(cid)
local ret = Player.removeVirtualEquip(self, cid) local ret = Player.removeVirtualEquip(self, cid)
self.room:doBroadcastNotify("RemoveVirtualEquip", json.encode{ self.room:doBroadcastNotify("RemoveVirtualEquip", json.encode {
player = self.id, player = self.id,
id = cid, id = cid,
}) })
@ -704,22 +682,22 @@ end
function ServerPlayer:addCardUseHistory(cardName, num) function ServerPlayer:addCardUseHistory(cardName, num)
Player.addCardUseHistory(self, cardName, num) Player.addCardUseHistory(self, cardName, num)
self:doNotify("AddCardUseHistory", json.encode{cardName, num}) self:doNotify("AddCardUseHistory", json.encode { cardName, num })
end end
function ServerPlayer:setCardUseHistory(cardName, num, scope) function ServerPlayer:setCardUseHistory(cardName, num, scope)
Player.setCardUseHistory(self, cardName, num, scope) Player.setCardUseHistory(self, cardName, num, scope)
self:doNotify("SetCardUseHistory", json.encode{cardName, num, scope}) self:doNotify("SetCardUseHistory", json.encode { cardName, num, scope })
end end
function ServerPlayer:addSkillUseHistory(cardName, num) function ServerPlayer:addSkillUseHistory(cardName, num)
Player.addSkillUseHistory(self, cardName, num) Player.addSkillUseHistory(self, cardName, num)
self.room:doBroadcastNotify("AddSkillUseHistory", json.encode{self.id, cardName, num}) self.room:doBroadcastNotify("AddSkillUseHistory", json.encode { self.id, cardName, num })
end end
function ServerPlayer:setSkillUseHistory(cardName, num, scope) function ServerPlayer:setSkillUseHistory(cardName, num, scope)
Player.setSkillUseHistory(self, cardName, num, scope) Player.setSkillUseHistory(self, cardName, num, scope)
self.room:doBroadcastNotify("SetSkillUseHistory", json.encode{self.id, cardName, num, scope}) self.room:doBroadcastNotify("SetSkillUseHistory", json.encode { self.id, cardName, num, scope })
end end
---@param chained boolean ---@param chained boolean
@ -732,7 +710,7 @@ function ServerPlayer:setChainState(chained)
self.chained = chained self.chained = chained
room:broadcastProperty(self, "chained") room:broadcastProperty(self, "chained")
room:sendLog{ room:sendLog {
type = "#ChainStateChange", type = "#ChainStateChange",
from = self.id, from = self.id,
arg = self.chained and "chained" or "un-chained" arg = self.chained and "chained" or "un-chained"
@ -743,7 +721,7 @@ function ServerPlayer:setChainState(chained)
end end
function ServerPlayer:reset() function ServerPlayer:reset()
self.room:sendLog{ self.room:sendLog {
type = "#ChainStateChange", type = "#ChainStateChange",
from = self.id, from = self.id,
arg = "reset-general" arg = "reset-general"
@ -797,7 +775,7 @@ function ServerPlayer:addFakeSkill(skill)
end end
-- TODO -- TODO
self:doNotify("AddSkill", json.encode{ self.id, skill.name, true }) self:doNotify("AddSkill", json.encode { self.id, skill.name, true })
end end
---@param skill Skill ---@param skill Skill
@ -816,7 +794,7 @@ function ServerPlayer:loseFakeSkill(skill)
end end
-- TODO -- TODO
self:doNotify("LoseSkill", json.encode{ self.id, skill.name, true }) self:doNotify("LoseSkill", json.encode { self.id, skill.name, true })
end end
function ServerPlayer:isFakeSkill(skill) function ServerPlayer:isFakeSkill(skill)
@ -852,7 +830,7 @@ function ServerPlayer:prelightSkill(skill, isPrelight)
end end
end end
self:doNotify("PrelightSkill", json.encode{ skill.name, isPrelight }) self:doNotify("PrelightSkill", json.encode { skill.name, isPrelight })
end end
---@param isDeputy bool ---@param isDeputy bool
@ -876,7 +854,8 @@ function ServerPlayer:revealGeneral(isDeputy, no_trigger)
local ret = true local ret = true
if not ((isDeputy and self.general ~= "anjiang") or (not isDeputy and self.deputyGeneral ~= "anjiang")) then if not ((isDeputy and self.general ~= "anjiang") or (not isDeputy and self.deputyGeneral ~= "anjiang")) then
local other = Fk.generals[self:getMark(isDeputy and "__heg_general" or "__heg_deputy")] or Fk.generals["blank_shibing"] local other = Fk.generals[self:getMark(isDeputy and "__heg_general" or "__heg_deputy")] or
Fk.generals["blank_shibing"]
for _, sname in ipairs(other:getSkillNameList()) do for _, sname in ipairs(other:getSkillNameList()) do
local s = Fk.skills[sname] local s = Fk.skills[sname]
if s.frequency == Skill.Compulsory and s.relate_to_place ~= (isDeputy and "m" or "d") then if s.frequency == Skill.Compulsory and s.relate_to_place ~= (isDeputy and "m" or "d") then
@ -909,7 +888,7 @@ function ServerPlayer:revealGeneral(isDeputy, no_trigger)
self.gender = general.gender self.gender = general.gender
end end
room:sendLog{ room:sendLog {
type = "#RevealGeneral", type = "#RevealGeneral",
from = self.id, from = self.id,
arg = isDeputy and "deputyGeneral" or "mainGeneral", arg = isDeputy and "deputyGeneral" or "mainGeneral",
@ -948,7 +927,7 @@ function ServerPlayer:hideGeneral(isDeputy)
local mark = isDeputy and "__heg_deputy" or "__heg_general" local mark = isDeputy and "__heg_deputy" or "__heg_general"
self:setMark(mark, generalName) self:setMark(mark, generalName)
self:doNotify("SetPlayerMark", json.encode{ self.id, mark, generalName}) self:doNotify("SetPlayerMark", json.encode { self.id, mark, generalName })
if isDeputy then if isDeputy then
room:setDeputyGeneral(self, "anjiang") room:setDeputyGeneral(self, "anjiang")
@ -983,6 +962,7 @@ function ServerPlayer:hideGeneral(isDeputy)
room.logic:trigger(fk.GeneralHidden, room, generalName) room.logic:trigger(fk.GeneralHidden, room, generalName)
end end
-- 神貂蝉 -- 神貂蝉
---@param p ServerPlayer ---@param p ServerPlayer
@ -1002,7 +982,7 @@ function ServerPlayer:addBuddy(other)
other = self.room:getPlayerById(other) other = self.room:getPlayerById(other)
end end
Player.addBuddy(self, other) Player.addBuddy(self, other)
self:doNotify("AddBuddy", json.encode{ other.id, other.player_cards[Player.Hand] }) self:doNotify("AddBuddy", json.encode { other.id, other.player_cards[Player.Hand] })
end end
function ServerPlayer:removeBuddy(other) function ServerPlayer:removeBuddy(other)
@ -1013,4 +993,8 @@ function ServerPlayer:removeBuddy(other)
self:doNotify("RmBuddy", tostring(other.id)) self:doNotify("RmBuddy", tostring(other.id))
end end
function ServerPlayer:getAI()
return self.ai
end
return ServerPlayer return ServerPlayer

View File

@ -1,10 +1,9 @@
-- SPDX-License-Identifier: GPL-3.0-or-later -- SPDX-License-Identifier: GPL-3.0-or-later
local extension = Package:new("maneuvering", Package.CardPack) local extension = Package:new("maneuvering", Package.CardPack)
local slash = Fk:cloneCard("slash") local slash = Fk:cloneCard("slash")
local thunderSlashSkill = fk.CreateActiveSkill{ local thunderSlashSkill = fk.CreateActiveSkill {
name = "thunder__slash_skill", name = "thunder__slash_skill",
max_phase_use_time = 1, max_phase_use_time = 1,
target_num = 1, target_num = 1,
@ -25,25 +24,18 @@ local thunderSlashSkill = fk.CreateActiveSkill{
}) })
end end
} }
local thunderSlash = fk.CreateBasicCard{ local thunderSlash = fk.CreateBasicCard {
name = "thunder__slash", name = "thunder__slash",
skill = thunderSlashSkill, skill = thunderSlashSkill,
is_damage_card = true, is_damage_card = true
} }
extension:addCards{ extension:addCards{thunderSlash:clone(Card.Club, 5), thunderSlash:clone(Card.Club, 6), thunderSlash:clone(Card.Club, 7),
thunderSlash:clone(Card.Club, 5), thunderSlash:clone(Card.Club, 8), thunderSlash:clone(Card.Spade, 4),
thunderSlash:clone(Card.Club, 6), thunderSlash:clone(Card.Spade, 5), thunderSlash:clone(Card.Spade, 6),
thunderSlash:clone(Card.Club, 7), thunderSlash:clone(Card.Spade, 7), thunderSlash:clone(Card.Spade, 8)}
thunderSlash:clone(Card.Club, 8),
thunderSlash:clone(Card.Spade, 4),
thunderSlash:clone(Card.Spade, 5),
thunderSlash:clone(Card.Spade, 6),
thunderSlash:clone(Card.Spade, 7),
thunderSlash:clone(Card.Spade, 8),
}
local fireSlashSkill = fk.CreateActiveSkill{ local fireSlashSkill = fk.CreateActiveSkill {
name = "fire__slash_skill", name = "fire__slash_skill",
max_phase_use_time = 1, max_phase_use_time = 1,
target_num = 1, target_num = 1,
@ -64,35 +56,34 @@ local fireSlashSkill = fk.CreateActiveSkill{
}) })
end end
} }
local fireSlash = fk.CreateBasicCard{ local fireSlash = fk.CreateBasicCard {
name = "fire__slash", name = "fire__slash",
skill = fireSlashSkill, skill = fireSlashSkill,
is_damage_card = true, is_damage_card = true
} }
extension:addCards{ extension:addCards{fireSlash:clone(Card.Heart, 4), fireSlash:clone(Card.Heart, 7), fireSlash:clone(Card.Heart, 10),
fireSlash:clone(Card.Heart, 4), fireSlash:clone(Card.Diamond, 4), fireSlash:clone(Card.Diamond, 5)}
fireSlash:clone(Card.Heart, 7),
fireSlash:clone(Card.Heart, 10),
fireSlash:clone(Card.Diamond, 4),
fireSlash:clone(Card.Diamond, 5),
}
local analepticSkill = fk.CreateActiveSkill{ local analepticSkill = fk.CreateActiveSkill {
name = "analeptic_skill", name = "analeptic_skill",
max_turn_use_time = 1, max_turn_use_time = 1,
mod_target_filter = function(self, to_select, selected, user, card, distance_limited) mod_target_filter = function(self, to_select, selected, user, card, distance_limited)
return self:withinTimesLimit(Fk:currentRoom():getPlayerById(to_select), Player.HistoryTurn, card, "analeptic", Fk:currentRoom():getPlayerById(to_select)) and local to = Fk:currentRoom():getPlayerById(to_select)
local from = Fk:currentRoom():getPlayerById(user)
return self:withinTimesLimit(from, Player.HistoryTurn, card, "analeptic", to) and
not table.find(Fk:currentRoom().alive_players, function(p) not table.find(Fk:currentRoom().alive_players, function(p)
return p.dying return p.dying
end) end) and not (card and from:isProhibited(to, card))
end, end,
can_use = function(self, player, card) can_use = function(self, player, card)
return self:withinTimesLimit(player, Player.HistoryTurn, card, "analeptic", player) return self:withinTimesLimit(player, Player.HistoryTurn, card, "analeptic", player) and
not player:isProhibited(player, card)
end, end,
on_use = function(self, room, use) on_use = function(self, room, use)
if not use.tos or #TargetGroup:getRealTargets(use.tos) == 0 then if not use.tos or #TargetGroup:getRealTargets(use.tos) == 0 then
use.tos = { { use.from } } use.tos = {{use.from}}
end end
if use.extra_data and use.extra_data.analepticRecover then if use.extra_data and use.extra_data.analepticRecover then
@ -106,7 +97,7 @@ local analepticSkill = fk.CreateActiveSkill{
who = to, who = to,
num = 1, num = 1,
recoverBy = room:getPlayerById(effect.from), recoverBy = room:getPlayerById(effect.from),
card = effect.card, card = effect.card
}) })
else else
to.drank = to.drank + 1 to.drank = to.drank + 1
@ -115,16 +106,15 @@ local analepticSkill = fk.CreateActiveSkill{
end end
} }
local analepticEffect = fk.CreateTriggerSkill{ local analepticEffect = fk.CreateTriggerSkill {
name = "analeptic_effect", name = "analeptic_effect",
global = true, global = true,
priority = 0, -- game rule priority = 0, -- game rule
events = { fk.PreCardUse, fk.EventPhaseStart }, events = {fk.PreCardUse, fk.EventPhaseStart},
can_trigger = function(self, event, target, player, data) can_trigger = function(self, event, target, player, data)
if target ~= player then if target ~= player then
return false return false
end end
if event == fk.PreCardUse then if event == fk.PreCardUse then
return data.card.trueName == "slash" and player.drank > 0 return data.card.trueName == "slash" and player.drank > 0
else else
@ -147,26 +137,21 @@ local analepticEffect = fk.CreateTriggerSkill{
end end
end end
end end
end, end
} }
Fk:addSkill(analepticEffect) Fk:addSkill(analepticEffect)
local analeptic = fk.CreateBasicCard{ local analeptic = fk.CreateBasicCard {
name = "analeptic", name = "analeptic",
suit = Card.Spade, suit = Card.Spade,
number = 3, number = 3,
skill = analepticSkill, skill = analepticSkill
} }
extension:addCards({ extension:addCards({analeptic, analeptic:clone(Card.Spade, 9), analeptic:clone(Card.Club, 3),
analeptic, analeptic:clone(Card.Club, 9), analeptic:clone(Card.Diamond, 9)})
analeptic:clone(Card.Spade, 9),
analeptic:clone(Card.Club, 3),
analeptic:clone(Card.Club, 9),
analeptic:clone(Card.Diamond, 9),
})
local recast = fk.CreateActiveSkill{ local recast = fk.CreateActiveSkill {
name = "recast", name = "recast",
target_num = 0, target_num = 0,
on_use = function(self, room, effect) on_use = function(self, room, effect)
@ -175,55 +160,62 @@ local recast = fk.CreateActiveSkill{
} }
Fk:addSkill(recast) Fk:addSkill(recast)
local ironChainCardSkill = fk.CreateActiveSkill{ local ironChainCardSkill = fk.CreateActiveSkill {
name = "iron_chain_skill", name = "iron_chain_skill",
min_target_num = 1, min_target_num = 1,
max_target_num = 2, max_target_num = 2,
mod_target_filter = function(self, to_select, selected, user, card, distance_limited) mod_target_filter = function(self, to_select, selected, user, card, distance_limited)
return true local to = Fk:currentRoom():getPlayerById(to_select)
local from = Fk:currentRoom():getPlayerById(user)
return not (card and from:isProhibited(to, card))
end,
target_filter = function(self, to_select, selected, _, card)
if #selected < self:getMaxTargetNum(Self, card) then
return self:modTargetFilter(to_select, selected, Self.id, card)
end
end, end,
target_filter = function() return true end,
on_effect = function(self, room, cardEffectEvent) on_effect = function(self, room, cardEffectEvent)
local to = room:getPlayerById(cardEffectEvent.to) local to = room:getPlayerById(cardEffectEvent.to)
to:setChainState(not to.chained) to:setChainState(not to.chained)
end, end
} }
local ironChain = fk.CreateTrickCard{ local ironChain = fk.CreateTrickCard {
name = "iron_chain", name = "iron_chain",
skill = ironChainCardSkill, skill = ironChainCardSkill,
special_skills = { "recast" }, special_skills = {"recast"},
multiple_targets = true, multiple_targets = true
}
extension:addCards{
ironChain:clone(Card.Spade, 11),
ironChain:clone(Card.Spade, 12),
ironChain:clone(Card.Club, 10),
ironChain:clone(Card.Club, 11),
ironChain:clone(Card.Club, 12),
ironChain:clone(Card.Club, 13),
} }
extension:addCards{ironChain:clone(Card.Spade, 11), ironChain:clone(Card.Spade, 12), ironChain:clone(Card.Club, 10),
ironChain:clone(Card.Club, 11), ironChain:clone(Card.Club, 12), ironChain:clone(Card.Club, 13)}
local fireAttackSkill = fk.CreateActiveSkill{ local fireAttackSkill = fk.CreateActiveSkill {
name = "fire_attack_skill", name = "fire_attack_skill",
target_num = 1, target_num = 1,
mod_target_filter = function(self, to_select, selected, user, card, distance_limited) mod_target_filter = function(self, to_select, selected, user, card, distance_limited)
return not Fk:currentRoom():getPlayerById(to_select):isKongcheng() local to = Fk:currentRoom():getPlayerById(to_select)
local from = Fk:currentRoom():getPlayerById(user)
return not (card and from:isProhibited(to, card)) and not to:isKongcheng()
end, end,
target_filter = function(self, to_select) target_filter = function(self, to_select, selected, _, card)
return self:modTargetFilter(to_select) if #selected < self:getMaxTargetNum(Self, card) then
return self:modTargetFilter(to_select, selected, Self.id, card)
end
end, end,
on_effect = function(self, room, cardEffectEvent) on_effect = function(self, room, cardEffectEvent)
local from = room:getPlayerById(cardEffectEvent.from) local from = room:getPlayerById(cardEffectEvent.from)
local to = room:getPlayerById(cardEffectEvent.to) local to = room:getPlayerById(cardEffectEvent.to)
if to:isKongcheng() then return end if to:isKongcheng() then
return
end
local showCard = room:askForCard(to, 1, 1, false, self.name, false, ".|.|.|hand", "#fire_attack-show:" .. from.id)[1] local showCard = room:askForCard(to, 1, 1, false, self.name, false, ".|.|.|hand",
"#fire_attack-show:" .. from.id)[1]
to:showCards(showCard) to:showCards(showCard)
showCard = Fk:getCardById(showCard) showCard = Fk:getCardById(showCard)
local cards = room:askForDiscard(from, 1, 1, false, self.name, true, local cards = room:askForDiscard(from, 1, 1, false, self.name, true, ".|.|" .. showCard:getSuitString(),
".|.|" .. showCard:getSuitString(), "#fire_attack-discard:" .. to.id .. "::" .. showCard:getSuitString()) "#fire_attack-discard:" .. to.id .. "::" .. showCard:getSuitString())
if #cards > 0 then if #cards > 0 then
room:damage({ room:damage({
from = from, from = from,
@ -234,29 +226,27 @@ local fireAttackSkill = fk.CreateActiveSkill{
skillName = self.name skillName = self.name
}) })
end end
end, end
} }
local fireAttack = fk.CreateTrickCard{ local fireAttack = fk.CreateTrickCard {
name = "fire_attack", name = "fire_attack",
skill = fireAttackSkill, skill = fireAttackSkill,
is_damage_card = true, is_damage_card = true
}
extension:addCards{
fireAttack:clone(Card.Heart, 2),
fireAttack:clone(Card.Heart, 3),
fireAttack:clone(Card.Diamond, 12),
} }
extension:addCards{fireAttack:clone(Card.Heart, 2), fireAttack:clone(Card.Heart, 3), fireAttack:clone(Card.Diamond, 12)}
local supplyShortageSkill = fk.CreateActiveSkill{ local supplyShortageSkill = fk.CreateActiveSkill {
name = "supply_shortage_skill", name = "supply_shortage_skill",
distance_limit = 1, distance_limit = 1,
mod_target_filter = function(self, to_select, selected, user, card, distance_limited) mod_target_filter = function(self, to_select, selected, user, card, distance_limited)
local player = Fk:currentRoom():getPlayerById(to_select) local player = Fk:currentRoom():getPlayerById(to_select)
local from = Fk:currentRoom():getPlayerById(user) local from = Fk:currentRoom():getPlayerById(user)
return from ~= player and not (distance_limited and not self:withinDistanceLimit(from, false, card, player)) return
user ~= to_select and not (distance_limited and not self:withinDistanceLimit(from, false, card, player)) and
not (card and from:isProhibited(player, card))
end, end,
target_filter = function(self, to_select, selected, _, card) target_filter = function(self, to_select, selected, _, card)
return #selected == 0 and self:modTargetFilter(to_select, selected, Self.id, card, true) return #selected < 1 and self:modTargetFilter(to_select, selected, Self.id, card, true)
end, end,
target_num = 1, target_num = 1,
on_effect = function(self, room, effect) on_effect = function(self, room, effect)
@ -264,61 +254,56 @@ local supplyShortageSkill = fk.CreateActiveSkill{
local judge = { local judge = {
who = to, who = to,
reason = "supply_shortage", reason = "supply_shortage",
pattern = ".|.|spade,heart,diamond", pattern = ".|.|club"
} }
room:judge(judge) room:judge(judge)
local result = judge.card if judge.card.suit ~= Card.Club then
if result.suit ~= Card.Club then
to:skip(Player.Draw) to:skip(Player.Draw)
end end
self:onNullified(room, effect) self:onNullified(room, effect)
end, end,
on_nullified = function(self, room, effect) on_nullified = function(self, room, effect)
room:moveCards{ room:moveCards{
ids = room:getSubcardsByRule(effect.card, { Card.Processing }), ids = room:getSubcardsByRule(effect.card, {Card.Processing}),
toArea = Card.DiscardPile, toArea = Card.DiscardPile,
moveReason = fk.ReasonUse moveReason = fk.ReasonUse
} }
end, end
} }
local supplyShortage = fk.CreateDelayedTrickCard{ local supplyShortage = fk.CreateDelayedTrickCard {
name = "supply_shortage", name = "supply_shortage",
skill = supplyShortageSkill, skill = supplyShortageSkill
}
extension:addCards{
supplyShortage:clone(Card.Spade, 10),
supplyShortage:clone(Card.Club, 4),
} }
extension:addCards{supplyShortage:clone(Card.Spade, 10), supplyShortage:clone(Card.Club, 4)}
local gudingSkill = fk.CreateTriggerSkill{ local gudingSkill = fk.CreateTriggerSkill {
name = "#guding_blade_skill", name = "#guding_blade_skill",
attached_equip = "guding_blade", attached_equip = "guding_blade",
frequency = Skill.Compulsory, frequency = Skill.Compulsory,
events = {fk.DamageCaused}, events = {fk.DamageCaused},
can_trigger = function(self, event, target, player, data) can_trigger = function(self, event, target, player, data)
return target == player and player:hasSkill(self.name) and return target == player and player:hasSkill(self.name) and data.to:isKongcheng() and data.card and
data.to:isKongcheng() and data.card and data.card.trueName == "slash" and data.card.trueName == "slash" and not data.chain
not data.chain
end, end,
on_use = function(_, _, _, _, data) on_use = function(_, _, _, _, data)
data.damage = data.damage + 1 data.damage = data.damage + 1
end, end
} }
Fk:addSkill(gudingSkill) Fk:addSkill(gudingSkill)
local gudingBlade = fk.CreateWeapon{ local gudingBlade = fk.CreateWeapon {
name = "guding_blade", name = "guding_blade",
suit = Card.Spade, suit = Card.Spade,
number = 1, number = 1,
attack_range = 2, attack_range = 2,
equip_skill = gudingSkill, equip_skill = gudingSkill
} }
extension:addCard(gudingBlade) extension:addCard(gudingBlade)
local fanSkill = fk.CreateTriggerSkill{ local fanSkill = fk.CreateTriggerSkill {
name = "#fan_skill", name = "#fan_skill",
attached_equip = "fan", attached_equip = "fan",
events = { fk.AfterCardUseDeclared }, events = {fk.AfterCardUseDeclared},
can_trigger = function(self, event, target, player, data) can_trigger = function(self, event, target, player, data)
return target == player and player:hasSkill(self.name) and data.card.name == "slash" return target == player and player:hasSkill(self.name) and data.card.name == "slash"
end, end,
@ -327,20 +312,20 @@ local fanSkill = fk.CreateTriggerSkill{
card.skillName = "fan" card.skillName = "fan"
card:addSubcard(data.card) card:addSubcard(data.card)
data.card = card data.card = card
end, end
} }
Fk:addSkill(fanSkill) Fk:addSkill(fanSkill)
local fan = fk.CreateWeapon{ local fan = fk.CreateWeapon {
name = "fan", name = "fan",
suit = Card.Diamond, suit = Card.Diamond,
number = 1, number = 1,
attack_range = 4, attack_range = 4,
equip_skill = fanSkill, equip_skill = fanSkill
} }
extension:addCard(fan) extension:addCard(fan)
local vineSkill = fk.CreateTriggerSkill{ local vineSkill = fk.CreateTriggerSkill {
name = "#vine_skill", name = "#vine_skill",
attached_equip = "vine", attached_equip = "vine",
mute = true, mute = true,
@ -349,13 +334,12 @@ local vineSkill = fk.CreateTriggerSkill{
events = {fk.PreCardEffect, fk.DamageInflicted}, events = {fk.PreCardEffect, fk.DamageInflicted},
can_trigger = function(self, event, target, player, data) can_trigger = function(self, event, target, player, data)
if event == fk.DamageInflicted then if event == fk.DamageInflicted then
return target == player and player:hasSkill(self.name) and return target == player and player:hasSkill(self.name) and data.damageType == fk.FireDamage
data.damageType == fk.FireDamage
end end
local effect = data ---@type CardEffectEvent local effect = data ---@type CardEffectEvent
return player.id == effect.to and player:hasSkill(self.name) and return player.id == effect.to and player:hasSkill(self.name) and
(effect.card.name == "slash" or effect.card.name == "savage_assault" or (effect.card.name == "slash" or effect.card.name == "savage_assault" or effect.card.name ==
effect.card.name == "archery_attack") "archery_attack")
end, end,
on_use = function(self, event, target, player, data) on_use = function(self, event, target, player, data)
local room = player.room local room = player.room
@ -368,19 +352,16 @@ local vineSkill = fk.CreateTriggerSkill{
room:setEmotion(player, "./packages/maneuvering/image/anim/vine") room:setEmotion(player, "./packages/maneuvering/image/anim/vine")
return true return true
end end
end, end
} }
Fk:addSkill(vineSkill) Fk:addSkill(vineSkill)
local vine = fk.CreateArmor{ local vine = fk.CreateArmor {
name = "vine", name = "vine",
equip_skill = vineSkill, equip_skill = vineSkill
}
extension:addCards{
vine:clone(Card.Spade, 2),
vine:clone(Card.Club, 2),
} }
extension:addCards{vine:clone(Card.Spade, 2), vine:clone(Card.Club, 2)}
local silverLionSkill = fk.CreateTriggerSkill{ local silverLionSkill = fk.CreateTriggerSkill {
name = "#silver_lion_skill", name = "#silver_lion_skill",
attached_equip = "silver_lion", attached_equip = "silver_lion",
frequency = Skill.Compulsory, frequency = Skill.Compulsory,
@ -390,10 +371,10 @@ local silverLionSkill = fk.CreateTriggerSkill{
end, end,
on_use = function(_, _, _, _, data) on_use = function(_, _, _, _, data)
data.damage = 1 data.damage = 1
end, end
} }
Fk:addSkill(silverLionSkill) Fk:addSkill(silverLionSkill)
local silverLion = fk.CreateArmor{ local silverLion = fk.CreateArmor {
name = "silver_lion", name = "silver_lion",
suit = Card.Club, suit = Card.Club,
number = 1, number = 1,
@ -409,40 +390,26 @@ local silverLion = fk.CreateArmor{
skillName = self.name skillName = self.name
} }
end end
end, end
} }
extension:addCard(silverLion) extension:addCard(silverLion)
local huaLiu = fk.CreateDefensiveRide{ local huaLiu = fk.CreateDefensiveRide {
name = "hualiu", name = "hualiu",
suit = Card.Diamond, suit = Card.Diamond,
number = 13, number = 13
} }
extension:addCards({ extension:addCards({huaLiu})
huaLiu,
})
extension:addCards{ extension:addCards{Fk:cloneCard("jink", Card.Heart, 8), Fk:cloneCard("jink", Card.Heart, 9),
Fk:cloneCard("jink", Card.Heart, 8), Fk:cloneCard("jink", Card.Heart, 11), Fk:cloneCard("jink", Card.Heart, 12),
Fk:cloneCard("jink", Card.Heart, 9), Fk:cloneCard("jink", Card.Diamond, 6), Fk:cloneCard("jink", Card.Diamond, 7),
Fk:cloneCard("jink", Card.Heart, 11), Fk:cloneCard("jink", Card.Diamond, 8), Fk:cloneCard("jink", Card.Diamond, 10),
Fk:cloneCard("jink", Card.Heart, 12), Fk:cloneCard("jink", Card.Diamond, 11), Fk:cloneCard("peach", Card.Heart, 5),
Fk:cloneCard("jink", Card.Diamond, 6), Fk:cloneCard("peach", Card.Heart, 6), Fk:cloneCard("peach", Card.Diamond, 2),
Fk:cloneCard("jink", Card.Diamond, 7), Fk:cloneCard("peach", Card.Diamond, 3), Fk:cloneCard("nullification", Card.Heart, 1),
Fk:cloneCard("jink", Card.Diamond, 8), Fk:cloneCard("nullification", Card.Heart, 13), Fk:cloneCard("nullification", Card.Spade, 13)}
Fk:cloneCard("jink", Card.Diamond, 10),
Fk:cloneCard("jink", Card.Diamond, 11),
Fk:cloneCard("peach", Card.Heart, 5),
Fk:cloneCard("peach", Card.Heart, 6),
Fk:cloneCard("peach", Card.Diamond, 2),
Fk:cloneCard("peach", Card.Diamond, 3),
Fk:cloneCard("nullification", Card.Heart, 1),
Fk:cloneCard("nullification", Card.Heart, 13),
Fk:cloneCard("nullification", Card.Spade, 13),
}
Fk:loadTranslationTable{ Fk:loadTranslationTable{
["maneuvering"] = "军争", ["maneuvering"] = "军争",
@ -475,7 +442,7 @@ Fk:loadTranslationTable{
["silver_lion"] = "白银狮子", ["silver_lion"] = "白银狮子",
[":silver_lion"] = "装备牌·防具<br /><b>防具技能</b>锁定技。每当你受到伤害时若此伤害大于1点防止多余的伤害。每当你失去装备区里的【白银狮子】后你回复1点体力。", [":silver_lion"] = "装备牌·防具<br /><b>防具技能</b>锁定技。每当你受到伤害时若此伤害大于1点防止多余的伤害。每当你失去装备区里的【白银狮子】后你回复1点体力。",
["hualiu"] = "骅骝", ["hualiu"] = "骅骝",
[":hualiu"] = "装备牌·坐骑<br /><b>坐骑技能</b>:其他角色与你的距离+1。", [":hualiu"] = "装备牌·坐骑<br /><b>坐骑技能</b>:其他角色与你的距离+1。"
} }
return extension return extension

View File

@ -0,0 +1,178 @@
fk.ai_card.thunder__slash = fk.ai_card.slash
fk.ai_use_play.thunder__slash = fk.ai_use_play.slash
fk.ai_card.fire__slash = fk.ai_card.slash
fk.ai_use_play.fire__slash = fk.ai_use_play.slash
fk.ai_card.analeptic = {
intention = 60, -- 身份值
value = 5, -- 卡牌价值
priority = 3 -- 使用优先值
}
fk.ai_use_play.analeptic = function(self, card)
local cards = table.map(self.player:getCardIds("&he"), function(id)
return Fk:getCardById(id)
end)
self:sortValue(cards)
for _, sth in ipairs(self:getActives("slash")) do
local slash = nil
if sth:isInstanceOf(Card) then
if sth.skill:canUse(self.player, sth) and not self.player:prohibitUse(sth) then
slash = sth
end
else
local selected = {}
for _, c in ipairs(cards) do
if sth:cardFilter(c.id, selected) then
table.insert(selected, c.id)
end
end
local tc = sth:viewAs(selected)
if tc and tc:matchPattern("slash") and tc.skill:canUse(self.player, tc) and not self.player:prohibitUse(tc) then
slash = tc
end
end
if slash then
fk.ai_use_play.slash(self, slash)
if self.use_id then
self.use_id = card.id
self.use_tos = {}
break
end
end
end
end
fk.ai_card.iron_chain = {
intention = function(self, card, from)
if self.player.chained then
return -80
end
return 80
end, -- 身份值
value = 2, -- 卡牌价值
priority = 3 -- 使用优先值
}
fk.ai_use_play.iron_chain = function(self, card)
for _, p in ipairs(self.friends) do
if card.skill:targetFilter(p.id, self.use_tos, {}, card) and p.chained then
table.insert(self.use_tos, p.id)
end
end
self:sort(self.enemies)
for _, p in ipairs(self.enemies) do
if card.skill:targetFilter(p.id, self.use_tos, {}, card) and not p.chained then
table.insert(self.use_tos, p.id)
end
end
if #self.use_tos < 2 then
self.use_tos = {}
else
self.use_id = card.id
end
end
fk.ai_use_play.recast = function(self, card)
if self.command == "PlayCard" then
self.use_id = card.id
self.special_skill = "recast"
end
end
fk.ai_card.fire_attack = {
intention = 90, -- 身份值
value = 3, -- 卡牌价值
priority = 4 -- 使用优先值
}
fk.ai_use_play.fire_attack = function(self, card)
self:sort(self.enemies)
for _, p in ipairs(self.enemies) do
if card.skill:targetFilter(p.id, self.use_tos, {}, card) and #self.player:getCardIds("h") > 2 then
self.use_id = card.id
table.insert(self.use_tos, p.id)
end
end
end
fk.ai_dis_card.fire_attack_skill = function(self, min_num, num, include_equip, cancelable, pattern, prompt)
local use = self:eventData("UseCard")
for _, p in ipairs(TargetGroup:getRealTargets(use.tos)) do
if self:isEnemie(p) then
local cards = table.map(self.player:getCardIds("h"), function(id)
return Fk:getCardById(id)
end)
local exp = Exppattern:Parse(pattern)
cards = table.filter(cards, function(c)
return exp:match(c)
end)
if #cards > 0 then
self:sortValue(cards)
return { cards[1].id }
end
end
end
end
fk.ai_nullification.fire_attack = function(self, card, to, from, positive)
if positive then
if self:isFriend(to) and #to:getCardIds("h") > 0 and #from:getCardIds("h") > 0 then
if #self.avail_cards > 1 or self:isWeak(to) or to.id == self.player.id then
self.use_id = self.avail_cards[1]
end
end
else
if self:isEnemie(to) and #to:getCardIds("h") > 0 and #from:getCardIds("h") > 1 then
if #self.avail_cards > 1 or self:isWeak(to) then
self.use_id = self.avail_cards[1]
end
end
end
end
fk.ai_card.fire_attack = {
intention = 120, -- 身份值
value = 2, -- 卡牌价值
priority = 2 -- 使用优先值
}
fk.ai_use_play.supply_shortage = function(self, card)
self:sort(self.enemies)
for _, p in ipairs(self.enemies) do
if card.skill:targetFilter(p.id, self.use_tos, {}, card) and not p.chained then
self.use_id = card.id
table.insert(self.use_tos, p.id)
end
end
end
fk.ai_nullification.supply_shortage = function(self, card, to, from, positive)
if positive then
if self:isFriend(to) then
if #self.avail_cards > 1 or self:isWeak(to) or to.id == self.player.id then
self.use_id = self.avail_cards[1]
end
end
else
if self:isEnemie(to) then
if #self.avail_cards > 1 or self:isWeak(to) then
self.use_id = self.avail_cards[1]
end
end
end
end
fk.ai_card.supply_shortage = {
intention = 130, -- 身份值
value = 2, -- 卡牌价值
priority = 1 -- 使用优先值
}
fk.ai_skill_invoke["#fan_skill"] = function(self)
local use = self:eventData("UseCard")
for _, p in ipairs(TargetGroup:getRealTargets(use.tos)) do
if not self:isFriend(p) then
return true
end
end
end

View File

@ -10,7 +10,7 @@ local function rewardAndPunish(killer, victim)
end end
end end
GameRule = fk.CreateTriggerSkill{ GameRule = fk.CreateTriggerSkill {
name = "game_rule", name = "game_rule",
events = { events = {
fk.GamePrepared, fk.GamePrepared,
@ -40,27 +40,52 @@ GameRule = fk.CreateTriggerSkill{
[fk.AskForPeaches] = function() [fk.AskForPeaches] = function()
local dyingPlayer = room:getPlayerById(data.who) local dyingPlayer = room:getPlayerById(data.who)
while dyingPlayer.hp < 1 do while dyingPlayer.hp < 1 do
local cardNames = {"peach"} local cardNames = { "peach" }
local prompt = "#AskForPeaches:" .. dyingPlayer.id .. "::" .. tostring(1 - dyingPlayer.hp) local prompt = "#AskForPeaches:" .. data.who .. "::" .. tostring(1 - dyingPlayer.hp)
if player == dyingPlayer then if player == dyingPlayer then
table.insert(cardNames, "analeptic") table.insert(cardNames, "analeptic")
prompt = "#AskForPeachesSelf:::" .. tostring(1 - dyingPlayer.hp) prompt = "#AskForPeachesSelf:::" .. tostring(1 - dyingPlayer.hp)
end end
cardNames = table.filter(cardNames, function (cardName) cardNames = table.filter(cardNames, function(cardName)
local cardCloned = Fk:cloneCard(cardName) local cardCloned = Fk:cloneCard(cardName)
return not (player:prohibitUse(cardCloned) or player:isProhibited(dyingPlayer, cardCloned)) return not (player:prohibitUse(cardCloned) or player:isProhibited(dyingPlayer, cardCloned))
end) end)
if #cardNames == 0 then return end if #cardNames < 1 then return end
room:notifyMoveFocus(player, "peach")
local peach_use = room:askForUseCard(player, "peach", table.concat(cardNames, ",") , prompt) local useData = {
if not peach_use then break end user = player,
peach_use.tos = { {dyingPlayer.id} } cardName = "peach",
if peach_use.card.trueName == "analeptic" then pattern = table.concat(cardNames, ","),
peach_use.extra_data = peach_use.extra_data or {} extraData = Util.DummyTable
peach_use.extra_data.analepticRecover = true }
room.logic:trigger(fk.AskForCardUse, player, useData)
if type(useData.result) == "table" then
useData = useData.result
useData.tos = { { data.who } }
if useData.card.trueName == "analeptic" then
useData.extra_data = useData.extra_data or {}
useData.extra_data.analepticRecover = true
end
room:useCard(useData)
end
useData = { "peach", table.concat(cardNames, ","), prompt, true, Util.DummyTable }
while dyingPlayer.hp < 1 do
Fk.currentResponsePattern = table.concat(cardNames, ",")
local result = room:doRequest(player, "AskForUseCard", json.encode(useData))
Fk.currentResponsePattern = nil
if result ~= "" then
result = room:handleUseCardReply(player, result)
result.tos = { { data.who } }
if result.card.trueName == "analeptic" then
result.extra_data = result.extra_data or {}
result.extra_data.analepticRecover = true
end
room:useCard(result)
else
return
end
end end
room:useCard(peach_use)
end end
end, end,
[fk.AskForPeachesDone] = function() [fk.AskForPeachesDone] = function()
@ -101,7 +126,7 @@ GameRule = fk.CreateTriggerSkill{
} }
local fastchat_m = fk.CreateActiveSkill{ name = "fastchat_m" } local fastchat_m = fk.CreateActiveSkill { name = "fastchat_m" }
local fastchat_f = fk.CreateActiveSkill{ name = "fastchat_f" } local fastchat_f = fk.CreateActiveSkill { name = "fastchat_f" }
Fk:addSkill(fastchat_m) Fk:addSkill(fastchat_m)
Fk:addSkill(fastchat_f) Fk:addSkill(fastchat_f)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,227 @@
fk.ai_use_play.rende = function(self, skill)
for _, p in ipairs(self.friends_noself) do
if p.kingdom == "shu" and #self.player:getCardIds("h") >= self.player.hp then
self.use_id = {}
for _, cid in ipairs(self.player:getCardIds("h")) do
if #self.use_id < #self.player:getCardIds("h") / 2 then
table.insert(self.use_id, cid)
end
end
self.use_tos = { p.id }
return
end
end
for _, p in ipairs(self.friends_noself) do
if #self.player:getCardIds("h") >= self.player.hp then
self.use_id = {}
for _, cid in ipairs(self.player:getCardIds("h")) do
if #self.use_id < #self.player:getCardIds("h") / 2 then
table.insert(self.use_id, cid)
end
end
self.use_tos = { p.id }
return
end
end
end
fk.ai_card.jijiang = { priority = 10 }
fk.ai_use_play.lijian = function(self, skill)
local c = Fk:cloneCard("duel")
c.skillName = "lijian"
for _, p in ipairs(self.enemies) do
for _, pt in ipairs(self.enemies) do
if p.gender == General.Male and pt.gender == General.Male and p.id ~= pt.id and
c.skill:targetFilter(pt.id, {}, p.id, c) then
self.use_id = { self.player:getCardIds("he")[1] }
self.use_tos = { pt.id, p.id }
break
end
end
end
for _, p in ipairs(self.friends_noself) do
for _, pt in ipairs(self.enemies) do
if p.gender == General.Male and pt.gender == General.Male and p.id ~= pt.id and
c.skill:targetFilter(pt.id, {}, p.id, c) then
self.use_id = { self.player:getCardIds("he")[1] }
self.use_tos = { pt.id, p.id }
break
end
end
end
end
fk.ai_card.lijian = { priority = 2 }
fk.ai_use_play.zhiheng = function(self, skill)
local card_ids = {}
for _, h in ipairs(self.player:getCardIds("he")) do
if #card_ids < #self.player:getCardIds("he") / 2 then
table.insert(card_ids, h)
end
end
if #card_ids > 0 then
self.use_id = card_ids
end
end
fk.ai_use_play.kurou = function(self, skill)
if #self:getActives("peach") + self.player.hp > 1 then
local slash = Fk:cloneCard("slash")
if slash.skill:canUse(self.player, slash) and not self.player:prohibitUse(slash) then
fk.ai_use_play.slash(self, slash)
if self.use_id then
self.use_id = {}
self.use_tos = {}
end
end
end
end
fk.ai_use_play.fanjian = function(self, skill)
for _, p in ipairs(self.enemies) do
if #self.player:getCardIds("h") > 0 then
self.use_id = {}
table.insert(self.use_tos, p.id)
break
end
end
end
fk.ai_use_play.jieyin = function(self, skill)
for cs, p in ipairs(self.friends_noself) do
cs = self.player:getCardIds("h")
if #cs > 1 and p.gender == General.Male and p:isWounded() then
self.use_id = { cs[1], cs[2] }
table.insert(self.use_tos, p.id)
break
end
end
end
fk.ai_use_play.qingnang = function(self, skill)
for cs, p in ipairs(self.friends) do
cs = self.player:getCardIds("h")
if #cs > 0 and p:isWounded() then
self.use_id = { cs[1] }
table.insert(self.use_tos, p.id)
break
end
end
end
fk.ai_skill_invoke.jianxiong = true
fk.ai_card.hujia = { priority = 10 }
fk.ai_response_card["#hujia-ask"] = function(self, pattern, prompt, cancelable, data)
local to = self.room:getPlayerById(tonumber(prompt:split(":")[2]))
if to and self:isFriend(to) then
self:setUseId(pattern)
end
end
fk.ai_response_card["#jijiang-ask"] = fk.ai_response_card["#hujia-ask"]
fk.ai_skill_invoke.fankui = function(self, data, prompt)
local damage = self:eventData("Damage")
return damage and damage.from and not self:isFriend(damage.from)
end
fk.ai_response_card["#guicai-ask"] = function(self, pattern, prompt, cancelable, data)
local cards = table.map(self.player:getHandlyIds(true), function(id)
return Fk:getCardById(id)
end)
local id = self:getRetrialCardId(cards)
if id then
self.use_id = id
end
end
fk.ai_skill_invoke.ganglie = function(self, data, prompt)
local damage = self:eventData("Damage")
return damage and damage.from and not self:isFriend(damage.from)
end
fk.ai_skill_invoke.luoyi = function(self, data, prompt)
for _, p in ipairs(self.enemies) do
if #self:getActives("slash") > 0 and not self:isWeak() then
return true
end
end
end
fk.ai_skill_invoke.tiandu = true
fk.ai_skill_invoke.yiji = true
fk.ai_skill_invoke.luoshen = true
fk.ai_skill_invoke.guanxing = true
fk.ai_skill_invoke.tieqi = function(self, data, prompt)
local use = self:eventData("UseCard")
for _, p in ipairs(TargetGroup:getRealTargets(use.tos)) do
p = self.room:getPlayerById(p)
if self:isEnemie(p) then
return true
end
end
end
fk.ai_skill_invoke.jizhi = true
fk.ai_skill_invoke.keji = true
fk.ai_skill_invoke.yingzi = true
fk.ai_skill_invoke.lianying = true
fk.ai_skill_invoke.xiaoji = true
fk.ai_skill_invoke.biyue = true
fk.ai_choose_players.tuxi = function(self, targets, min_num, num, cancelable)
for _, pid in ipairs(targets) do
local p = self.room:getPlayerById(pid)
if self:isEnemie(p) and #self.use_tos < num then
table.insert(self.use_tos, pid)
end
end
end
fk.ai_use_skill.yiji_active = function(self, prompt, cancelable, data)
for _, p in ipairs(self.friends_noself) do
for c, cid in ipairs(self.player.yiji_ids) do
c = Fk:getCardById(cid)
if c:getMark("yiji") > 0 and c.skill:canUse(p, c) then
self.use_tos = { p.id }
self.use_id = json.encode {
skill = "yiji_active",
subcards = { cid }
}
return
end
end
end
end
fk.ai_choose_players.liuli = function(self, targets, min_num, num, cancelable)
for _, pid in ipairs(targets) do
local p = self.room:getPlayerById(pid)
if self:isEnemie(p) and #self.use_tos < num and #self.player:getCardIds("he") > 0 then
table.insert(self.use_tos, pid)
self.use_id = { self.player:getCardIds("he")[1] }
return
end
end
for _, pid in ipairs(targets) do
local p = self.room:getPlayerById(pid)
if not self:isFriend(p) and #self.use_tos < num and #self.player:getCardIds("he") > 0 then
table.insert(self.use_tos, pid)
self.use_id = { self.player:getCardIds("he")[1] }
return
end
end
end

View File

@ -1,6 +1,6 @@
-- SPDX-License-Identifier: GPL-3.0-or-later -- SPDX-License-Identifier: GPL-3.0-or-later
Fk:loadTranslationTable{ Fk:loadTranslationTable {
["standard_cards"] = "标+EX", ["standard_cards"] = "标+EX",
["unknown_card"] = '<font color="#B5BA00"><b>未知牌</b></font>', ["unknown_card"] = '<font color="#B5BA00"><b>未知牌</b></font>',
@ -50,13 +50,12 @@ Fk:loadTranslationTable{
["slash"] = "", ["slash"] = "",
[":slash"] = "基本牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:攻击范围内的一名其他角色<br /><b>效果</b>对目标角色造成1点伤害。", [":slash"] = "基本牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:攻击范围内的一名其他角色<br /><b>效果</b>对目标角色造成1点伤害。",
["#slash-jink"] = "%src 对你使用了杀,请使用 %arg 张闪", ["#slash-jink"] = "%src 对你使用了杀,请使用 %arg 张闪",
["#slash-jinks"] = "%src 对你使用了杀,需 %arg2 张闪,你还需使用 %arg 张闪",
["jink"] = "", ["jink"] = "",
[":jink"] = "基本牌<br /><b>时机</b>:【杀】对你生效时<br /><b>目标</b>:此【杀】<br /><b>效果</b>:抵消此【杀】的效果。", [":jink"] = "基本牌<br /><b>时机</b>:【杀】对你生效时<br /><b>目标</b>:此【杀】<br /><b>效果</b>:抵消此【杀】的效果。",
["peach"] = "", ["peach"] = "",
[":peach"] = "基本牌<br /><b>时机</b>:出牌阶段/一名角色处于濒死状态时<br /><b>目标</b>:已受伤的你/处于濒死状态的角色<br /><b>效果</b>目标角色回复1点体力。", [":peach"] = "基本牌<br /><b>时机</b>:出牌阶段/一名角色处于濒死状态时<br /><b>目标</b>:已受伤的你/处于濒死状态的角色<br /><b>效果</b>目标角色回复1点体力。",
["dismantlement"] = "过河拆桥", ["dismantlement"] = "过河拆桥",
[":dismantlement"] = "锦囊牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:一名区域内有牌的其他角色。<br /><b>效果</b>:你弃置目标角色区域内的一张牌。", [":dismantlement"] = "锦囊牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:一名区域内有牌的其他角色。<br /><b>效果</b>:你弃置目标角色区域内的一张牌。",
["dismantlement_skill"] = "过河拆桥", ["dismantlement_skill"] = "过河拆桥",
@ -67,26 +66,19 @@ Fk:loadTranslationTable{
["duel"] = "决斗", ["duel"] = "决斗",
[":duel"] = "锦囊牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:一名其他角色<br /><b>效果</b>由目标角色开始你与其轮流打出一张【杀】否则受到对方的1点伤害并结束此牌结算。", [":duel"] = "锦囊牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:一名其他角色<br /><b>效果</b>由目标角色开始你与其轮流打出一张【杀】否则受到对方的1点伤害并结束此牌结算。",
["collateral"] = "借刀杀人", ["collateral"] = "借刀杀人",
[":collateral"] = "锦囊牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>装备区内有武器牌且攻击范围内有【杀】的合法目标的一名其他角色A你需要选择一名A攻击范围内的【杀】的合法目标B<br /><b>效果</b>A须对B使用一张【杀】否则你获得A装备区内的武器牌。", [":collateral"] = "锦囊牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>装备区内有武器牌且攻击范围内有【杀】的合法目标的一名其他角色A你需要选择一名A攻击范围内的【杀】的合法目标B<br /><b>效果</b>A须对B使用一张【杀】否则你获得A装备区内的武器牌。",
["#collateral-slash"] = "借刀杀人:你需对 %dest 使用【杀】,否则 %src 获得你的武器", ["#collateral-slash"] = "借刀杀人:你需对 %dest 使用【杀】,否则 %src 获得你的武器",
["ex_nihilo"] = "无中生有", ["ex_nihilo"] = "无中生有",
[":ex_nihilo"] = "锦囊牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:你<br /><b>效果</b>:目标角色摸两张牌。", [":ex_nihilo"] = "锦囊牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:你<br /><b>效果</b>:目标角色摸两张牌。",
["nullification"] = "无懈可击", ["nullification"] = "无懈可击",
[":nullification"] = "锦囊牌<br /><b>时机</b>:锦囊牌对目标角色生效前,或一张【无懈可击】生效前<br /><b>目标</b>:该锦囊牌<br /><b>效果</b>:抵消该锦囊牌对该角色产生的效果,或抵消另一张【无懈可击】产生的效果。", [":nullification"] = "锦囊牌<br /><b>时机</b>:锦囊牌对目标角色生效前,或一张【无懈可击】生效前<br /><b>目标</b>:该锦囊牌<br /><b>效果</b>:抵消该锦囊牌对该角色产生的效果,或抵消另一张【无懈可击】产生的效果。",
["savage_assault"] = "南蛮入侵", ["savage_assault"] = "南蛮入侵",
[":savage_assault"] = "锦囊牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:所有其他角色<br /><b>效果</b>每名目标角色须打出一张【杀】否则受到1点伤害。", [":savage_assault"] = "锦囊牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:所有其他角色<br /><b>效果</b>每名目标角色须打出一张【杀】否则受到1点伤害。",
["archery_attack"] = "万箭齐发", ["archery_attack"] = "万箭齐发",
[":archery_attack"] = "锦囊牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:所有其他角色<br /><b>效果</b>每名目标角色须打出一张【闪】否则受到1点伤害。", [":archery_attack"] = "锦囊牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:所有其他角色<br /><b>效果</b>每名目标角色须打出一张【闪】否则受到1点伤害。",
["god_salvation"] = "桃园结义", ["god_salvation"] = "桃园结义",
[":god_salvation"] = "锦囊牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:所有角色<br /><b>效果</b>每名目标角色回复1点体力。", [":god_salvation"] = "锦囊牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:所有角色<br /><b>效果</b>每名目标角色回复1点体力。",
["amazing_grace"] = "五谷丰登", ["amazing_grace"] = "五谷丰登",
[":amazing_grace"] = "锦囊牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:所有角色<br /><b>效果</b>:你亮出牌堆顶等于角色数的牌,每名目标角色获得其中一张牌,然后将其余的牌置入弃牌堆。", [":amazing_grace"] = "锦囊牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:所有角色<br /><b>效果</b>:你亮出牌堆顶等于角色数的牌,每名目标角色获得其中一张牌,然后将其余的牌置入弃牌堆。",
["amazing_grace_skill"] = "五谷选牌", ["amazing_grace_skill"] = "五谷选牌",
@ -94,16 +86,12 @@ Fk:loadTranslationTable{
["lightning"] = "闪电", ["lightning"] = "闪电",
[":lightning"] = "延时锦囊牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:你<br /><b>效果</b>将此牌置于目标角色判定区内。其判定阶段进行判定若结果为黑桃2-9其受到3点雷电伤害并将【闪电】置入弃牌堆否则将【闪电】移动至其下家判定区内。", [":lightning"] = "延时锦囊牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:你<br /><b>效果</b>将此牌置于目标角色判定区内。其判定阶段进行判定若结果为黑桃2-9其受到3点雷电伤害并将【闪电】置入弃牌堆否则将【闪电】移动至其下家判定区内。",
["indulgence"] = "乐不思蜀", ["indulgence"] = "乐不思蜀",
[":indulgence"] = "延时锦囊牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:一名其他角色<br /><b>效果</b>:将此牌置于目标角色判定区内。其判定阶段进行判定:若结果不为红桃,其跳过出牌阶段。然后将【乐不思蜀】置入弃牌堆。", [":indulgence"] = "延时锦囊牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:一名其他角色<br /><b>效果</b>:将此牌置于目标角色判定区内。其判定阶段进行判定:若结果不为红桃,其跳过出牌阶段。然后将【乐不思蜀】置入弃牌堆。",
["crossbow"] = "诸葛连弩", ["crossbow"] = "诸葛连弩",
[":crossbow"] = "装备牌·武器<br /><b>攻击范围</b><br /><b>武器技能</b>:锁定技。你于出牌阶段内使用【杀】无次数限制。", [":crossbow"] = "装备牌·武器<br /><b>攻击范围</b><br /><b>武器技能</b>:锁定技。你于出牌阶段内使用【杀】无次数限制。",
["qinggang_sword"] = "青釭剑", ["qinggang_sword"] = "青釭剑",
[":qinggang_sword"] = "装备牌·武器<br /><b>攻击范围</b><br /><b>武器技能</b>:锁定技。你的【杀】无视目标角色的防具。", [":qinggang_sword"] = "装备牌·武器<br /><b>攻击范围</b><br /><b>武器技能</b>:锁定技。你的【杀】无视目标角色的防具。",
["ice_sword"] = "寒冰剑", ["ice_sword"] = "寒冰剑",
[":ice_sword"] = "装备牌·武器<br /><b>攻击范围</b><br /><b>武器技能</b>:每当你使用【杀】对目标角色造成伤害时,若该角色有牌,你可以防止此伤害,然后依次弃置其两张牌。", [":ice_sword"] = "装备牌·武器<br /><b>攻击范围</b><br /><b>武器技能</b>:每当你使用【杀】对目标角色造成伤害时,若该角色有牌,你可以防止此伤害,然后依次弃置其两张牌。",
["#ice_sword_skill"] = "寒冰剑", ["#ice_sword_skill"] = "寒冰剑",
@ -130,7 +118,6 @@ Fk:loadTranslationTable{
["halberd"] = "方天画戟", ["halberd"] = "方天画戟",
[":halberd"] = "装备牌·武器<br /><b>攻击范围</b><br /><b>武器技能</b>:锁定技。你使用最后的手牌【杀】可以额外选择至多两名目标。", [":halberd"] = "装备牌·武器<br /><b>攻击范围</b><br /><b>武器技能</b>:锁定技。你使用最后的手牌【杀】可以额外选择至多两名目标。",
["kylin_bow"] = "麒麟弓", ["kylin_bow"] = "麒麟弓",
[":kylin_bow"] = "装备牌·武器<br /><b>攻击范围</b><br /><b>武器技能</b>:每当你使用【杀】对目标角色造成伤害时,你可以弃置其装备区内的一张坐骑牌。", [":kylin_bow"] = "装备牌·武器<br /><b>攻击范围</b><br /><b>武器技能</b>:每当你使用【杀】对目标角色造成伤害时,你可以弃置其装备区内的一张坐骑牌。",
["#kylin_bow_skill"] = "麒麟弓", ["#kylin_bow_skill"] = "麒麟弓",

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,436 @@
fk.ai_card.slash = {
intention = 100, -- 身份值
value = 4, -- 卡牌价值
priority = 2.5 -- 使用优先值
}
fk.ai_card.peach = {
intention = -150,
value = 10,
priority = 0.5
}
fk.ai_card.dismantlement = {
intention = function(self, card, from)
if #self.player.player_cards[Player.Judge] < 1 then
return 80
elseif fk.ai_role[from.id] == "neutral" then
return 30
end
end,
value = 3.5,
priority = 10.5
}
fk.ai_card.snatch = {
intention = function(self, card, from)
if #self.player.player_cards[Player.Judge] < 1 then
return 80
elseif fk.ai_role[from.id] == "neutral" then
return 30
end
end,
value = 4.5,
priority = 10.4
}
fk.ai_card.duel = {
intention = 120,
value = 4.5,
priority = 3.5
}
fk.ai_card.collateral = {
intention = 20,
value = 3,
priority = 4.5
}
fk.ai_card.ex_nihilo = {
intention = -200,
value = 8,
priority = 10
}
fk.ai_card.savage_assault = {
intention = 20,
value = 2,
priority = 4
}
fk.ai_card.archery_attack = {
intention = 30,
value = 2,
priority = 3
}
fk.ai_card.god_salvation = {
intention = function(self, card, from)
if self.player.hp ~= self.player.maxHp then
return -45
end
end,
value = 1.5,
priority = 4
}
fk.ai_card.amazing_grace = {
intention = -30,
value = 2,
priority = 2
}
fk.ai_card.indulgence = {
intention = 150,
value = -1,
priority = 2
}
local function slashEeffect(slash, to)
for _, s in ipairs(to:getAllSkills()) do
if s.name == "#vine_skill" then
if slash.name == "slash" then
return
end
end
if s.name == "#nioh_shield_skill" then
if slash.color == Card.Black then
return
end
end
end
return true
end
fk.ai_use_play.slash = function(self, card)
self:sort(self.enemies)
for _, p in ipairs(self.enemies) do
if card.skill:targetFilter(p.id, self.use_tos, {}, card) and slashEeffect(card, p) then
self.use_id = card.id
table.insert(self.use_tos, p.id)
end
end
end
fk.ai_askuse_card["#slash-jink"] = function(self, pattern, prompt, cancelable, extra_data)
local act = self:getActives(pattern)
if tonumber(prompt:split(":")[4]) > #act then
return
end
local cards =
table.map(
self.player:getCardIds("&he"),
function(id)
return Fk:getCardById(id)
end
)
self:sortValue(cards)
for _, sth in ipairs(act) do
if sth:isInstanceOf(Card) then
self.use_id = sth.id
break
else
local selected = {}
for _, c in ipairs(cards) do
if sth.cardFilter(sth, c.id, selected) then
table.insert(selected, c.id)
end
end
local tc = sth.viewAs(sth, selected)
if tc and tc:matchPattern(pattern) then
self.use_id =
json.encode {
skill = sth.name,
subcards = selected
}
break
end
end
end
end
fk.ai_askuse_card["#slash-jinks"] = fk.ai_askuse_card["#slash-jink"]
fk.ai_use_play.snatch = function(self, card)
for _, p in ipairs(self.friends_noself) do
if card.skill:targetFilter(p.id, self.use_tos, {}, card) and #p:getCardIds("j") > 0 then
self.use_id = card.id
table.insert(self.use_tos, p.id)
end
end
self:sort(self.enemies)
for _, p in ipairs(self.enemies) do
if card.skill:targetFilter(p.id, self.use_tos, {}, card) and #p:getCardIds("he") > 0 then
self.use_id = card.id
table.insert(self.use_tos, p.id)
end
end
end
fk.ai_nullification.snatch = function(self, card, to, from, positive)
if positive then
if self:isFriend(to) and not self:isFriend(from) and fk.ai_role[from.id] ~= "neutral" then
if #self.avail_cards > 1 or self:isWeak(to) or to.id == self.player.id then
self.use_id = self.avail_cards[1]
end
end
else
if self:isEnemie(to) and self:isEnemie(from) then
if #self.avail_cards > 1 or self:isWeak(to) then
self.use_id = self.avail_cards[1]
end
end
end
end
fk.ai_use_play.dismantlement = function(self, card)
for _, p in ipairs(self.friends_noself) do
if card.skill:targetFilter(p.id, self.use_tos, {}, card) and #p:getCardIds("j") > 0 then
self.use_id = card.id
table.insert(self.use_tos, p.id)
end
end
self:sort(self.enemies)
for _, p in ipairs(self.enemies) do
if card.skill:targetFilter(p.id, self.use_tos, {}, card) and #p:getCardIds("he") > 0 then
self.use_id = card.id
table.insert(self.use_tos, p.id)
end
end
end
fk.ai_nullification.dismantlement = function(self, card, to, from, positive)
if positive then
if self:isFriend(to) and not self:isFriend(from) and fk.ai_role[from.id] ~= "neutral" then
if #self.avail_cards > 1 or self:isWeak(to) or to.id == self.player.id then
self.use_id = self.avail_cards[1]
end
end
else
if self:isEnemie(to) and self:isEnemie(from) then
if #self.avail_cards > 1 or self:isWeak(to) then
self.use_id = self.avail_cards[1]
end
end
end
end
fk.ai_use_play.indulgence = function(self, card)
self:sort(self.enemies, nil, true)
for _, p in ipairs(self.enemies) do
if card.skill:targetFilter(p.id, self.use_tos, {}, card) then
self.use_id = card.id
table.insert(self.use_tos, p.id)
end
end
end
fk.ai_nullification.indulgence = function(self, card, to, from, positive)
if positive then
if self:isFriend(to) then
if #self.avail_cards > 1 or self:isWeak(to) or to.id == self.player.id then
self.use_id = self.avail_cards[1]
end
end
else
if self:isEnemie(to) then
if #self.avail_cards > 1 or self:isWeak(to) then
self.use_id = self.avail_cards[1]
end
end
end
end
fk.ai_use_play.collateral = function(self, card)
local max = (card.skill:getMaxTargetNum(self.player, card) - 1) * 2
self:sort(self.enemies)
for _, p in ipairs(self.enemies) do
if #self.use_tos < max and card.skill:targetFilter(p.id, {}, {}, card) then
for _, pt in ipairs(self.enemies) do
if p ~= pt and p:inMyAttackRange(pt) then
table.insert(self.use_tos, p.id)
table.insert(self.use_tos, pt.id)
self.use_id = card.id
break
end
end
end
end
for _, p in ipairs(self.friends_noself) do
if #self.use_tos < max and card.skill:targetFilter(p.id, {}, {}, card) then
for _, pt in ipairs(self.enemies) do
if p ~= pt and p:inMyAttackRange(pt) then
table.insert(self.use_tos, p.id)
table.insert(self.use_tos, pt.id)
self.use_id = card.id
break
end
end
end
end
end
fk.ai_nullification.collateral = function(self, card, to, from, positive)
if positive then
if self:isFriend(to) and self:isEnemie(from) then
if #self.avail_cards > 1 or self:isWeak(to) or to.id == self.player.id then
self.use_id = self.avail_cards[1]
end
end
end
end
fk.ai_nullification.ex_nihilo = function(self, card, to, from, positive)
if positive then
if self:isEnemie(to) then
if #self.avail_cards > 1 or self:isWeak(to) then
self.use_id = self.avail_cards[1]
end
end
else
if self:isFriend(to) then
if #self.avail_cards > 1 or self:isWeak(to) or to.id == self.player.id then
self.use_id = self.avail_cards[1]
end
end
end
end
fk.ai_nullification.savage_assault = function(self, card, to, from, positive)
if positive then
if self:isFriend(to) then
if #self.avail_cards > 1 or self:isWeak(to) or to.id == self.player.id then
self.use_id = self.avail_cards[1]
end
end
else
if self:isEnemie(to) then
if #self.avail_cards > 1 or self:isWeak(to) then
self.use_id = self.avail_cards[1]
end
end
end
end
fk.ai_nullification.archery_attack = function(self, card, to, from, positive)
if positive then
if self:isFriend(to) then
if #self.avail_cards > 1 or self:isWeak(to) or to.id == self.player.id then
self.use_id = self.avail_cards[1]
end
end
else
if self:isEnemie(to) then
if #self.avail_cards > 1 or self:isWeak(to) then
self.use_id = self.avail_cards[1]
end
end
end
end
fk.ai_nullification.god_salvation = function(self, card, to, from, positive)
if positive then
if self:isEnemie(to) and to.hp ~= to.maxHp then
if #self.avail_cards > 1 or self:isWeak(to) then
self.use_id = self.avail_cards[1]
end
end
else
if self:isFriend(to) and to.hp ~= to.maxHp then
if #self.avail_cards > 1 or self:isWeak(to) or to.id == self.player.id then
self.use_id = self.avail_cards[1]
end
end
end
end
fk.ai_use_play.god_salvation = function(self, card)
local can = 0
for _, p in ipairs(self.enemies) do
if p:isWounded()
then
can = can - 1
if self:isWeak(p)
then
can = can - 1
end
end
end
for _, p in ipairs(self.friends) do
if p:isWounded()
then
can = can + 1
if self:isWeak(p)
then
can = can + 1
end
end
end
self.use_id = can > 0 and card.id
end
fk.ai_use_play.amazing_grace = function(self, card)
self.use_id = #self.player:getCardIds("&h") <= self.player.hp and card.id
end
fk.ai_use_play.ex_nihilo = function(self, card)
self.use_id = card.id
end
fk.ai_use_play.lightning = function(self, card)
self.use_id = #self.enemies > #self.friends and card.id
end
fk.ai_use_play.peach = function(self, card)
if self.command == "PlayCard" then
self.use_id = self.player.hp ~= self.player.maxHp and self.player.hp < #self.player:getCardIds("h") and card.id
else
for _, p in ipairs(self.friends) do
if p.dying then
self.use_id = card.id
self.use_tos = { p.id }
break
end
end
end
end
fk.ai_use_play.duel = function(self, card)
self:sort(self.enemies)
for _, p in ipairs(self.enemies) do
if card.skill:targetFilter(p.id, self.use_tos, {}, card) then
self.use_id = card.id
table.insert(self.use_tos, p.id)
end
end
end
fk.ai_skill_invoke["#ice_sword_skill"] = function(self)
local damage = self:eventData("Damage")
return self:isFriend(damage.to) or not self:isWeak(damage.to) and #damage.to:getCardIds("e") > 1
end
fk.ai_skill_invoke["#double_swords_skill"] = function(self)
local use = self:eventData("UseCard")
for _, p in ipairs(TargetGroup:getRealTargets(use.tos)) do
if not self:isFriend(p) and self.room:getPlayerById(p).gender ~= self.player.gender then
return true
end
end
end
fk.ai_dis_card["#double_swords_skill"] = function(self, min_num, num, include_equip, cancelable, pattern, prompt)
local use = self:eventData("UseCard")
return self:isEnemie(use.from) and { self.player:getCardIds("h")[1] }
end
fk.ai_dis_card["#axe_skill"] = function(self, min_num, num, include_equip, cancelable, pattern, prompt)
local ids = {}
for _, cid in ipairs(self.player:getCardIds("he")) do
if Fk:getCardById(cid):matchPattern(pattern) then
table.insert(ids, cid)
end
if
#ids >= min_num and self:isEnemie(self.player.axe_to) and
(self:isWeak(self.player.axe_to) or #self.player:getCardIds("he") > 3)
then
return ids
end
end
end
fk.ai_skill_invoke["#kylin_bow_skill"] = function(self)
local damage = self:eventData("Damage")
return not self:isFriend(damage.to)
end
fk.ai_skill_invoke["#eight_diagram_skill"] = true

View File

@ -90,12 +90,6 @@ local control = fk.CreateActiveSkill{
-- room:swapSeat(from, to) -- room:swapSeat(from, to)
for _, pid in ipairs(effect.tos) do for _, pid in ipairs(effect.tos) do
local to = room:getPlayerById(pid) local to = room:getPlayerById(pid)
-- p(room:askForPoxi(from, "test", {
-- { "你自己", from:getCardIds "h" },
-- { "对方", to:getCardIds "h" },
-- }))
-- room:setPlayerMark(from, "@$a", {1,2,3})
-- room:setPlayerMark(from, "@$b", {'slash','duel','axe'})
if to:getMark("mouxushengcontrolled") == 0 then if to:getMark("mouxushengcontrolled") == 0 then
room:addPlayerMark(to, "mouxushengcontrolled") room:addPlayerMark(to, "mouxushengcontrolled")
from:control(to) from:control(to)
@ -130,22 +124,6 @@ local control = fk.CreateActiveSkill{
-- room:useVirtualCard("slash", nil, from, room:getOtherPlayers(from), self.name, true) -- room:useVirtualCard("slash", nil, from, room:getOtherPlayers(from), self.name, true)
end, end,
} }
--[[
Fk:addPoxiMethod{
name = "test",
card_filter = function(to_select, selected, data)
local s = Fk:getCardById(to_select).suit
for _, id in ipairs(selected) do
if Fk:getCardById(id).suit == s then return false end
end
return true
end,
feasible = function(selected, data)
return #selected == 0 or #selected == 4
end,
prompt = "魄袭:选你们俩手牌总共四个花色,或者不选直接按确定按钮"
}
--]]
local test_vs = fk.CreateViewAsSkill{ local test_vs = fk.CreateViewAsSkill{
name = "test_vs", name = "test_vs",
pattern = "nullification", pattern = "nullification",