Switch skill (#148)

- 实现转换技;
- 将特殊的标记名称注册在mark_enum.lua文件;
- 标记值在UI的显示支持解析数组;
- 将觉醒技的觉醒条件分离至canWake函数;
- 修复一系列bug;
- 在Room类新增从牌堆、弃牌堆中随机获取牌的方法。
This commit is contained in:
Ho-spair 2023-05-13 14:20:34 +08:00 committed by GitHub
parent 5e9505d18e
commit a7e3ad0f19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 298 additions and 45 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -525,7 +525,10 @@ end
---@param times integer
local function updateLimitSkill(pid, skill, times)
if not skill.visible then return end
if skill.frequency == Skill.Limited or skill.frequency == Skill.Wake then
if skill:isSwitchSkill() then
times = ClientInstance:getPlayerById(pid):getSwitchSkillState(skill.switchSkillName) == fk.SwitchYang and 0 or 1
ClientInstance:notifyUI("UpdateLimitSkill", json.encode{ pid, skill.switchSkillName, times })
elseif skill.frequency == Skill.Limited or skill.frequency == Skill.Wake then
ClientInstance:notifyUI("UpdateLimitSkill", json.encode{ pid, skill.name, times })
end
end

View File

@ -274,6 +274,7 @@ function GetSkillData(skill_name)
extension = skill.package.extensionName,
freq = freq,
frequency = frequency,
switchSkillName = skill.switchSkillName,
}
end

View File

@ -193,6 +193,10 @@ FreeKill使用的是libgit2的C API与此同时使用Git完成拓展包的下
["$Winner"] = "%1 获胜",
["$NoWinner"] = "平局!",
["Back To Lobby"] = "返回大厅",
["basic_char"] = "",
["trick_char"] = "",
["equip_char"] = "",
}
-- Game concepts

View File

@ -284,6 +284,12 @@ local function getNumberStr(num)
return tostring(num)
end
--- 判断卡牌是否为普通锦囊牌
---@return boolean
function Card:isCommonTrick()
return self.type == Card.TypeTrick and self.sub_type ~= Card.SubtypeDelayedTrick
end
--- 为卡牌赋予Mark。
---@param mark string @ 标记
---@param count integer @ 为标记赋予的数量
@ -333,6 +339,51 @@ function Card:getMarkNames()
return ret
end
---@param anotherCard Card
---@param diff boolean
---@return boolean
function Card:compareSuitWith(anotherCard, diff)
if table.contains({ self.suit, anotherCard.suit }, Card.NoSuit) then
return false
end
if diff then
return self.suit ~= anotherCard.suit
else
return self.suit == anotherCard.suit
end
end
---@param anotherCard Card
---@param diff boolean
---@return boolean
function Card:compareColorWith(anotherCard, diff)
if table.contains({ self.color, anotherCard.color }, Card.NoColor) then
return false
end
if diff then
return self.color ~= anotherCard.color
else
return self.color == anotherCard.color
end
end
---@param anotherCard Card
---@param diff boolean
---@return boolean
function Card:compareNumberWith(anotherCard, diff)
if self.number < 1 or anotherCard.number < 1 then
return false
end
if diff then
return self.number ~= anotherCard.number
else
return self.number == anotherCard.number
end
end
-- for sendLog
--- 获取卡牌的文字信息并准备作为log发送。
function Card:toLogString()

View File

@ -119,8 +119,8 @@ function Player:setGeneral(general, setHp, addSkills)
end
function Player:getGeneralMaxHp()
local general = Fk.generals[self:getMark("__heg_general") or self.general]
local deputy = Fk.generals[self:getMark("__heg_deputy") or self.deputy]
local general = Fk.generals[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.deputy]
if not deputy then
return general.maxHp
@ -727,4 +727,19 @@ function Player:prohibitDiscard(card)
return false
end
---@field SwitchYang number @ 转换技状态阳
fk.SwitchYang = 0
---@field SwitchYin number @ 转换技状态阴
fk.SwitchYin = 1
---@param skillName string
---@return number
function Player:getSwitchSkillState(skillName, afterUse)
if afterUse then
return self:getMark(MarkEnum.SwithSkillPreName .. skillName) < 1 and fk.SwitchYin or fk.SwitchYang
else
return self:getMark(MarkEnum.SwithSkillPreName .. skillName) < 1 and fk.SwitchYang or fk.SwitchYin
end
end
return Player

View File

