Modify game core (#294)

- 新增船新“休整”机制;
- 修改作废逻辑,并可在当前响应读条禁用该技能(出牌阶段空闲时间点尚未完成限制);
- 修复锁视技的相关bug,其cardFilter新增标识是否为判定的参数;
- 将护甲扣减融合进体力扣减流程,为伤害流程增加“虚拟伤害”概念,为伤害流程增加“造成过伤害”标识id以供记录搜索使用;
- 为变将新增可删除副将。

---------

Co-authored-by: notify <notify-ctrl@qq.com>
This commit is contained in:
Ho-spair 2023-12-10 18:55:16 +08:00 committed by GitHub
parent 94c1107c2e
commit cec18e0614
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 629 additions and 228 deletions

20
Fk/Common/Avatar.qml Normal file
View File

@ -0,0 +1,20 @@
import QtQuick
import Fk
Image {
property string general
width: 64
height: 64
source: SkinBank.getGeneralExtraPic(general, "avatar/") ?? SkinBank.getGeneralPicture(general)
// sourceSize.width: 250
// sourceSize.height: 292
property bool useSmallPic: !!SkinBank.getGeneralExtraPic(general, "avatar/")
sourceClipRect: useSmallPic ? undefined : Qt.rect(61, 0, 128, 128)
Rectangle {
anchors.fill: parent
color: "transparent"
border.width: 1
}
}

238
Fk/Common/AvatarChatBox.qml Normal file
View File

@ -0,0 +1,238 @@
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Fk.Pages
Rectangle {
property bool isLobby: false
function append(chatter, data) {
let general = data.general;
let avatar;
if (general == "__server") {
general = "";
avatar = "__server"
} else if (!roomScene.getPhoto(data.sender)) {
avatar = "__observer";
}
chatLogBox.append({
avatar: data.general || roomScene.getPhoto(data.sender)?.general || avatar || "unknown",
general: general,
msg: data.msg,
userName: data.userName,
time: data.time,
isSelf: data.sender === Self.id,
})
}
function loadSkills() {
for (let i = 1; i <= 16; i++) {
skills.append({ name: "fastchat_m", idx: i });
}
}
Timer {
id: opTimer
interval: 1500
}
Component {
id: avatarDelegate
Item {
width: chatLogBox.width
height: childrenRect.height
Avatar {
id: avatarPic
width: 36
height: 36
general: avatar
anchors.top: parent.top
anchors.topMargin: 8
anchors.left: isSelf ? undefined : parent.left
anchors.right: !isSelf ? undefined : parent.right
}
Text {
id: unameLbl
anchors.left: isSelf ? undefined : avatarPic.right
anchors.right: !isSelf ? undefined : avatarPic.left
anchors.margins: 6
font.pixelSize: 14
text: userName + (general ? (" (" + Backend.translate(general) + ")") : "")
+ ' <font color="grey">[' + time + "]</font>"
}
Rectangle {
anchors.left: isSelf ? undefined : avatarPic.right
anchors.right: !isSelf ? undefined : avatarPic.left
anchors.margins: 4
anchors.top: unameLbl.bottom
width: Math.min(parent.width - 80, childrenRect.width + 12)
height: childrenRect.height + 12
radius: 8
color: isSelf ? "lightgreen" : "lightsteelblue"
Text {
width: Math.min(contentWidth, parent.parent.width - 80 - 12)
x: 6; y: 6
text: msg
wrapMode: Text.WrapAnywhere
font.family: fontLibian.name
font.pixelSize: 16
}
}
TapHandler {
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.NoButton
gesturePolicy: TapHandler.WithinBounds
onTapped: chatLogBox.currentIndex = index;
}
}
}
ColumnLayout {
anchors.fill: parent
spacing: 0
Item {
Layout.fillWidth: true
Layout.fillHeight: true
LogEdit {
id: chatLogBox
anchors.fill: parent
anchors.margins: 10
delegate: avatarDelegate
//font.pixelSize: 14
}
}
GridView {
id: emojiSelector
Layout.fillWidth: true
Layout.preferredHeight: 120
cellHeight: 48
cellWidth: 48
model: 59
visible: false
clip: true
delegate: ItemDelegate {
Image {
height: 32; width: 32
anchors.centerIn: parent
source: "../../image/emoji/" + index
}
onClicked: chatEdit.insert(chatEdit.cursorPosition, "{emoji" + index + "}");
}
}
ListView {
id: soundSelector
Layout.fillWidth: true
Layout.preferredHeight: 180
visible: false
clip: true
ScrollBar.vertical: ScrollBar {}
model: ListModel {
id: skills
}
// onVisibleChanged: {skills.clear(); loadSkills();}
delegate: ItemDelegate {
width: soundSelector.width
height: 30
text: Backend.translate("$" + name + (idx ? idx.toString() : ""))
onClicked: {
opTimer.start();
const general = roomScene.getPhoto(Self.id).general;
let skill = "fastchat_m";
if (general !== "") {
const data = JSON.parse(Backend.callLuaFunction("GetGeneralDetail", [general]));
const gender = data.gender;
if (gender !== 1) {
skill = "fastchat_f";
}
}
ClientInstance.notifyServer(
"Chat",
JSON.stringify({
type: isLobby ? 1 : 2,
msg: "$" + skill + ":" + idx
})
);
soundSelector.visible = false;
}
}
}
RowLayout {
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 28
color: "#040403"
radius: 3
border.width: 1
border.color: "#A6967A"
TextInput {
id: chatEdit
anchors.fill: parent
anchors.margins: 6
color: "white"
clip: true
font.pixelSize: 14
maximumLength: 300
onAccepted: {
if (text != "") {
ClientInstance.notifyServer(
"Chat",
JSON.stringify({
type: isLobby ? 1 : 2,
msg: text
})
);
text = "";
}
}
}
}
MetroButton {
id: soundBtn
text: "🗨️"
visible: !isLobby
enabled: !opTimer.running;
onClicked: {
emojiSelector.visible = false;
soundSelector.visible = !soundSelector.visible;
}
}
MetroButton {
id: emojiBtn
text: "😃"
onClicked: {
soundSelector.visible = false;
emojiSelector.visible = !emojiSelector.visible;
}
}
MetroButton {
text: "✔️"
enabled: !opTimer.running;
onClicked: {
opTimer.start();
chatEdit.accepted();
}
}
}
}
Component.onCompleted: {
loadSkills();
}
}

