Abort area (#249)

Co-authored-by: notify <notify-ctrl@qq.com>
This commit is contained in:
Ho-spair 2023-08-13 02:25:04 +08:00 committed by GitHub
parent 69137c9eba
commit 64127bffb6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 350 additions and 71 deletions

View File

@ -15,7 +15,7 @@ QtObject {
property string roomBg
property string bgmFile
property string language
property var disabledPack: []
property list<string> disabledPack: []
property string preferedMode
property int preferedPlayerNum
property int preferredGeneralNum
@ -23,8 +23,8 @@ QtObject {
property real bgmVolume
property bool disableMsgAudio
property bool hideUseless
property var disabledGenerals: []
property var disableGeneralSchemes: []
property list<string> disabledGenerals: []
property list<var> disableGeneralSchemes: []
property int disableSchemeIdx: 0
property int preferredTimeout
@ -39,13 +39,13 @@ QtObject {
// Client data
property string serverMotd: ""
property var serverHiddenPacks: []
property list<string> serverHiddenPacks: []
property int roomCapacity: 0
property int roomTimeout: 0
property bool enableFreeAssign: false
property bool observing: false
property bool replaying: false
property var blockedUsers: []
property list<string> blockedUsers: []
onDisabledGeneralsChanged: {
disableGeneralSchemes[disableSchemeIdx] = disabledGenerals;

View File

@ -431,6 +431,7 @@ Item {
isOwner: model.isOwner
ready: model.ready
surrendered: model.surrendered
sealedSlots: JSON.parse(model.sealedSlots)
onSelectedChanged: {
Logic.updateSelectedTargets(playerid, selected);
@ -1227,6 +1228,7 @@ Item {
isOwner: false,
ready: false,
surrendered: false,
sealedSlots: "[]",
});
}

View File

@ -663,11 +663,14 @@ callbacks["PropertyUpdate"] = (jsonData) => {
const data = JSON.parse(jsonData);
const uid = data[0];
const property_name = data[1];
const value = data[2];
let value = data[2];
let model = getPhotoModel(uid);
if (typeof(model) !== "undefined") {
if (property_name == "sealedSlots")
value = JSON.stringify(value); // 辣鸡qml
model[property_name] = value;
}

View File

@ -5,6 +5,16 @@ import Fk
import Fk.RoomElement
Item {
property bool sealed: parent.sealedSlots.includes("JudgeSlot")
Image {
visible: sealed
x: -6; y: 8; z: 9
source: SkinBank.DELAYED_TRICK_DIR + "sealed"
height: 28
fillMode: Image.PreserveAspectFit
}
InvisibleCardArea {
id: area
checkExisting: true

View File

@ -13,9 +13,11 @@ import Fk.RoomElement
*/
Column {
id: root
height: 70
width: 138
property int itemHeight: treasureItem.name === "" ? height / 3 : height / 4
property int itemHeight: (treasureItem.name === "" && !treasureItem.sealed) ? height / 3 : height / 4
property var items: [treasureItem, weaponItem, armorItem, defensiveHorseItem, offensiveHorseItem]
property var subtypes: ["treasure", "weapon", "armor", "defensive_horse", "offensive_horse"]
property int length: area.length
@ -28,23 +30,29 @@ Column {
EquipItem {
id: treasureItem
subtype: "treasure"
width: parent.width
height: name === "" ? 0 : itemHeight
height: (name === "" && !sealed) ? 0 : itemHeight
opacity: 0
sealed: root.parent.sealedSlots.includes('TreasureSlot')
}
EquipItem {
id: weaponItem
subtype: "weapon"
width: parent.width
height: itemHeight
opacity: 0
sealed: root.parent.sealedSlots.includes('WeaponSlot')
}
EquipItem {
id: armorItem
subtype: "armor"
width: parent.width
height: itemHeight
opacity: 0
sealed: root.parent.sealedSlots.includes('ArmorSlot')
}
Row {
@ -61,6 +69,7 @@ Column {
height: itemHeight
icon: "horse"
opacity: 0
sealed: root.parent.sealedSlots.includes('DefensiveRideSlot')
}
}
@ -74,6 +83,7 @@ Column {
height: itemHeight
icon: "horse"
opacity: 0
sealed: root.parent.sealedSlots.includes('OffensiveRideSlot')
}
}
}

View File

@ -9,31 +9,41 @@ Item {
property string name: ""
property string suit: ""
property int number: 0
property bool sealed: false
property string subtype
property string icon: ""
property alias text: textItem.text
id: root
Rectangle {
anchors.fill: parent
radius: 2
visible: sealed
color: "#CCC"
opacity: 0.8
}
Image {
id: iconItem
anchors.verticalCenter: parent.verticalCenter
x: 3
source: icon ? SkinBank.getEquipIcon(cid, icon) : ""
source: sealed ? (SkinBank.EQUIP_ICON_DIR + "sealed") : (icon ? SkinBank.getEquipIcon(cid, icon) : "")
}
Image {
id: suitItem
anchors.right: parent.right
source: suit ? SkinBank.CARD_SUIT_DIR + suit : ""
source: (suit && !sealed) ? SkinBank.CARD_SUIT_DIR + suit : ""
width: implicitWidth / implicitHeight * height
height: 16
}
GlowText {
id: numberItem
visible: number > 0 && number < 14
visible: !sealed && number > 0 && number < 14
text: Utility.convertNumber(number)
color: "white"
font.family: fontLibian.name
@ -49,7 +59,7 @@ Item {
Text {
id: textItem
font.family: fontLibian.name
color: "white"
color: sealed ? "black" : "white"
font.pixelSize: 18
anchors.left: iconItem.right
anchors.leftMargin: -8
@ -131,13 +141,24 @@ Item {
}
}
function show()
{
function show() {
if (!sealed) {
showAnime.start();
}
}
function hide()
{
function hide() {
if (!sealed) {
hideAnime.start();
}
}
onSealedChanged: {
showAnime.stop();
hideAnime.stop();
x = 0;
opacity = 1;
text = ' ' + Backend.translate(subtype + "_sealed")
}
}

View File

@ -34,6 +34,7 @@ Item {
property int winGame: 0
property int runGame: 0
property int totalGame: 0
property list<string> sealedSlots: []
property int distance: -1
property string status: "normal"
@ -41,6 +42,7 @@ Item {
property alias handcardArea: handcardAreaItem
property alias equipArea: equipAreaItem
property alias areasSealed: equipAreaItem
property alias markArea: markAreaItem
property alias picMarkArea: picMarkAreaItem
property alias delayedTrickArea: delayedTrickAreaItem

View File

@ -16,7 +16,7 @@ Window {
minimumHeight: 90
title: qsTr("FreeKill") + " v" + FkVersion
property var callbacks: Logic.callbacks
property var tipList: []
property list<string> tipList: []
Item {
id: mainWindow

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1016 B

View File

@ -338,6 +338,11 @@ Fk:loadTranslationTable{
["Distance"] = "距离",
["Judge"] = "判定",
["Retrial"] = "改判",
["_sealed"] = "废除",
["weapon_sealed"] = "武器栏废除",
["armor_sealed"] = "防具栏废除",
["treasure_sealed"] = "宝物栏废除",
}
-- related to sendLog

View File

@ -61,6 +61,13 @@ Player.HistoryTurn = 2
Player.HistoryRound = 3
Player.HistoryGame = 4
Player.WeaponSlot = 'WeaponSlot'
Player.ArmorSlot = 'ArmorSlot'
Player.OffensiveRideSlot = 'OffensiveRideSlot'
Player.DefensiveRideSlot = 'DefensiveRideSlot'
Player.TreasureSlot = 'TreasureSlot'
Player.JudgeSlot = 'JudgeSlot'
--- 构造函数。总之这不是随便调用的函数
function Player:initialize()
self.id = 0
@ -93,6 +100,15 @@ function Player:initialize()
self.virtual_equips = {}
self.special_cards = {}
self.equipSlots = {
Player.WeaponSlot,
Player.ArmorSlot,
Player.OffensiveRideSlot,
Player.DefensiveRideSlot,
Player.TreasureSlot,
}
self.sealedSlots = {}
self.cardUsedHistory = {}
self.skillUsedHistory = {}
self.fixedDistance = {}
@ -392,6 +408,20 @@ function Player:getEquipment(cardSubtype)
return nil
end
--- 检索玩家装备区是否存在对应类型的装备列表。
---@param cardSubtype CardSubtype @ 卡牌子类
---@return integer[] @ 返回卡牌ID或空表
function Player:getEquipments(cardSubtype)
local cardIds = {}
for _, cardId in ipairs(self.player_cards[Player.Equip]) do
if Fk:getCardById(cardId).sub_type == cardSubtype then
table.insert(cardIds, cardId)
end
end
return cardIds
end
--- 获取玩家手牌上限。
function Player:getMaxCards()
local baseValue = math.max(self.hp, 0)
@ -799,6 +829,15 @@ end
---@param card Card @ 特定牌
function Player:isProhibited(to, card)
local r = Fk:currentRoom()
if card.type == Card.TypeEquip and #to:getAvailableEquipSlots(card.sub_type) == 0 then
return true
end
if card.sub_type == Card.SubtypeDelayedTrick and table.contains(to.sealedSlots, Player.JudgeSlot) then
return true
end
local status_skills = r.status_skills[ProhibitSkill] or Util.DummyTable
for _, skill in ipairs(status_skills) do
if skill:isProhibited(self, to, card) then
@ -871,11 +910,15 @@ function Player:canMoveCardInBoardTo(to, id)
assert(card.type == Card.TypeEquip or card.sub_type == Card.SubtypeDelayedTrick)
if card.type == Card.TypeEquip then
return not to:getEquipment(card.sub_type)
return to:hasEmptyEquipSlot(card.sub_type)
else
return not table.find(to:getCardIds(Player.Judge), function(cardId)
return
not (
table.find(to:getCardIds(Player.Judge), function(cardId)
return Fk:getCardById(cardId).name == card.name
end)
end) or
table.contains(to.sealedSlots, Player.JudgeSlot)
)
end
end
@ -910,6 +953,41 @@ function Player:getQuestSkillState(skillName)
return type(questSkillState) == "string" and questSkillState or nil
end
function Player:getAvailableEquipSlots(subtype)
local tempSlots = table.simpleClone(self.equipSlots)
local tempSealedSlots = table.simpleClone(self.sealedSlots)
if subtype then
local subtype2slot = {
[Card.SubtypeWeapon] = Player.WeaponSlot,
[Card.SubtypeArmor] = Player.ArmorSlot,
[Card.SubtypeOffensiveRide] = Player.OffensiveRideSlot,
[Card.SubtypeDefensiveRide] = Player.DefensiveRideSlot,
[Card.SubtypeTreasure] = Player.TreasureSlot,
}
local singleSlot = table.filter(tempSlots, function(slot)
return slot == subtype2slot[subtype]
end)
for _, sealedSlot in ipairs(tempSealedSlots) do
table.removeOne(singleSlot, sealedSlot)
end
return singleSlot
end
for _, sealedSlot in ipairs(tempSealedSlots) do
table.removeOne(tempSlots, sealedSlot)
end
return tempSlots
end
function Player:hasEmptyEquipSlot(subtype)
return #self:getAvailableEquipSlots(subtype) - #self:getEquipments(subtype) > 0
end
function Player:addBuddy(other)
table.insert(self.buddy_list, other.id)
end

View File

@ -28,6 +28,30 @@ Util.lockTable = function(t)
return setmetatable({}, new_mt)
end
Util.convertSubtypeAndEquipSlot = function(value)
if type(value) == "number" then
local mapper = {
[Card.SubtypeWeapon] = Player.WeaponSlot,
[Card.SubtypeArmor] = Player.ArmorSlot,
[Card.SubtypeOffensiveRide] = Player.OffensiveRideSlot,
[Card.SubtypeDefensiveRide] = Player.DefensiveRideSlot,
[Card.SubtypeTreasure] = Player.TreasureSlot,
}
return mapper[value]
else
local mapper = {
[Player.WeaponSlot] = Card.SubtypeWeapon,
[Player.ArmorSlot] = Card.SubtypeArmor,
[Player.OffensiveRideSlot] = Card.SubtypeOffensiveRide,
[Player.DefensiveRideSlot] = Card.SubtypeDefensiveRide,
[Player.TreasureSlot] = Card.SubtypeTreasure,
}
return mapper[value]
end
end
function printf(fmt, ...)
print(string.format(fmt, ...))
end

View File

@ -438,6 +438,18 @@ local defaultCardSkill = fk.CreateActiveSkill{
end
}
local defaultEquipSkill = fk.CreateActiveSkill{
name = "default_equip_skill",
can_use = function(self, player, card)
return #player:getAvailableEquipSlots(card.sub_type) > 0
end,
on_use = function(self, room, use)
if not use.tos or #TargetGroup:getRealTargets(use.tos) == 0 then
use.tos = { { use.from } }
end
end
}
local function preprocessCardSpec(spec)
assert(type(spec.name) == "string" or type(spec.class_name) == "string")
if not spec.name then spec.name = spec.class_name
@ -447,7 +459,7 @@ local function preprocessCardSpec(spec)
end
local function readCardSpecToCard(card, spec)
card.skill = spec.skill or defaultCardSkill
card.skill = spec.skill or (card.type == Card.TypeEquip and defaultEquipSkill or defaultCardSkill)
card.skill.cardSkill = true
card.special_skills = spec.special_skills
card.is_damage_card = spec.is_damage_card

View File

@ -17,14 +17,32 @@ GameEvent.functions[GameEvent.MoveCards] = function(self)
---@type MoveInfo[]
local infos = {}
local abortMoveInfos = {}
for _, id in ipairs(cardsMoveInfo.ids) do
local toAbortDrop = false
if cardsMoveInfo.toArea == Card.PlayerEquip and cardsMoveInfo.to then
local moveToPlayer = room:getPlayerById(cardsMoveInfo.to)
local card = moveToPlayer:getVirualEquip(id) or Fk:getCardById(id)
if card.type == Card.TypeEquip and #moveToPlayer:getAvailableEquipSlots(card.sub_type) == 0 then
table.insert(abortMoveInfos, {
cardId = id,
fromArea = room:getCardArea(id),
fromSpecialName = cardsMoveInfo.from and room:getPlayerById(cardsMoveInfo.from):getPileNameOfId(id),
})
toAbortDrop = true
end
end
if not toAbortDrop then
table.insert(infos, {
cardId = id,
fromArea = room:getCardArea(id),
fromSpecialName = cardsMoveInfo.from and room:getPlayerById(cardsMoveInfo.from):getPileNameOfId(id),
})
end
end
if #infos > 0 then
---@type CardsMoveStruct
local cardsMoveStruct = {
moveInfo = infos,
@ -42,7 +60,25 @@ GameEvent.functions[GameEvent.MoveCards] = function(self)
table.insert(cardsMoveStructs, cardsMoveStruct)
end
if #abortMoveInfos > 0 then
---@type CardsMoveStruct
local cardsMoveStruct = {
moveInfo = abortMoveInfos,
from = cardsMoveInfo.from,
toArea = Card.DiscardPile,
moveReason = fk.ReasonPutIntoDiscardPile,
specialName = cardsMoveInfo.specialName,
specialVisible = cardsMoveInfo.specialVisible,
drawPilePosition = cardsMoveInfo.drawPilePosition,
}
table.insert(cardsMoveStructs, cardsMoveStruct)
end
end
end
self.data = cardsMoveStructs
if #cardsMoveStructs < 1 then
return false

View File

@ -185,13 +185,7 @@ GameEvent.functions[GameEvent.UseCard] = function(self)
local room = self.room
local logic = room.logic
local from = cardUseEvent.from
room:moveCards({
ids = room:getSubcardsByRule(cardUseEvent.card),
from = from,
toArea = Card.Processing,
moveReason = fk.ReasonUse,
})
room:moveCardTo(cardUseEvent.card, Card.Processing, nil, fk.ReasonUse)
if cardUseEvent.card.skill then
cardUseEvent.card.skill:onUse(room, cardUseEvent)
@ -270,12 +264,7 @@ GameEvent.functions[GameEvent.RespondCard] = function(self)
card = cardIds,
}
end
room:moveCards({
ids = cardIds,
from = from,
toArea = Card.Processing,
moveReason = fk.ReasonResonpse,
})
room:moveCardTo(card, Card.Processing, nil, fk.ReasonResonpse)
if #cardIds > 0 then
room:sendFootnote(cardIds, {
type = "##ResponsePlayCard",

View File

@ -2210,7 +2210,7 @@ function Room:doCardUseEffect(cardUseEvent)
end
if self:getPlayerById(TargetGroup:getRealTargets(cardUseEvent.tos)[1]).dead then
self.moveCards({
self:moveCards({
ids = realCardIds,
toArea = Card.DiscardPile,
moveReason = fk.ReasonPutIntoDiscardPile,
@ -2621,9 +2621,18 @@ function Room:moveCardTo(card, to_place, target, reason, skill_name, special_nam
to = target.id
end
self:moveCards{
ids = ids,
from = self.owner_map[ids[1]],
local movesSplitedByOwner = {}
for _, cardId in ipairs(ids) do
local moveFound = table.find(movesSplitedByOwner, function(move)
return move.from == self.owner_map[cardId]
end)
if moveFound then
table.insert(moveFound.ids, cardId)
else
table.insert(movesSplitedByOwner, {
ids = { cardId },
from = self.owner_map[cardId],
to = to,
toArea = to_place,
moveReason = reason,
@ -2631,7 +2640,11 @@ function Room:moveCardTo(card, to_place, target, reason, skill_name, special_nam
specialName = special_name,
moveVisible = visible,
proposer = proposer,
}
})
end
end
self:moveCards(table.unpack(movesSplitedByOwner))
end
------------------------------------------------------------------------
@ -3210,4 +3223,54 @@ function Room:updateQuestSkillState(player, skillName, failed)
})
end
function Room:abortPlayerArea(player, playerSlots)
assert(type(playerSlots) == "string" or type(playerSlots) == "table")
if type(playerSlots) == "string" then
playerSlots = { playerSlots }
end
local cardsToDrop = {}
local slotsSealed = {}
local slotsToSeal = {}
for _, slot in ipairs(playerSlots) do
if slot == Player.JudgeSlot then
if not table.contains(player.sealedSlots, Player.JudgeSlot) then
table.insertIfNeed(slotsToSeal, slot)
local delayedTricks = player:getCardIds(Player.Judge)
if #delayedTricks > 0 then
table.insertTable(cardsToDrop, delayedTricks)
end
end
else
local subtype = Util.convertSubtypeAndEquipSlot(slot)
if #player:getAvailableEquipSlots(subtype) > 0 then
table.insert(slotsToSeal, slot)
local equipmentIndex = (slotsSealed[tostring(subtype)] or 0) + 1
slotsSealed[tostring(subtype)] = equipmentIndex
if equipmentIndex <= #player:getEquipments(subtype) then
table.insert(cardsToDrop, player:getEquipments(subtype)[equipmentIndex])
end
end
end
end
if next(slotsSealed) == nil then
return
end
self:moveCards({
ids = cardsToDrop,
from = player.id,
toArea = Card.DiscardPile,
moveReason = fk.ReasonPutIntoDiscardPile,
})
table.insertTable(player.sealedSlots, slotsToSeal)
self:broadcastProperty(player, "sealedSlots")
end
return Room

View File

@ -306,6 +306,10 @@ function ServerPlayer:marshal(player, observe)
if self.role_shown then
room:notifyProperty(player, self, "role")
end
if #self.sealedSlots > 0 then
room:notifyProperty(player, self, "sealedSlots")
end
end
function ServerPlayer:reconnect()

View File

@ -51,7 +51,6 @@ local discardSkill = fk.CreateActiveSkill{
local chooseCardsSkill = fk.CreateActiveSkill{
name = "choose_cards_skill",
-- expand_pile = function(self) return self.expand_pile end,
card_filter = function(self, to_select, selected)
if #selected >= self.num then
return false

View File

@ -648,7 +648,7 @@ extension:addCards({
local lightningSkill = fk.CreateActiveSkill{
name = "lightning_skill",
can_use = function(self, player)
return not Self:hasDelayedTrick("lightning")
return not (Self:hasDelayedTrick("lightning") or table.contains(player.sealedSlots, Player.JudgeSlot))
end,
on_use = function(self, room, use)
if not use.tos or #TargetGroup:getRealTargets(use.tos) == 0 then

View File

@ -293,6 +293,26 @@ local test_zhenggong = fk.CreateTriggerSkill{
player:gainAnExtraTurn()
end,
}
local test_feichu = fk.CreateActiveSkill{
name = "test_feichu",
can_use = function(self, player)
return true
end,
card_filter = function(self, card)
return false
end,
card_num = 0,
target_filter = function(self, to_select, selected)
return #selected < 1
end,
target_num = 1,
on_use = function(self, room, effect)
local from = room:getPlayerById(effect.from)
local eqipSlots = from:getAvailableEquipSlots()
table.insert(eqipSlots, Player.JudgeSlot)
room:abortPlayerArea(from, eqipSlots)
end,
}
local test2 = General(extension, "mouxusheng", "wu", 99, 99, General.Female)
test2.shield = 5
test2:addSkill("rende")
@ -303,6 +323,7 @@ test2:addSkill(control)
test2:addSkill(damage_maker)
test2:addSkill(change_hero)
test2:addSkill(test_zhenggong)
test2:addSkill(test_feichu)
local shibing = General(extension, "blank_shibing", "qun", 5)
shibing.hidden = true