@ -12,6 +12,7 @@
---@field public anim_type string @ 技能类型定义
---@field public related_skills Skill[] @ 和本技能相关的其他技能,有时候一个技能实际上是通过好几个技能拼接而实现的。
---@field public attached_equip string @ 属于什么装备的技能?
---@field public switchSkillName string
local Skill = class("Skill")
---@alias Frequency integer
@ -90,4 +91,8 @@ function Skill:addAttachedKingdom(kingdom)
table.insertIfNeed(self.attachedKingdom, kingdom)
end
function Skill:isSwitchSkill()
return self.switchSkillName and type(self.switchSkillName) == 'string' and self.switchSkillName ~= ""
end
return Skill

View File

@ -57,8 +57,14 @@ end
-- DO NOT modify this function
function TriggerSkill:doCost(event, target, player, data)
local ret = self:cost(event, target, player, data)
local room = player.room
local cost_data_bak = self.cost_data
room.logic:trigger(fk.BeforeTriggerSkillUse, player, { skill = self, willUse = ret })
self.cost_data = cost_data_bak
if ret then
return player.room:useSkill(player, self, function()
return room:useSkill(player, self, function()
return self:use(event, target, player, data)
end)
end
@ -90,4 +96,22 @@ end
---@return boolean
function TriggerSkill:use(event, target, player, data) end
function TriggerSkill:canWake(event, target, player, data)
return true
end
---@param event Event @ TriggerEvent
---@param target ServerPlayer @ Player who triggered this event
---@param player ServerPlayer @ Player who is operating
---@param data any @ useful data of the event
---@return boolean
function TriggerSkill:enableToWake(event, target, player, data)
return
type(player:getMark(MarkEnum.StraightToWake)) == "table" and
table.find(player:getMark(MarkEnum.StraightToWake), function(skillName)
return self.name == skillName
end) or
self:canWake(event, target, player, data)
end
return TriggerSkill

View File

@ -7,6 +7,7 @@
-- 首先加载所有详细的技能类型、卡牌类型等等,以及时机列表
dofile "lua/server/event.lua"
dofile "lua/server/system_enum.lua"
dofile "lua/server/mark_enum.lua"
TriggerSkill = require "core.skill_type.trigger"
ActiveSkill = require "core.skill_type.active"
ViewAsSkill = require "core.skill_type.view_as"
@ -32,6 +33,11 @@ local function readCommonSpecToSkill(skill, spec)
assert(type(spec.attached_equip) == "string")
skill.attached_equip = spec.attached_equip
end
if spec.switch_skill_name then
assert(type(spec.switch_skill_name) == "string")
skill.switchSkillName = spec.switch_skill_name
end
end
local function readUsableSpecToSkill(skill, spec)
@ -110,7 +116,18 @@ function fk.CreateTriggerSkill(spec)
if spec.on_trigger then skill.trigger = spec.on_trigger end
if spec.can_trigger then
skill.triggerable = spec.can_trigger
if spec.frequency == Skill.Wake then
skill.triggerable = function(self, event, target, player, data)
return spec.can_trigger(self, event, target, player, data) and
skill:enableToWake(event, target, player, data)
end
else
skill.triggerable = spec.can_trigger
end
end
if skill.frequency == Skill.Wake and spec.can_wake then
skill.canWake = spec.can_wake
end
if spec.on_cost then skill.cost = spec.on_cost end
@ -197,7 +214,7 @@ end
---@field public pattern string
---@field public enabled_at_play fun(self: ViewAsSkill, player: Player): boolean
---@field public enabled_at_response fun(self: ViewAsSkill, player: Player): boolean
---@field public before_use fun(self: ViewAsSkill, player: Player)
---@field public before_use fun(self: ViewAsSkill, player: ServerPlayer)
---@param spec ViewAsSkillSpec
---@return ViewAsSkill

View File

@ -9,6 +9,10 @@
---FreeKill's lua API
fk = {}
---@class MarkEnum
---Special marks
MarkEnum = {}
---@class fk.SPlayerList
SPlayerList = {}

View File

@ -15,7 +15,7 @@ local function useActiveSkill(self, skill, card)
filter_func = function() return false end
end
if self.command == "PlayCard" and not skill:canUse(player) then
if self.command == "PlayCard" and not skill:canUse(player, card) then
return ""
end

View File

@ -95,5 +95,10 @@ fk.PindianResultConfirmed = 71
fk.PindianFinished = 72
-- 73 = TurnEnd
fk.AfterDrawPileShuffle = 74
fk.NumOfEvents = 74
fk.BeforeTriggerSkillUse = 75
fk.BeforeDrawCard = 76
fk.NumOfEvents = 77

View File

@ -40,15 +40,12 @@ GameEvent.functions[GameEvent.Judge] = function(self)
if self.logic:trigger(fk.FinishJudge, who, data) then
self.logic:breakEvent()
end
if self:getCardArea(data.card.id) == Card.Processing then
self:moveCardTo(data.card, Card.DiscardPile, nil, fk.ReasonPutIntoDiscardPile)
end
end
GameEvent.cleaners[GameEvent.Judge] = function(self)
local data = table.unpack(self.data)
local self = self.room
if self:getCardArea(data.card.id) == Card.Processing then
if (self.interrupted or not data.skipDrop) and self:getCardArea(data.card.id) == Card.Processing then
self:moveCardTo(data.card, Card.DiscardPile, nil, fk.ReasonPutIntoDiscardPile)
end
if not self.interrupted then return end

View File

@ -100,7 +100,7 @@ GameEvent.functions[GameEvent.MoveCards] = function(self)
toAreaIds = self.void
end
table.insert(toAreaIds, toAreaIds == Card.DrawPile and 1 or #toAreaIds + 1, info.cardId)
table.insert(toAreaIds, data.toArea == Card.DrawPile and 1 or #toAreaIds + 1, info.cardId)
end
self:setCardArea(info.cardId, data.toArea, data.to)
if data.toArea == Card.DrawPile or realFromArea == Card.DrawPile then

View File

@ -414,8 +414,15 @@ function GameLogic:trigger(event, target, data, refresh_only)
local len = #skills
broken = skill:trigger(event, target, player, data)
table.insertTable(skill_names, table.map(
table.slice(skills, len - #skills), function(s) return s.name end))
table.insertTable(
skill_names,
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)
)
broken = broken or (event == fk.AskForPeaches
and room:getPlayerById(data.who).hp > 0)

8
lua/server/mark_enum.lua Normal file
View File

@ -0,0 +1,8 @@
-- SPDX-License-Identifier: GPL-3.0-or-later
MarkEnum = {}
---@field StraightToWake string @ 跳过觉醒标记(值为技能名通过+连接)
MarkEnum.StraightToWake = "_straight_to_wake"
MarkEnum.SwithSkillPreName = "__switcher_"

View File

@ -195,8 +195,25 @@ end
--- 将房间中的玩家按照座位顺序重新排序。
---@param playerIds integer[] @ 玩家id列表这个数组会被这个函数排序
function Room:sortPlayersByAction(playerIds)
function Room:sortPlayersByAction(playerIds, isTargetGroup)
table.sort(playerIds, function(prev, next)
local prevSeat = self:getPlayerById(isTargetGroup and prev[1] or prev).seat
local nextSeat = self:getPlayerById(isTargetGroup and next[1] or next).seat
return prevSeat < nextSeat
end)
if
self.current and
table.find(isTargetGroup and TargetGroup:getRealTargets(playerIds) or playerIds, function(id)
return self:getPlayerById(id).seat >= self.current.seat
end)
then
while self:getPlayerById(isTargetGroup and playerIds[1][1] or playerIds[1]).seat < self.current.seat do
local toPlayerId = table.remove(playerIds, 1)
table.insert(playerIds, toPlayerId)
end
end
end
function Room:deadPlayerFilter(playerIds)
@ -487,10 +504,10 @@ function Room:changeHero(player, new_general, full, isDeputy, sendLog)
end
player.maxHp = player:getGeneralMaxHp()
self:broadcastProperty(player, "hp")
self:broadcastProperty(player, "maxHp")
if full then
player.hp = player.maxHp
self:broadcastProperty(player, "maxHp")
self:broadcastProperty(player, "hp")
end
end
@ -1220,7 +1237,10 @@ end
---@param cards integer[] @ 可以被观星的卡牌id列表
---@param top_limit integer[] @ 置于牌堆顶的牌的限制(下限,上限),不填写则不限
---@param bottom_limit integer[] @ 置于牌堆顶的牌的限制(下限,上限),不填写则不限
function Room:askForGuanxing(player, cards, top_limit, bottom_limit)
---@param customNotify string|null @ 自定义读条操作提示
---@param noPut boolean|null @ 是否进行放置牌操作
---@return table<top|bottom, cardId[]>
function Room:askForGuanxing(player, cards, top_limit, bottom_limit, customNotify, noPut)
-- 这一大堆都是来提前报错的
top_limit = top_limit or {}
bottom_limit = bottom_limit or {}
@ -1236,7 +1256,7 @@ function Room:askForGuanxing(player, cards, top_limit, bottom_limit)
assert(#cards >= top_limit[1] + bottom_limit[1] and #cards <= top_limit[2] + bottom_limit[2], "限定区间设置错误:可用空间不能容纳所有牌。")
end
local command = "AskForGuanxing"
self:notifyMoveFocus(player, command)
self:notifyMoveFocus(player, customNotify or command)
local data = {
cards = cards,
min_top_cards = top_limit and top_limit[1] or 0,
@ -1261,19 +1281,23 @@ function Room:askForGuanxing(player, cards, top_limit, bottom_limit)
bottom = {}
end
for i = #top, 1, -1 do
table.insert(self.draw_pile, 1, top[i])
end
for _, id in ipairs(bottom) do
table.insert(self.draw_pile, id)
if not noPut then
for i = #top, 1, -1 do
table.insert(self.draw_pile, 1, top[i])
end
for _, id in ipairs(bottom) do
table.insert(self.draw_pile, id)
end
self:sendLog{
type = "#GuanxingResult",
from = player.id,
arg = #top,
arg2 = #bottom,
}
end
self:sendLog{
type = "#GuanxingResult",
from = player.id,
arg = #top,
arg2 = #bottom,
}
return { top = top, bottom = bottom }
end
--- 平时写DIY用不到的函数。
@ -1567,7 +1591,7 @@ local onAim = function(room, cardUseEvent, aimEventCollaborators)
return false
end
room:sortPlayersByAction(cardUseEvent.tos)
room:sortPlayersByAction(cardUseEvent.tos, true)
local aimGroup = AimGroup:initAimGroup(TargetGroup:getRealTargets(cardUseEvent.tos))
local collaboratorsIndex = {}
@ -1628,7 +1652,7 @@ local onAim = function(room, cardUseEvent, aimEventCollaborators)
local aimEventTargetGroup = aimStruct.targetGroup
if aimEventTargetGroup then
room:sortPlayersByAction(aimEventTargetGroup)
room:sortPlayersByAction(aimEventTargetGroup, true)
end
cardUseEvent.from = aimStruct.from
@ -2030,6 +2054,17 @@ end
---@param fromPlace string @ 摸牌的位置,"top" 或者 "bottom"
---@return integer[] @ 摸到的牌
function Room:drawCards(player, num, skillName, fromPlace)
local drawData = {
who = player,
num = num,
skillName = skillName,
fromPlace = fromPlace,
}
self.logic:trigger(fk.BeforeDrawCard, player, drawData)
num = drawData.num
fromPlace = drawData.fromPlace
local topCards = self:getNCards(num, fromPlace)
self:moveCards({
ids = topCards,
@ -2408,6 +2443,8 @@ function Room:shuffleDrawPile()
end
self.discard_pile = {}
table.shuffle(self.draw_pile)
self.logic:trigger(fk.AfterDrawPileShuffle, nil, {})
end
--- 使用技能。先增加技能发动次数,再执行相应的函数。
@ -2428,6 +2465,15 @@ function Room:useSkill(player, skill, effect_cb)
self:notifySkillInvoked(player, skill.name)
end
end
if skill:isSwitchSkill() then
local switchSkillName = skill.switchSkillName
self:setPlayerMark(
player,
MarkEnum.SwithSkillPreName .. switchSkillName,
player:getSwitchSkillState(switchSkillName, true)
)
end
player:addSkillUseHistory(skill.name)
if effect_cb then
@ -2470,6 +2516,55 @@ function Room:getSubcardsByRule(card, fromAreas)
return cardIds
end
---@param pattern string
---@param num number|null
---@param fromPile number|null
---@return cardId[]
function Room:getCardFromPileByRule(pattern, num, fromPile)
num = num or 1
local pileToSearch = fromPile == Card.DiscardPile and self.discard_pile or self.draw_pile
local cardPack = {}
if num < 3 then
for i = 1, num do
local randomIndex = math.random(1, #pileToSearch)
local curIndex = randomIndex
repeat
local curCardId = pileToSearch[curIndex]
if Fk:getCardById(curCardId):matchPattern(pattern) and not table.contains(cardPack, curCardId) then
table.insert(cardPack, pileToSearch[curIndex])
break
end
curIndex = curIndex + 1
if curIndex > #pileToSearch then
curIndex = 1
end
until curIndex == randomIndex
if #cardPack < num then
break
end
end
else
local matchedIds = {}
for _, id in ipairs(pileToSearch) do
if Fk:getCardById(id):matchPattern(pattern) then
table.insert(matchedIds, id)
end
end
local loopTimes = math.min(num, #matchedIds)
for i = 1, loopTimes do
local randomCardId = matchedIds[math.random(1, #matchedIds)]
table.insert(cardPack, randomCardId)
table.removeOne(matchedIds, randomCardId)
end
end
return cardPack
end
function CreateRoom(_room)
RoomInstance = Room:new(_room)
end

View File

@ -141,6 +141,7 @@ fk.IceDamage = 4
---@field public card Card
---@field public reason string
---@field public pattern string
---@field public skipDrop boolean|null
---@class CardResponseEvent
---@field public from integer
@ -189,3 +190,13 @@ fk.ReasonResonpse = 10
---@field public arg any
---@field public arg2 any
---@field public arg3 any
---@class SkillUseStruct
---@field public skill Skill
---@field public willUse boolean
---@class DrawCardStruct
---@field public who ServerPlayer
---@field public num number
---@field public skillName string
---@field public fromPlace "top"|"bottom"

View File

@ -79,8 +79,8 @@ extension:addCards{
local analepticSkill = fk.CreateActiveSkill{
name = "analeptic_skill",
max_turn_use_time = 1,
can_use = function(self, player)
return player:usedCardTimes("analeptic", Player.HistoryTurn) < self:getMaxUseTime(Self, Player.HistoryTurn)
can_use = function(self, player, card)
return player:usedCardTimes("analeptic", Player.HistoryTurn) < self:getMaxUseTime(Self, Player.HistoryTurn, card)
end,
on_use = function(self, room, use)
if not use.tos or #TargetGroup:getRealTargets(use.tos) == 0 then
@ -120,21 +120,22 @@ local analepticEffect = fk.CreateTriggerSkill{
if event == fk.PreCardUse then
return data.card.trueName == "slash" and player.drank > 0
else
return player.phase == Player.NotActive
return target.phase == Player.NotActive
end
end,
on_trigger = function(self, event, target, player, data)
local room = player.room
if event == fk.PreCardUse then
data.additionalDamage = (data.additionalDamage or 0) + player.drank
data.extra_data = data.extra_data or {}
data.extra_data.drankBuff = player.drank
player.drank = 0
player.room:broadcastProperty(player, "drank")
room:broadcastProperty(player, "drank")
else
for _, p in ipairs(player.room:getAlivePlayers(true)) do
for _, p in ipairs(room:getAlivePlayers(true)) do
if p.drank > 0 then
p.drank = 0
p.room:broadcastProperty(player, "drank")
room:broadcastProperty(p, "drank")
end
end
end
@ -270,12 +271,12 @@ extension:addCards{
local supplyShortageSkill = fk.CreateActiveSkill{
name = "supply_shortage_skill",
distance_limit = 1,
target_filter = function(self, to_select, selected)
target_filter = function(self, to_select, selected, _, card)
if #selected == 0 then
local player = Fk:currentRoom():getPlayerById(to_select)
if Self ~= player then
return not player:hasDelayedTrick("supply_shortage") and
Self:distanceTo(player) <= self:getDistanceLimit(Self)
Self:distanceTo(player) <= self:getDistanceLimit(Self, card)
end
end
return false

View File

@ -41,8 +41,8 @@ Item {
onSkillnameChanged: {
let data = Backend.callLuaFunction("GetSkillData", [skillname]);
data = JSON.parse(data);
if (data.frequency) {
skilltype = data.frequency;
if (data.frequency || data.switchSkillName) {
skilltype = data.switchSkillName ? 'switch' : data.frequency;
visible = true;
} else {
visible = false;
@ -59,6 +59,9 @@ Item {
x.visible = true;
bg.source = SkinBank.LIMIT_SKILL_DIR + "limit-used";
}
} else if (skilltype === 'switch') {
visible = true;
bg.source = SkinBank.LIMIT_SKILL_DIR + (usedtimes < 1 ? 'switch' : 'switch-yin');
}
}
}

View File

@ -33,7 +33,7 @@ Item {
width: childrenRect.width
height: 22
Text {
text: Backend.translate(mark_name) + ' ' + Backend.translate(mark_extra)
text: Backend.translate(mark_name) + ' ' + mark_extra
font.family: fontLibian.name
font.pixelSize: 22
font.letterSpacing: -0.6
@ -82,6 +82,8 @@ Item {
break;
}
}
data = (data instanceof Array ? data.map((markText) => Backend.translate(markText)).join(' ') : Backend.translate(data));
if (modelItem)
modelItem.mark_extra = data;
else

View File

@ -878,7 +878,7 @@ callbacks["SetPlayerMark"] = function(jsonData) {
let data = JSON.parse(jsonData);
let player = getPhoto(data[0]);
let mark = data[1];
let value = data[2].toString();
let value = data[2] instanceof Array ? data[2] : data[2].toString();
if (value == 0) {
player.markArea.removeMark(mark);
} else {