View File

@ -9,7 +9,7 @@ Rectangle {
property bool isLobby: false
function append(chatter) {
chatLogBox.append(chatter)
chatLogBox.append({ logText: chatter })
}
function loadSkills() {

View File

@ -33,6 +33,8 @@ ListView {
font.pixelSize: 16
TapHandler {
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.NoButton
gesturePolicy: TapHandler.WithinBounds
onTapped: root.currentIndex = index;
}
}
@ -43,9 +45,9 @@ ListView {
onClicked: root.currentIndex = logModel.count - 1;
}
function append(text) {
function append(data) {
const autoScroll = root.currentIndex === logModel.count - 1;
logModel.append({ logText: text });
logModel.append(data);
if (autoScroll) {
root.currentIndex = logModel.count - 1;
}

View File

@ -1,3 +1,5 @@
module Fk.Common
ChatBox 1.0 ChatBox.qml
LogEdit 1.0 LogEdit.qml
Avatar 1.0 Avatar.qml
AvatarChatBox 1.0 AvatarChatBox.qml

View File

@ -3,6 +3,7 @@
import QtQuick
import QtQuick.Layouts
import Fk
import Fk.Common
Item {
id: root
@ -20,19 +21,10 @@ Item {
RowLayout {
Item { Layout.preferredWidth: 16 }
Image {
Avatar {
Layout.preferredWidth: 64
Layout.preferredHeight: 64
source: SkinBank.getGeneralExtraPic(Self.avatar, "avatar/") ?? SkinBank.getGeneralPicture(Self.avatar)
// sourceSize.width: 250
// sourceSize.height: 292
sourceClipRect: sourceSize.width > 200 ? Qt.rect(61, 0, 128, 128) : undefined
Rectangle {
anchors.fill: parent
color: "transparent"
border.width: 1
}
general: Self.avatar
}
Item { Layout.preferredWidth: 8 }

View File

@ -5,6 +5,7 @@ import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Dialogs
import Fk
import Fk.Common
Item {
id: root
@ -59,21 +60,13 @@ Item {
width: root.width
height: 64
Image {
Avatar {
id: generalPic
width: 48; height: 48
anchors.top: parent.top
anchors.left: parent.left
anchors.margins: 8
width: 48
height: 48
source: SkinBank.getGeneralExtraPic(general, "avatar/") ?? SkinBank.getGeneralPicture(general)
sourceClipRect: sourceSize.width > 200 ? Qt.rect(61, 0, 128, 128) : undefined
Rectangle {
anchors.fill: parent
color: "transparent"
border.width: 1
}
general: general
}
ColumnLayout {

View File

@ -392,6 +392,7 @@ Item {
faceup: model.faceup
chained: model.chained
drank: model.drank
rest: model.rest
isOwner: model.isOwner
ready: model.ready
surrendered: model.surrendered
@ -855,7 +856,7 @@ Item {
}
Item {
visible: !config.replaying
ChatBox {
AvatarChatBox {
id: chat
anchors.fill: parent
}
@ -1121,7 +1122,7 @@ Item {
if (raw.msg.startsWith("$")) {
if (specialChat(pid, raw, raw.msg.slice(1))) return;
}
chat.append(msg);
chat.append(msg, raw);
const photo = Logic.getPhoto(pid);
if (photo === undefined) {
const user = raw.userName;
@ -1176,10 +1177,11 @@ Item {
Backend.playSound("./packages/" + extension + "/audio/death/" + g);
const m = Backend.translate("~" + g);
data.msg = m;
if (general === "")
chat.append(`[${time}] ${userName}: ${m}`);
chat.append(`[${time}] ${userName}: ${m}`, data);
else
chat.append(`[${time}] ${userName}(${general}): ${m}`);
chat.append(`[${time}] ${userName}(${general}): ${m}`, data);
const photo = Logic.getPhoto(pid);
if (photo === undefined) {
@ -1205,10 +1207,11 @@ Item {
}));
} catch (e) {}
const m = Backend.translate("$" + skill + (gene ? "_" + gene : "") + (idx ? idx.toString() : ""));
data.msg = m;
if (general === "")
chat.append(`[${time}] ${userName}: ${m}`);
chat.append(`[${time}] ${userName}: ${m}`, data);
else
chat.append(`[${time}] ${userName}(${general}): ${m}`);
chat.append(`[${time}] ${userName}(${general}): ${m}`, data)
const photo = Logic.getPhoto(pid);
if (photo === undefined) {
@ -1224,12 +1227,17 @@ Item {
}
function addToLog(msg) {
log.append(msg);
log.append({ logText: msg });
}
function sendDanmaku(msg) {
danmaku.sendLog(msg);
chat.append(msg);
chat.append(null, {
msg: msg,
general: "__server", // FIXME:
userName: "",
time: "Server",
});
}
function showDistance(show) {
@ -1327,6 +1335,7 @@ Item {
faceup: true,
chained: false,
drank: 0,
rest: 0,
isOwner: false,
ready: false,
surrendered: false,

View File

@ -1319,6 +1319,7 @@ callbacks["AskForUseCard"] = (jsonData) => {
const pattern = data[1];
const prompt = data[2];
const extra_data = data[4];
const disabledSkillNames = data[5];
if (extra_data != null) {
if (extra_data.effectTo !== Self.id && roomScene.skippedUseEventId.find(id => id === extra_data.useEventId)) {
doCancelButton();
@ -1336,6 +1337,7 @@ callbacks["AskForUseCard"] = (jsonData) => {
}
roomScene.responding_card = pattern;
roomScene.respond_play = false;
disabledSkillNames && (dashboard.disabledSkillNames = disabledSkillNames);
roomScene.state = "responding";
okButton.enabled = false;
cancelButton.enabled = true;
@ -1347,6 +1349,7 @@ callbacks["AskForResponseCard"] = (jsonData) => {
const cardname = data[0];
const pattern = data[1];
const prompt = data[2];
const disabledSkillNames = data[5];
if (prompt === "") {
roomScene.promptText = Backend.translate("#AskForResponseCard")
@ -1356,6 +1359,7 @@ callbacks["AskForResponseCard"] = (jsonData) => {
}
roomScene.responding_card = pattern;
roomScene.respond_play = true;
disabledSkillNames && (dashboard.disabledSkillNames = disabledSkillNames);
roomScene.state = "responding";
okButton.enabled = false;
cancelButton.enabled = true;

View File

@ -19,6 +19,8 @@ RowLayout {
property var expanded_piles: ({}) // name -> int[]
property var disabledSkillNames: []
signal cardSelected(var card)
Item { width: 5 }
@ -454,6 +456,11 @@ RowLayout {
// if cname is presented, we are responding use or play.
for (let i = 0; i < skillButtons.count; i++) {
const item = skillButtons.itemAt(i);
if (disabledSkillNames.includes(item.orig)) {
item.enabled = false;
continue;
}
const fitpattern = JSON.parse(Backend.callLuaFunction("SkillFitPattern", [item.orig, cname]));
const canresp = JSON.parse(Backend.callLuaFunction("SkillCanResponse", [item.orig, cardResponsing]));
item.enabled = fitpattern && canresp;
@ -462,11 +469,17 @@ RowLayout {
}
for (let i = 0; i < skillButtons.count; i++) {
const item = skillButtons.itemAt(i);
if (disabledSkillNames.includes(item.orig)) {
item.enabled = false;
continue;
}
item.enabled = JSON.parse(Backend.callLuaFunction("ActiveCanUse", [item.orig]));
}
}
function disableSkills() {
disabledSkillNames = [];
for (let i = 0; i < skillButtons.count; i++)
skillButtons.itemAt(i).enabled = false;
}

View File

@ -3,6 +3,7 @@
import QtQuick
import Qt5Compat.GraphicalEffects
import QtQuick.Controls
import QtQuick.Layouts
import Fk
import Fk.PhotoElement
@ -29,6 +30,7 @@ Item {
property bool faceup: true
property bool chained: false
property int drank: 0
property int rest: 0
property bool isOwner: false
property bool ready: false
property int winGame: 0
@ -269,6 +271,45 @@ Item {
opacity: 0.4 + Math.log(root.drank) * 0.12
}
ColumnLayout {
id: restRect
anchors.centerIn: photoMask
anchors.leftMargin: 20
visible: root.rest > 0
Text {
Layout.alignment: Qt.AlignCenter
text: "休整中"
font.family: fontLibian.name
font.pixelSize: 40
color: "white"
style: Text.Outline
textFormat: Text.RichText
}
Text {
Layout.alignment: Qt.AlignCenter
visible: root.rest > 0 && root.rest < 999
text: root.rest
font.family: fontLibian.name
font.pixelSize: 30
color: "white"
style: Text.Outline
textFormat: Text.RichText
}
Text {
Layout.alignment: Qt.AlignCenter
visible: root.rest > 0 && root.rest < 999
text: "轮次"
font.family: fontLibian.name
font.pixelSize: 28
color: "white"
style: Text.Outline
textFormat: Text.RichText
}
}
Rectangle {
id: winRateRect
width: 138; x: 31
@ -389,7 +430,7 @@ Item {
Image {
// id: saveme
visible: root.dead || root.dying || root.surrendered
visible: (root.dead && !root.rest) || root.dying || root.surrendered
source: {
if (root.dead) {
return SkinBank.getRoleDeathPic(root.role);

View File

@ -455,12 +455,12 @@ end
---@param player Player @ 和这张牌扯上关系的那名玩家
---@param data any @ 随意目前只用到JudgeStruct为了影响判定牌
function Engine:filterCard(id, player, data)
local card = self:getCardById(id, true)
if player == nil then
self.filtered_cards[id] = nil
return
end
local skills = player:getAllSkills()
local card = self:getCardById(id, true)
local filters = self:currentRoom().status_skills[FilterSkill] or Util.DummyTable
if #filters == 0 then
@ -475,7 +475,7 @@ function Engine:filterCard(id, player, data)
end
for _, f in ipairs(filters) do
if f:cardFilter(card, player) then
if f:cardFilter(card, player, type(data) == "table" and data.isJudgeEvent) then
local _card = f:viewAs(card, player)
_card.id = id
_card.skillName = f.name

View File

@ -27,10 +27,14 @@ end
---@param victim ServerPlayer @ 死者
---@return string @ 胜者阵营
function GameMode:getWinner(victim)
if victim.rest > 0 then
return ""
end
local room = victim.room
local winner = ""
local alive = table.filter(room.alive_players, function(p)
return not p.surrendered
local alive = table.filter(room.players, function(p)
return not p.surrendered and not (p.dead and p.rest == 0)
end)
if victim.role == "lord" then

View File

@ -86,6 +86,7 @@ function Player:initialize()
self.dying = false
self.dead = false
self.drank = 0
self.rest = 0
self.player_skills = {}
self.derivative_skills = {}
@ -570,9 +571,9 @@ end
---@param ignoreRemoved? boolean @ 忽略被移除
---@param num? integer @ 第几个默认1
---@return ServerPlayer
function Player:getNextAlive(ignoreRemoved, num)
function Player:getNextAlive(ignoreRemoved, num, ignoreRest)
if #Fk:currentRoom().alive_players == 0 then
return self
return self.rest > 0 and self.next.rest > 0 and self.next or self
end
local doNotIgnore = not ignoreRemoved
if doNotIgnore and table.every(Fk:currentRoom().alive_players, function(p) return p:isRemoved() end) then
@ -583,7 +584,7 @@ function Player:getNextAlive(ignoreRemoved, num)
num = num or 1
for _ = 1, num do
ret = ret.next
while ret.dead or (doNotIgnore and ret:isRemoved()) do
while (ret.dead and not ignoreRest) or (doNotIgnore and ret:isRemoved()) do
ret = ret.next
end
end

View File

@ -4,7 +4,7 @@
local FilterSkill = StatusSkill:subclass("FilterSkill")
---@param card Card
function FilterSkill:cardFilter(card, player)
function FilterSkill:cardFilter(card, player, isJudgeEvent)
return false
end

View File

@ -135,4 +135,6 @@ fk.BeforePropertyChange = 92
fk.PropertyChange = 93
fk.AfterPropertyChange = 94
fk.AfterPlayerRevived = 95
fk.NumOfEvents = 96

View File

@ -53,7 +53,11 @@ GameEvent.functions[GameEvent.Death] = function(self)
local room = self.room
local victim = room:getPlayerById(deathStruct.who)
victim.dead = true
if victim.rest <= 0 then
victim._splayer:setDied(true)
end
table.removeOne(room.alive_players, victim)
local logic = room.logic
@ -65,22 +69,26 @@ GameEvent.functions[GameEvent.Death] = function(self)
type = "#KillPlayer",
to = {killer.id},
from = victim.id,
arg = victim.role,
arg = (victim.rest > 0 and 'unknown' or victim.role),
}
else
room:sendLog{
type = "#KillPlayerWithNoKiller",
from = victim.id,
arg = victim.role,
arg = (victim.rest > 0 and 'unknown' or victim.role),
}
end
room:sendLogEvent("Death", {to = victim.id})
if victim.rest == 0 then
room:broadcastProperty(victim, "role")
end
room:broadcastProperty(victim, "dead")
victim.drank = 0
room:broadcastProperty(victim, "drank")
victim.shield = 0
room:broadcastProperty(victim, "shield")
logic:trigger(fk.GameOverJudge, victim, deathStruct)
logic:trigger(fk.Death, victim, deathStruct)
@ -88,3 +96,23 @@ GameEvent.functions[GameEvent.Death] = function(self)
logic:trigger(fk.Deathed, victim, deathStruct)
end
GameEvent.functions[GameEvent.Revive] = function(self)
local room = self.room
local player, sendLog, reason = table.unpack(self.data)
if not player.dead then return end
room:setPlayerProperty(player, "dead", false)
player._splayer:setDied(false)
room:setPlayerProperty(player, "dying", false)
room:setPlayerProperty(player, "hp", player.maxHp)
table.insertIfNeed(room.alive_players, player)
sendLog = (sendLog == nil) and true or sendLog
if sendLog then
room:sendLog { type = "#Revive", from = player.id }
end
reason = reason or ""
room.logic:trigger(fk.AfterPlayerRevived, player, { reason = reason })
end

View File

@ -161,8 +161,8 @@ GameEvent.functions[GameEvent.Round] = function(self)
p = room.current
GameEvent(GameEvent.Turn, p):exec()
if room.game_finished then break end
room.current = room.current:getNextAlive(true)
until p.seat >= p:getNextAlive(true).seat
room.current = room.current:getNextAlive(true, nil, true)
until p.seat >= p:getNextAlive(true, nil, true).seat
logic:trigger(fk.RoundEnd, p)
end
@ -194,6 +194,15 @@ GameEvent.prepare_funcs[GameEvent.Turn] = function(self)
local logic = room.logic
local player = room.current
if player.rest > 0 and player.rest < 999 then
room:setPlayerRest(player, player.rest - 1)
if player.rest == 0 and player.dead then
room:revivePlayer(player, true, "rest")
else
room:delay(50)
end
end
if player.dead then return true end
room:sendLog{ type = "$AppendSeparator" }
@ -312,9 +321,9 @@ GameEvent.functions[GameEvent.Phase] = function(self)
local result = room:doRequest(player, "PlayCard", player.id)
if result == "" then break end
local use = room:handleUseCardReply(player, result)
if use then
room:useCard(use)
local useResult = room:handleUseCardReply(player, result)
if type(useResult) == "table" then
room:useCard(useResult)
end
if player._play_phase_end then

View File

@ -48,17 +48,29 @@ GameEvent.functions[GameEvent.ChangeHp] = function(self)
damageEvent = damageStruct,
}
if reason == "damage" then
data.shield_lost = math.min(-num, player.shield)
data.num = num + data.shield_lost
end
if logic:trigger(fk.BeforeHpChanged, player, data) then
logic:breakEvent(false)
end
if reason == "damage" and data.shield_lost > 0 and not damageStruct.isVirtualDMG then
room:changeShield(player, -data.shield_lost)
end
if reason == "damage" then
sendDamageLog(room, damageStruct)
end
if not (reason == "damage" and (data.num == 0 or damageStruct.isVirtualDMG)) then
assert(not (data.reason == "recover" and data.num < 0))
player.hp = math.min(player.hp + data.num, player.maxHp)
room:broadcastProperty(player, "hp")
if reason == "damage" then
sendDamageLog(room, damageStruct)
elseif reason == "loseHp" then
if reason == "loseHp" then
room:sendLog{
type = "#LoseHP",
from = player.id,
@ -79,6 +91,7 @@ GameEvent.functions[GameEvent.ChangeHp] = function(self)
arg = player.hp,
arg2 = player.maxHp,
}
end
logic:trigger(fk.HpChanged, player, data)
@ -124,10 +137,12 @@ GameEvent.functions[GameEvent.Damage] = function(self)
local stages = {
{fk.PreDamage, damageStruct.from},
{fk.DamageCaused, damageStruct.from},
{fk.DamageInflicted, damageStruct.to},
}
if not damageStruct.isVirtualDMG then
table.insertTable(stages, { { fk.DamageCaused, damageStruct.from }, { fk.DamageInflicted, damageStruct.to } })
end
for _, struct in ipairs(stages) do
local event, player = table.unpack(struct)
if logic:trigger(event, player, damageStruct) or damageStruct.damage < 1 then
@ -141,6 +156,9 @@ GameEvent.functions[GameEvent.Damage] = function(self)
return false
end
damageStruct.dealtRecorderId = room.logic.specific_events_id[GameEvent.Damage]
room.logic.specific_events_id[GameEvent.Damage] = room.logic.specific_events_id[GameEvent.Damage] + 1
if damageStruct.card and damageStruct.damage > 0 then
local parentUseData = logic:getCurrentEvent():findParent(GameEvent.UseCard)
if parentUseData then
@ -155,22 +173,15 @@ GameEvent.functions[GameEvent.Damage] = function(self)
damageStruct.to:setChainState(false)
end
-- 先扣减护甲,再扣体力值
local shield_to_lose = math.min(damageStruct.damage, damageStruct.to.shield)
room:changeShield(damageStruct.to, -shield_to_lose)
if shield_to_lose < damageStruct.damage then
if not room:changeHp(
damageStruct.to,
shield_to_lose - damageStruct.damage,
-damageStruct.damage,
"damage",
damageStruct.skillName,
damageStruct) then
logic:breakEvent(false)
end
else
sendDamageLog(room, damageStruct)
end
stages = {
{fk.Damage, damageStruct.from},
@ -201,9 +212,15 @@ GameEvent.exit_funcs[GameEvent.Damage] = function(self)
type = "#ChainDamage",
from = p.id
}
local dmg = table.simpleClone(damageStruct)
dmg.to = p
dmg.chain = true
local dmg = {
from = damageStruct.from,
to = p,
damage = damageStruct.damage,
card = damageStruct.card,
skillName = damageStruct.skillName,
chain = true,
}
room:damage(dmg)
end
end

View File

@ -14,6 +14,7 @@ dofile "lua/server/events/hp.lua"
GameEvent.Dying = 6
GameEvent.Death = 7
GameEvent.Revive = 22
dofile "lua/server/events/death.lua"
GameEvent.MoveCards = 8
@ -64,6 +65,7 @@ local eventTranslations = {
[GameEvent.ChangeMaxHp] = "GameEvent.ChangeMaxHp",
[GameEvent.Dying] = "GameEvent.Dying",
[GameEvent.Death] = "GameEvent.Death",
[GameEvent.Revive] = "GameEvent.Revive",
[GameEvent.MoveCards] = "GameEvent.MoveCards",
[GameEvent.UseCard] = "GameEvent.UseCard",
[GameEvent.RespondCard] = "GameEvent.RespondCard",

View File

@ -5,6 +5,8 @@ GameEvent.functions[GameEvent.Judge] = function(self)
local room = self.room
local logic = room.logic
local who = data.who
data.isJudgeEvent = true
logic:trigger(fk.StartJudge, who, data)
data.card = data.card or Fk:getCardById(room:getNCards(1)[1])

View File

@ -39,12 +39,14 @@ GameEvent.functions[GameEvent.ChangeProperty] = function(self)
room:setPlayerProperty(player, "general", data.general)
end
if data.deputyGeneral and data.deputyGeneral ~= "" and data.deputyGeneral ~= player.deputyGeneral then
if data.deputyGeneral and data.deputyGeneral ~= player.deputyGeneral then
local originalDeputy = Fk.generals[player.deputyGeneral] or Fk.generals["blank_shibing"]
local originalSkills = originalDeputy and originalDeputy:getSkillNameList() or Util.DummyTable
table.insertTableIfNeed(skills, table.map(originalSkills, function(e)
return "-" .. e
end))
if data.deputyGeneral ~= "" then
local newDeputy = Fk.generals[data.deputyGeneral] or Fk.generals["blank_shibing"]
for _, name in ipairs(newDeputy:getSkillNameList()) do
local s = Fk.skills[name]
@ -52,6 +54,7 @@ GameEvent.functions[GameEvent.ChangeProperty] = function(self)
table.insertIfNeed(skills, name)
end
end
if data.sendLog then
room:sendLog{
type = "#ChangeHero",
@ -61,6 +64,8 @@ GameEvent.functions[GameEvent.ChangeProperty] = function(self)
arg3 = "deputyGeneral",
}
end
end
data.results["deputyChange"] = {player.deputyGeneral, data.deputyGeneral}
room:setPlayerProperty(player, "deputyGeneral", data.deputyGeneral)
end

View File

@ -152,10 +152,18 @@ GameEvent.functions[GameEvent.MoveCards] = function(self)
room:doBroadcastNotify("UpdateDrawPile", #room.draw_pile)
end
if not (data.to and data.toArea ~= Card.PlayerHand) then
Fk:filterCard(info.cardId, room:getPlayerById(data.to))
local beforeCard = Fk:getCardById(info.cardId)
if
realFromArea == Player.Equip and
beforeCard.type == Card.TypeEquip and
data.from ~= nil and
beforeCard.equip_skill
then
beforeCard:onUninstall(room, room:getPlayerById(data.from))
end
Fk:filterCard(info.cardId, room:getPlayerById(data.to))
local currentCard = Fk:getCardById(info.cardId)
for name, _ in pairs(currentCard.mark) do
if name:endsWith("-inhand") and
@ -174,15 +182,6 @@ GameEvent.functions[GameEvent.MoveCards] = function(self)
then
currentCard:onInstall(room, room:getPlayerById(data.to))
end
if
realFromArea == Player.Equip and
currentCard.type == Card.TypeEquip and
data.from ~= nil and
currentCard.equip_skill
then
currentCard:onUninstall(room, room:getPlayerById(data.from))
end
end
end
end

View File

@ -155,13 +155,29 @@ local sendCardEmotionAndLog = function(room, cardUseEvent)
}
end
end
end
GameEvent.functions[GameEvent.UseCard] = function(self)
local cardUseEvent = table.unpack(self.data)
local room = self.room
local logic = room.logic
if cardUseEvent.card.skill then
cardUseEvent.card.skill:onUse(room, cardUseEvent)
end
sendCardEmotionAndLog(room, cardUseEvent)
room:moveCardTo(cardUseEvent.card, Card.Processing, nil, fk.ReasonUse)
local card = cardUseEvent.card
local useCardIds = card:isVirtual() and card.subcards or { card.id }
if #useCardIds == 0 then return end
if cardUseEvent.tos and #cardUseEvent.tos > 0 and #cardUseEvent.tos <= 2 then
local tos = table.map(cardUseEvent.tos, function(e) return e[1] end)
room:sendFootnote(useCardIds, {
type = "##UseCardTo",
from = from,
from = cardUseEvent.from,
to = tos,
})
if card:isVirtual() then
@ -170,26 +186,12 @@ local sendCardEmotionAndLog = function(room, cardUseEvent)
else
room:sendFootnote(useCardIds, {
type = "##UseCard",
from = from,
from = cardUseEvent.from,
})
if card:isVirtual() then
room:sendCardVirtName(useCardIds, card.name)
end
end
end
GameEvent.functions[GameEvent.UseCard] = function(self)
local cardUseEvent = table.unpack(self.data)
local room = self.room
local logic = room.logic
room:moveCardTo(cardUseEvent.card, Card.Processing, nil, fk.ReasonUse)
if cardUseEvent.card.skill then
cardUseEvent.card.skill:onUse(room, cardUseEvent)
end
sendCardEmotionAndLog(room, cardUseEvent)
if logic:trigger(fk.PreCardUse, room:getPlayerById(cardUseEvent.from), cardUseEvent) then
logic:breakEvent()

View File

@ -23,6 +23,9 @@ function GameLogic:initialize(room)
self.all_game_events = {}
self.event_recorder = {}
self.current_event_id = 0
self.specific_events_id = {
[GameEvent.Damage] = 0,
}
self.role_table = {
{ "lord" },

View File

@ -1913,10 +1913,13 @@ function Room:handleUseCardReply(player, data)
end
use.card = c
skill:beforeUse(player, use)
self:useSkill(player, skill, Util.DummyFunc)
local rejectSkillName = skill:beforeUse(player, use)
if type(rejectSkillName) == "string" then
return rejectSkillName
end
return use
end
end
@ -2007,7 +2010,12 @@ function Room:askForUseCard(player, card_name, pattern, prompt, cancelable, extr
player.room:setPlayerMark(player, MarkEnum.BypassTimesLimit .. "-tmp", 0)
return askForUseCardData.result
else
local data = {card_name, pattern, prompt, cancelable, extra_data}
local useResult
local disabledSkillNames = {}
repeat
useResult = nil
local data = {card_name, pattern, prompt, cancelable, extra_data, disabledSkillNames}
Fk.currentResponsePattern = pattern
local result = self:doRequest(player, command, json.encode(data))
@ -2016,9 +2024,16 @@ function Room:askForUseCard(player, card_name, pattern, prompt, cancelable, extr
if result ~= "" then
player.room:setPlayerMark(player, MarkEnum.BypassDistancesLimit .. "-tmp", 0)
player.room:setPlayerMark(player, MarkEnum.BypassTimesLimit .. "-tmp", 0)
return self:handleUseCardReply(player, result)
useResult = self:handleUseCardReply(player, result)
if type(useResult) == "string" and useResult ~= "" then
table.insertIfNeed(disabledSkillNames, useResult)
end
end
until type(useResult) ~= "string"
return useResult
end
player.room:setPlayerMark(player, MarkEnum.BypassDistancesLimit .. "-tmp", 0)
player.room:setPlayerMark(player, MarkEnum.BypassTimesLimit .. "-tmp", 0)
return nil
@ -2056,18 +2071,29 @@ function Room:askForResponse(player, card_name, pattern, prompt, cancelable, ext
if eventData.result then
return eventData.result
else
local data = {card_name, pattern, prompt, cancelable, extra_data}
local useResult
local disabledSkillNames = {}
repeat
useResult = nil
local data = {card_name, pattern, prompt, cancelable, extra_data, disabledSkillNames}
Fk.currentResponsePattern = pattern
local result = self:doRequest(player, command, json.encode(data))
Fk.currentResponsePattern = nil
if result ~= "" then
local use = self:handleUseCardReply(player, result)
if use then
return use.card
useResult = self:handleUseCardReply(player, result)
if type(useResult) == "string" and useResult ~= "" then
table.insertIfNeed(disabledSkillNames, useResult)
end
end
until type(useResult) ~= "string"
if useResult then
return useResult.card
end
end
return nil
end
@ -2094,20 +2120,31 @@ function Room:askForNullification(players, card_name, pattern, prompt, cancelabl
prompt = prompt or ""
pattern = pattern or card_name
local useResult
local disabledSkillNames = {}
repeat
useResult = nil
self:notifyMoveFocus(self.alive_players, card_name)
self:doBroadcastNotify("WaitForNullification", "")
local data = {card_name, pattern, prompt, cancelable, extra_data}
local data = {card_name, pattern, prompt, cancelable, extra_data, disabledSkillNames}
Fk.currentResponsePattern = pattern
local winner = self:doRaceRequest(command, players, json.encode(data))
if winner then
local result = winner.client_reply
return self:handleUseCardReply(winner, result)
useResult = self:handleUseCardReply(winner, result)
if type(useResult) == "string" and useResult ~= "" then
table.insertIfNeed(disabledSkillNames, useResult)
end
end
Fk.currentResponsePattern = nil
return nil
until type(useResult) ~= "string"
return useResult
end
-- AG(a.k.a. Amazing Grace) functions
@ -2409,9 +2446,8 @@ local onAim = function(room, cardUseEvent, aimEventCollaborators)
firstTarget = false
if room.logic:trigger(stage, (stage == fk.TargetSpecifying or stage == fk.TargetSpecified) and room:getPlayerById(aimStruct.from) or room:getPlayerById(aimStruct.to), aimStruct) then
return false
end
room.logic:trigger(stage, (stage == fk.TargetSpecifying or stage == fk.TargetSpecified) and room:getPlayerById(aimStruct.from) or room:getPlayerById(aimStruct.to), aimStruct)
AimGroup:removeDeadTargets(room, aimStruct)
local aimEventTargetGroup = aimStruct.targetGroup
@ -3292,19 +3328,9 @@ function Room:useSkill(player, skill, effect_cb)
end
---@param player ServerPlayer
---@param sendLog? boolean
function Room:revivePlayer(player, sendLog)
if not player.dead then return end
self:setPlayerProperty(player, "dead", false)
player._splayer:setDied(false)
self:setPlayerProperty(player, "dying", false)
self:setPlayerProperty(player, "hp", player.maxHp)
table.insertIfNeed(self.alive_players, player)
sendLog = (sendLog == nil) and true or sendLog
if sendLog then
self:sendLog { type = "#Revive", from = player.id }
end
---@param sendLog? bool
function Room:revivePlayer(player, sendLog, reason)
return execGameEvent(GameEvent.Revive, player, sendLog, reason)
end
---@param room Room
@ -3573,4 +3599,9 @@ function Room:resumePlayerArea(player, playerSlots)
end
end
function Room:setPlayerRest(player, roundNum)
player.rest = roundNum
self:broadcastProperty(player, "rest")
end
return Room

View File

@ -39,6 +39,7 @@
--- 描述和一次体力变化有关的数据
---@class HpChangedData
---@field public num integer @ 体力变化量,可能是正数或者负数
---@field public shield_lost integer|nil
---@field public reason string @ 体力变化原因
---@field public skillName string @ 引起体力变化的技能名
---@field public damageEvent? DamageStruct @ 引起这次体力变化的伤害数据

View File

@ -82,7 +82,7 @@ GameRule = fk.CreateTriggerSkill{
end,
[fk.BuryVictim] = function()
player:bury()
if room.tag["SkipNormalDeathProcess"] then
if room.tag["SkipNormalDeathProcess"] or player.rest > 0 then
return false
end
local damage = data.damage

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@ -423,6 +423,31 @@ local jijiang = fk.CreateViewAsSkill{
c.skillName = self.name
return c
end,
before_use = function(self, player, use)
local room = player.room
if use.tos then
room:doIndicate(player.id, TargetGroup:getRealTargets(use.tos))
end
for _, p in ipairs(room:getOtherPlayers(player)) do
if p.kingdom == "shu" then
local cardResponded = room:askForResponse(p, "slash", "slash", "#jijiang-ask:" .. player.id, true)
if cardResponded then
room:responseCard({
from = p.id,
card = cardResponded,
skipDrop = true,
})
use.card = cardResponded
return
end
end
end
room:setPlayerMark(player, "jijiang-failed-phase", 1)
return self.name
end,
enabled_at_play = function(self, player)
return player:getMark("jijiang-failed-phase") == 0 and not table.every(Fk:currentRoom().alive_players, function(p)
return p == player or p.kingdom ~= "shu"
@ -434,51 +459,6 @@ local jijiang = fk.CreateViewAsSkill{
end)
end,
}
local jijiangResponse = fk.CreateTriggerSkill{
name = "#jijiangResponse",
events = {fk.PreCardUse, fk.PreCardRespond},
mute = true,
priority = 10,
can_trigger = function(self, event, target, player, data)
return target == player and player:hasSkill(self.name, true) and table.contains(data.card.skillNames, "jijiang")
end,
on_cost = function(self, event, target, player, data)
local room = player.room
room:doIndicate(player.id, TargetGroup:getRealTargets(data.tos))
return true
end,
on_use = function(self, event, target, player, data)
local room = player.room
for _, p in ipairs(room:getOtherPlayers(player)) do
if p.kingdom == "shu" then
local cardResponded = room:askForResponse(p, "slash", "slash", "#jijiang-ask:" .. player.id, true)
if cardResponded then
room:responseCard({
from = p.id,
card = cardResponded,
skipDrop = true,
})
data.card = cardResponded
return false
end
end
end
if event == fk.PreCardUse and player.phase == Player.Play then
room:setPlayerMark(player, "jijiang-failed-phase", 1)
end
return true
end,
refresh_events = {fk.CardUsing},
can_refresh = function(self, event, target, player, data)
return target == player and player:hasSkill(self.name, true) and player:getMark("jijiang-failed-phase") > 0
end,
on_refresh = function(self, event, target, player, data)
player.room:setPlayerMark(player, "jijiang-failed-phase", 0)
end,
}
jijiang:addRelatedSkill(jijiangResponse)
local liubei = General:new(extension, "liubei", "shu", 4)
liubei:addSkill(rende)

View File

@ -338,6 +338,7 @@ local test_feichu = fk.CreateActiveSkill{
room:abortPlayerArea(from, eqipSlots)
end,
}
local test2 = General(extension, "mouxusheng", "wu", 4, 4, General.Female)
test2.shield = 3
test2.hidden = true