2023-04-09 05:35:35 +00:00
|
|
|
|
-- SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
|
2023-04-12 12:51:09 +00:00
|
|
|
|
-- fk_ex.lua对标太阳神三国杀的sgs_ex.lua
|
|
|
|
|
-- 目的是提供类似太阳神三国杀拓展般的拓展语法。
|
|
|
|
|
-- 关于各种CreateXXXSkill的介绍,请见相应文档,这里不做赘述。
|
2022-04-01 12:51:01 +00:00
|
|
|
|
|
2023-04-12 12:51:09 +00:00
|
|
|
|
-- 首先加载所有详细的技能类型、卡牌类型等等,以及时机列表
|
2022-04-30 07:27:56 +00:00
|
|
|
|
dofile "lua/server/event.lua"
|
|
|
|
|
dofile "lua/server/system_enum.lua"
|
2023-05-13 06:20:34 +00:00
|
|
|
|
dofile "lua/server/mark_enum.lua"
|
2022-04-30 07:27:56 +00:00
|
|
|
|
TriggerSkill = require "core.skill_type.trigger"
|
2023-01-29 10:11:41 +00:00
|
|
|
|
ActiveSkill = require "core.skill_type.active"
|
2023-01-16 11:13:07 +00:00
|
|
|
|
ViewAsSkill = require "core.skill_type.view_as"
|
2023-01-04 06:21:29 +00:00
|
|
|
|
DistanceSkill = require "core.skill_type.distance"
|
2023-01-21 16:49:11 +00:00
|
|
|
|
ProhibitSkill = require "core.skill_type.prohibit"
|
|
|
|
|
AttackRangeSkill = require "core.skill_type.attack_range"
|
|
|
|
|
MaxCardsSkill = require "core.skill_type.max_cards"
|
2023-01-29 10:11:41 +00:00
|
|
|
|
TargetModSkill = require "core.skill_type.target_mod"
|
2023-02-15 11:54:35 +00:00
|
|
|
|
FilterSkill = require "core.skill_type.filter"
|
2023-03-20 12:49:23 +00:00
|
|
|
|
InvaliditySkill = require "lua.core.skill_type.invalidity"
|
2022-04-30 07:27:56 +00:00
|
|
|
|
|
2022-04-01 12:51:01 +00:00
|
|
|
|
BasicCard = require "core.card_type.basic"
|
2022-04-08 10:39:58 +00:00
|
|
|
|
local Trick = require "core.card_type.trick"
|
|
|
|
|
TrickCard, DelayedTrickCard = table.unpack(Trick)
|
|
|
|
|
local Equip = require "core.card_type.equip"
|
|
|
|
|
_, Weapon, Armor, DefensiveRide, OffensiveRide, Treasure = table.unpack(Equip)
|
2022-04-01 12:51:01 +00:00
|
|
|
|
|
2023-02-26 07:01:14 +00:00
|
|
|
|
local function readCommonSpecToSkill(skill, spec)
|
|
|
|
|
skill.mute = spec.mute
|
|
|
|
|
skill.anim_type = spec.anim_type
|
|
|
|
|
|
|
|
|
|
if spec.attached_equip then
|
2023-02-26 08:51:29 +00:00
|
|
|
|
assert(type(spec.attached_equip) == "string")
|
2023-02-26 07:01:14 +00:00
|
|
|
|
skill.attached_equip = spec.attached_equip
|
|
|
|
|
end
|
2023-05-13 06:20:34 +00:00
|
|
|
|
|
|
|
|
|
if spec.switch_skill_name then
|
|
|
|
|
assert(type(spec.switch_skill_name) == "string")
|
|
|
|
|
skill.switchSkillName = spec.switch_skill_name
|
|
|
|
|
end
|
2023-08-24 13:37:06 +00:00
|
|
|
|
|
|
|
|
|
if spec.relate_to_place then
|
|
|
|
|
assert(type(spec.relate_to_place) == "string")
|
|
|
|
|
skill.relate_to_place = spec.relate_to_place
|
|
|
|
|
end
|
2023-02-26 07:01:14 +00:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function readUsableSpecToSkill(skill, spec)
|
|
|
|
|
readCommonSpecToSkill(skill, spec)
|
2023-08-09 14:25:15 +00:00
|
|
|
|
assert(spec.main_skill == nil or spec.main_skill:isInstanceOf(UsableSkill))
|
|
|
|
|
skill.main_skill = spec.main_skill
|
2023-02-26 07:01:14 +00:00
|
|
|
|
skill.target_num = spec.target_num or skill.target_num
|
|
|
|
|
skill.min_target_num = spec.min_target_num or skill.min_target_num
|
|
|
|
|
skill.max_target_num = spec.max_target_num or skill.max_target_num
|
|
|
|
|
skill.target_num_table = spec.target_num_table or skill.target_num_table
|
|
|
|
|
skill.card_num = spec.card_num or skill.card_num
|
|
|
|
|
skill.min_card_num = spec.min_card_num or skill.min_card_num
|
|
|
|
|
skill.max_card_num = spec.max_card_num or skill.max_card_num
|
|
|
|
|
skill.card_num_table = spec.card_num_table or skill.card_num_table
|
|
|
|
|
skill.max_use_time = {
|
|
|
|
|
spec.max_phase_use_time or 9999,
|
|
|
|
|
spec.max_turn_use_time or 9999,
|
|
|
|
|
spec.max_round_use_time or 9999,
|
|
|
|
|
spec.max_game_use_time or 9999,
|
|
|
|
|
}
|
|
|
|
|
skill.distance_limit = spec.distance_limit or skill.distance_limit
|
2023-03-07 02:21:56 +00:00
|
|
|
|
skill.expand_pile = spec.expand_pile
|
2023-02-26 07:01:14 +00:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function readStatusSpecToSkill(skill, spec)
|
|
|
|
|
readCommonSpecToSkill(skill, spec)
|
|
|
|
|
if spec.global then
|
|
|
|
|
skill.global = spec.global
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2023-01-29 10:11:41 +00:00
|
|
|
|
---@class UsableSkillSpec: UsableSkill
|
2023-12-03 11:35:14 +00:00
|
|
|
|
---@field public max_phase_use_time? integer
|
|
|
|
|
---@field public max_turn_use_time? integer
|
|
|
|
|
---@field public max_round_use_time? integer
|
|
|
|
|
---@field public max_game_use_time? integer
|
2023-01-29 10:11:41 +00:00
|
|
|
|
|
|
|
|
|
---@class StatusSkillSpec: StatusSkill
|
2022-04-02 13:39:44 +00:00
|
|
|
|
|
2023-12-03 11:35:14 +00:00
|
|
|
|
---@alias TrigFunc fun(self: TriggerSkill, event: Event, target: ServerPlayer, player: ServerPlayer, data: any): boolean?
|
2023-01-29 10:11:41 +00:00
|
|
|
|
---@class TriggerSkillSpec: UsableSkillSpec
|
2023-12-03 11:35:14 +00:00
|
|
|
|
---@field public global? boolean
|
|
|
|
|
---@field public events? Event | Event[]
|
|
|
|
|
---@field public refresh_events? Event | Event[]
|
|
|
|
|
---@field public priority? number | table<Event, number>
|
|
|
|
|
---@field public on_trigger? TrigFunc
|
|
|
|
|
---@field public can_trigger? TrigFunc
|
|
|
|
|
---@field public on_cost? TrigFunc
|
|
|
|
|
---@field public on_use? TrigFunc
|
|
|
|
|
---@field public on_refresh? TrigFunc
|
|
|
|
|
---@field public can_refresh? TrigFunc
|
|
|
|
|
---@field public can_wake? TrigFunc
|
2022-04-02 13:39:44 +00:00
|
|
|
|
|
2022-04-30 07:27:56 +00:00
|
|
|
|
---@param spec TriggerSkillSpec
|
|
|
|
|
---@return TriggerSkill
|
|
|
|
|
function fk.CreateTriggerSkill(spec)
|
|
|
|
|
assert(type(spec.name) == "string")
|
|
|
|
|
--assert(type(spec.on_trigger) == "function")
|
|
|
|
|
if spec.frequency then assert(type(spec.frequency) == "number") end
|
|
|
|
|
|
|
|
|
|
local frequency = spec.frequency or Skill.NotFrequent
|
|
|
|
|
local skill = TriggerSkill:new(spec.name, frequency)
|
2023-02-26 07:01:14 +00:00
|
|
|
|
readUsableSpecToSkill(skill, spec)
|
2022-04-30 07:27:56 +00:00
|
|
|
|
|
|
|
|
|
if type(spec.events) == "number" then
|
|
|
|
|
table.insert(skill.events, spec.events)
|
|
|
|
|
elseif type(spec.events) == "table" then
|
|
|
|
|
table.insertTable(skill.events, spec.events)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if type(spec.refresh_events) == "number" then
|
|
|
|
|
table.insert(skill.refresh_events, spec.refresh_events)
|
|
|
|
|
elseif type(spec.refresh_events) == "table" then
|
|
|
|
|
table.insertTable(skill.refresh_events, spec.refresh_events)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if type(spec.global) == "boolean" then skill.global = spec.global end
|
|
|
|
|
|
|
|
|
|
if spec.on_trigger then skill.trigger = spec.on_trigger end
|
|
|
|
|
|
|
|
|
|
if spec.can_trigger then
|
2023-05-13 06:20:34 +00:00
|
|
|
|
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
|
2022-04-30 07:27:56 +00:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-16 13:04:28 +00:00
|
|
|
|
if spec.on_cost then skill.cost = spec.on_cost end
|
|
|
|
|
if spec.on_use then skill.use = spec.on_use end
|
|
|
|
|
|
2022-04-30 07:27:56 +00:00
|
|
|
|
if spec.can_refresh then
|
|
|
|
|
skill.canRefresh = spec.can_refresh
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if spec.on_refresh then
|
|
|
|
|
skill.refresh = spec.on_refresh
|
|
|
|
|
end
|
|
|
|
|
|
2023-02-15 13:20:40 +00:00
|
|
|
|
if spec.attached_equip then
|
|
|
|
|
if not spec.priority then
|
|
|
|
|
spec.priority = 0.1
|
2022-04-30 07:27:56 +00:00
|
|
|
|
end
|
2023-02-15 13:20:40 +00:00
|
|
|
|
elseif not spec.priority then
|
|
|
|
|
spec.priority = 1
|
2022-04-30 07:27:56 +00:00
|
|
|
|
end
|
2023-02-15 13:20:40 +00:00
|
|
|
|
|
2022-04-30 07:27:56 +00:00
|
|
|
|
if type(spec.priority) == "number" then
|
|
|
|
|
for _, event in ipairs(skill.events) do
|
|
|
|
|
skill.priority_table[event] = spec.priority
|
|
|
|
|
end
|
|
|
|
|
elseif type(spec.priority) == "table" then
|
|
|
|
|
for event, priority in pairs(spec.priority) do
|
|
|
|
|
skill.priority_table[event] = priority
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
return skill
|
|
|
|
|
end
|
|
|
|
|
|
2023-01-29 10:11:41 +00:00
|
|
|
|
---@class ActiveSkillSpec: UsableSkillSpec
|
2023-12-03 11:35:14 +00:00
|
|
|
|
---@field public can_use? fun(self: ActiveSkill, player: Player, card: Card): boolean?
|
|
|
|
|
---@field public card_filter? fun(self: ActiveSkill, to_select: integer, selected: integer[], selected_targets: integer[]): boolean?
|
|
|
|
|
---@field public target_filter? fun(self: ActiveSkill, to_select: integer, selected: integer[], selected_cards: integer[], card: Card): boolean?
|
|
|
|
|
---@field public feasible? fun(self: ActiveSkill, selected: integer[], selected_cards: integer[]): boolean?
|
|
|
|
|
---@field public on_use? fun(self: ActiveSkill, room: Room, cardUseEvent: CardUseStruct): boolean?
|
|
|
|
|
---@field public about_to_effect? fun(self: ActiveSkill, room: Room, cardEffectEvent: CardEffectEvent): boolean?
|
|
|
|
|
---@field public on_effect? fun(self: ActiveSkill, room: Room, cardEffectEvent: CardEffectEvent): boolean?
|
|
|
|
|
---@field public on_nullified? fun(self: ActiveSkill, room: Room, cardEffectEvent: CardEffectEvent): boolean?
|
|
|
|
|
---@field public mod_target_filter? fun(self: ActiveSkill, to_select: integer, selected: integer[], user: integer, card: Card, distance_limited: boolean): boolean?
|
|
|
|
|
---@field public prompt? string|fun(self: ActiveSkill, selected_cards: integer[], selected_targets: integer[]): string
|
2023-08-10 19:24:22 +00:00
|
|
|
|
---@field public interaction any
|
2022-04-30 07:27:56 +00:00
|
|
|
|
|
|
|
|
|
---@param spec ActiveSkillSpec
|
|
|
|
|
---@return ActiveSkill
|
|
|
|
|
function fk.CreateActiveSkill(spec)
|
|
|
|
|
assert(type(spec.name) == "string")
|
2023-04-04 17:05:06 +00:00
|
|
|
|
local skill = ActiveSkill:new(spec.name, spec.frequency or Skill.NotFrequent)
|
2023-02-26 07:01:14 +00:00
|
|
|
|
readUsableSpecToSkill(skill, spec)
|
2023-02-15 13:20:40 +00:00
|
|
|
|
|
2023-04-30 10:55:59 +00:00
|
|
|
|
if spec.can_use then
|
2023-05-13 05:23:18 +00:00
|
|
|
|
skill.canUse = function(curSkill, player, card)
|
|
|
|
|
return spec.can_use(curSkill, player, card) and curSkill:isEffectable(player)
|
2023-04-30 10:55:59 +00:00
|
|
|
|
end
|
|
|
|
|
end
|
2022-04-30 07:27:56 +00:00
|
|
|
|
if spec.card_filter then skill.cardFilter = spec.card_filter end
|
|
|
|
|
if spec.target_filter then skill.targetFilter = spec.target_filter end
|
2023-08-01 18:19:51 +00:00
|
|
|
|
if spec.mod_target_filter then skill.modTargetFilter = spec.mod_target_filter end
|
2023-02-26 07:01:14 +00:00
|
|
|
|
if spec.feasible then
|
2023-08-10 19:19:59 +00:00
|
|
|
|
-- print(spec.name .. ": feasible is deprecated. Use target_num and card_num instead.")
|
2023-02-26 07:01:14 +00:00
|
|
|
|
skill.feasible = spec.feasible
|
|
|
|
|
end
|
2022-04-30 07:27:56 +00:00
|
|
|
|
if spec.on_use then skill.onUse = spec.on_use end
|
2023-02-15 11:54:35 +00:00
|
|
|
|
if spec.about_to_effect then skill.aboutToEffect = spec.about_to_effect end
|
2022-04-30 07:27:56 +00:00
|
|
|
|
if spec.on_effect then skill.onEffect = spec.on_effect end
|
2022-12-20 10:40:17 +00:00
|
|
|
|
if spec.on_nullified then skill.onNullified = spec.on_nullified end
|
2023-06-16 02:58:28 +00:00
|
|
|
|
if spec.prompt then skill.prompt = spec.prompt end
|
2023-04-19 16:19:48 +00:00
|
|
|
|
|
|
|
|
|
if spec.interaction then
|
|
|
|
|
skill.interaction = setmetatable({}, {
|
|
|
|
|
__call = function(self)
|
|
|
|
|
if type(spec.interaction) == "function" then
|
|
|
|
|
return spec.interaction(self)
|
|
|
|
|
else
|
|
|
|
|
return spec.interaction
|
|
|
|
|
end
|
|
|
|
|
end,
|
|
|
|
|
})
|
|
|
|
|
end
|
2022-04-30 07:27:56 +00:00
|
|
|
|
return skill
|
2023-01-16 11:13:07 +00:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-29 10:11:41 +00:00
|
|
|
|
---@class ViewAsSkillSpec: UsableSkillSpec
|
2023-12-03 11:35:14 +00:00
|
|
|
|
---@field public card_filter? fun(self: ViewAsSkill, to_select: integer, selected: integer[]): boolean?
|
|
|
|
|
---@field public view_as fun(self: ViewAsSkill, cards: integer[]): Card?
|
|
|
|
|
---@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, response: boolean): boolean?
|
|
|
|
|
---@field public before_use? fun(self: ViewAsSkill, player: ServerPlayer, use: CardUseStruct)
|
|
|
|
|
---@field public prompt? string|fun(self: ActiveSkill, selected: integer[], selected_cards: integer[]): string
|
2023-01-16 11:13:07 +00:00
|
|
|
|
|
|
|
|
|
---@param spec ViewAsSkillSpec
|
|
|
|
|
---@return ViewAsSkill
|
|
|
|
|
function fk.CreateViewAsSkill(spec)
|
|
|
|
|
assert(type(spec.name) == "string")
|
|
|
|
|
assert(type(spec.view_as) == "function")
|
|
|
|
|
|
2023-05-13 05:23:18 +00:00
|
|
|
|
local skill = ViewAsSkill:new(spec.name, spec.frequency or Skill.NotFrequent)
|
2023-02-26 07:01:14 +00:00
|
|
|
|
readUsableSpecToSkill(skill, spec)
|
2023-02-15 13:20:40 +00:00
|
|
|
|
|
2023-01-16 11:13:07 +00:00
|
|
|
|
skill.viewAs = spec.view_as
|
|
|
|
|
if spec.card_filter then
|
|
|
|
|
skill.cardFilter = spec.card_filter
|
|
|
|
|
end
|
|
|
|
|
if type(spec.pattern) == "string" then
|
|
|
|
|
skill.pattern = spec.pattern
|
|
|
|
|
end
|
|
|
|
|
if type(spec.enabled_at_play) == "function" then
|
2023-04-30 10:55:59 +00:00
|
|
|
|
skill.enabledAtPlay = function(curSkill, player)
|
|
|
|
|
return spec.enabled_at_play(curSkill, player) and curSkill:isEffectable(player)
|
|
|
|
|
end
|
2023-01-16 11:13:07 +00:00
|
|
|
|
end
|
|
|
|
|
if type(spec.enabled_at_response) == "function" then
|
2023-04-30 10:55:59 +00:00
|
|
|
|
skill.enabledAtResponse = function(curSkill, player, cardResponsing)
|
|
|
|
|
return spec.enabled_at_response(curSkill, player, cardResponsing) and curSkill:isEffectable(player)
|
|
|
|
|
end
|
2023-01-16 11:13:07 +00:00
|
|
|
|
end
|
2023-06-16 02:58:28 +00:00
|
|
|
|
if spec.prompt then skill.prompt = spec.prompt end
|
2023-01-16 11:13:07 +00:00
|
|
|
|
|
2023-04-19 16:19:48 +00:00
|
|
|
|
if spec.interaction then
|
|
|
|
|
skill.interaction = setmetatable({}, {
|
|
|
|
|
__call = function()
|
|
|
|
|
if type(spec.interaction) == "function" then
|
|
|
|
|
return spec.interaction(skill)
|
|
|
|
|
else
|
|
|
|
|
return spec.interaction
|
|
|
|
|
end
|
|
|
|
|
end,
|
|
|
|
|
})
|
|
|
|
|
end
|
|
|
|
|
|
2023-04-30 10:55:59 +00:00
|
|
|
|
if spec.before_use and type(spec.before_use) == "function" then
|
|
|
|
|
skill.beforeUse = spec.before_use
|
|
|
|
|
end
|
|
|
|
|
|
2023-01-16 11:13:07 +00:00
|
|
|
|
return skill
|
2022-04-30 07:27:56 +00:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-29 10:11:41 +00:00
|
|
|
|
---@class DistanceSpec: StatusSkillSpec
|
2023-12-03 11:35:14 +00:00
|
|
|
|
---@field public correct_func? fun(self: DistanceSkill, from: Player, to: Player): integer?
|
|
|
|
|
---@field public fixed_func? fun(self: DistanceSkill, from: Player, to: Player): integer?
|
2023-01-04 06:21:29 +00:00
|
|
|
|
|
|
|
|
|
---@param spec DistanceSpec
|
|
|
|
|
---@return DistanceSkill
|
|
|
|
|
function fk.CreateDistanceSkill(spec)
|
|
|
|
|
assert(type(spec.name) == "string")
|
2023-08-01 18:19:51 +00:00
|
|
|
|
assert(type(spec.correct_func) == "function" or type(spec.fixed_func) == "function")
|
2023-01-04 06:21:29 +00:00
|
|
|
|
|
|
|
|
|
local skill = DistanceSkill:new(spec.name)
|
2023-02-26 07:01:14 +00:00
|
|
|
|
readStatusSpecToSkill(skill, spec)
|
2023-01-04 06:21:29 +00:00
|
|
|
|
skill.getCorrect = spec.correct_func
|
2023-07-07 17:05:54 +00:00
|
|
|
|
skill.getFixed = spec.fixed_func
|
2023-02-15 13:20:40 +00:00
|
|
|
|
|
2023-01-04 06:21:29 +00:00
|
|
|
|
return skill
|
|
|
|
|
end
|
|
|
|
|
|
2023-01-29 10:11:41 +00:00
|
|
|
|
---@class ProhibitSpec: StatusSkillSpec
|
2023-12-03 11:35:14 +00:00
|
|
|
|
---@field public is_prohibited? fun(self: ProhibitSkill, from: Player, to: Player, card: Card): boolean?
|
|
|
|
|
---@field public prohibit_use? fun(self: ProhibitSkill, player: Player, card: Card): boolean?
|
|
|
|
|
---@field public prohibit_response? fun(self: ProhibitSkill, player: Player, card: Card): boolean?
|
|
|
|
|
---@field public prohibit_discard? fun(self: ProhibitSkill, player: Player, card: Card): boolean?
|
2023-01-21 16:49:11 +00:00
|
|
|
|
|
|
|
|
|
---@param spec ProhibitSpec
|
|
|
|
|
---@return ProhibitSkill
|
|
|
|
|
function fk.CreateProhibitSkill(spec)
|
|
|
|
|
assert(type(spec.name) == "string")
|
|
|
|
|
|
|
|
|
|
local skill = ProhibitSkill:new(spec.name)
|
2023-02-26 07:01:14 +00:00
|
|
|
|
readStatusSpecToSkill(skill, spec)
|
|
|
|
|
skill.isProhibited = spec.is_prohibited or skill.isProhibited
|
|
|
|
|
skill.prohibitUse = spec.prohibit_use or skill.prohibitUse
|
|
|
|
|
skill.prohibitResponse = spec.prohibit_response or skill.prohibitResponse
|
|
|
|
|
skill.prohibitDiscard = spec.prohibit_discard or skill.prohibitDiscard
|
2023-02-15 13:20:40 +00:00
|
|
|
|
|
2023-01-21 16:49:11 +00:00
|
|
|
|
return skill
|
|
|
|
|
end
|
|
|
|
|
|
2023-01-29 10:11:41 +00:00
|
|
|
|
---@class AttackRangeSpec: StatusSkillSpec
|
2023-12-03 11:35:14 +00:00
|
|
|
|
---@field public correct_func? fun(self: AttackRangeSkill, from: Player, to: Player): number?
|
|
|
|
|
---@field public within_func? fun(self: AttackRangeSkill, from: Player, to: Player): boolean?
|
2023-01-21 16:49:11 +00:00
|
|
|
|
|
|
|
|
|
---@param spec AttackRangeSpec
|
|
|
|
|
---@return AttackRangeSkill
|
|
|
|
|
function fk.CreateAttackRangeSkill(spec)
|
|
|
|
|
assert(type(spec.name) == "string")
|
2023-05-28 10:45:54 +00:00
|
|
|
|
assert(type(spec.correct_func) == "function" or type(spec.within_func) == "function")
|
2023-01-21 16:49:11 +00:00
|
|
|
|
|
|
|
|
|
local skill = AttackRangeSkill:new(spec.name)
|
2023-02-26 07:01:14 +00:00
|
|
|
|
readStatusSpecToSkill(skill, spec)
|
2023-05-28 10:45:54 +00:00
|
|
|
|
if spec.correct_func then
|
|
|
|
|
skill.getCorrect = spec.correct_func
|
|
|
|
|
end
|
|
|
|
|
if spec.within_func then
|
|
|
|
|
skill.withinAttackRange = spec.within_func
|
|
|
|
|
end
|
2023-02-15 13:20:40 +00:00
|
|
|
|
|
2023-01-21 16:49:11 +00:00
|
|
|
|
return skill
|
|
|
|
|
end
|
|
|
|
|
|
2023-01-29 10:11:41 +00:00
|
|
|
|
---@class MaxCardsSpec: StatusSkillSpec
|
2023-12-03 11:35:14 +00:00
|
|
|
|
---@field public correct_func? fun(self: MaxCardsSkill, player: Player): number?
|
|
|
|
|
---@field public fixed_func? fun(self: MaxCardsSkill, player: Player): number?
|
|
|
|
|
---@field public exclude_from? fun(self: MaxCardsSkill, player: Player, card: Card): boolean?
|
2023-01-21 16:49:11 +00:00
|
|
|
|
|
|
|
|
|
---@param spec MaxCardsSpec
|
|
|
|
|
---@return MaxCardsSkill
|
|
|
|
|
function fk.CreateMaxCardsSkill(spec)
|
|
|
|
|
assert(type(spec.name) == "string")
|
2023-06-04 11:39:20 +00:00
|
|
|
|
assert(type(spec.correct_func) == "function" or type(spec.fixed_func) == "function" or type(spec.exclude_from) == "function")
|
2023-01-21 16:49:11 +00:00
|
|
|
|
|
|
|
|
|
local skill = MaxCardsSkill:new(spec.name)
|
2023-02-26 07:01:14 +00:00
|
|
|
|
readStatusSpecToSkill(skill, spec)
|
2023-01-21 16:49:11 +00:00
|
|
|
|
if spec.correct_func then
|
|
|
|
|
skill.getCorrect = spec.correct_func
|
|
|
|
|
end
|
|
|
|
|
if spec.fixed_func then
|
|
|
|
|
skill.getFixed = spec.fixed_func
|
|
|
|
|
end
|
2023-06-04 11:39:20 +00:00
|
|
|
|
skill.excludeFrom = spec.exclude_from or skill.excludeFrom
|
2023-02-15 13:20:40 +00:00
|
|
|
|
|
2023-01-21 16:49:11 +00:00
|
|
|
|
return skill
|
|
|
|
|
end
|
|
|
|
|
|
2023-01-29 10:11:41 +00:00
|
|
|
|
---@class TargetModSpec: StatusSkillSpec
|
2023-12-03 11:35:14 +00:00
|
|
|
|
---@field public bypass_times? fun(self: TargetModSkill, player: Player, skill: ActiveSkill, scope: integer, card: Card, to: Player): boolean?
|
|
|
|
|
---@field public residue_func? fun(self: TargetModSkill, player: Player, skill: ActiveSkill, scope: integer, card: Card, to: Player): number?
|
|
|
|
|
---@field public bypass_distances? fun(self: TargetModSkill, player: Player, skill: ActiveSkill, card: Card, to: Player): boolean?
|
|
|
|
|
---@field public distance_limit_func? fun(self: TargetModSkill, player: Player, skill: ActiveSkill, card: Card, to: Player): number?
|
|
|
|
|
---@field public extra_target_func? fun(self: TargetModSkill, player: Player, skill: ActiveSkill, card: Card): number?
|
2023-01-29 10:11:41 +00:00
|
|
|
|
|
|
|
|
|
---@param spec TargetModSpec
|
|
|
|
|
---@return TargetModSkill
|
|
|
|
|
function fk.CreateTargetModSkill(spec)
|
|
|
|
|
assert(type(spec.name) == "string")
|
|
|
|
|
|
|
|
|
|
local skill = TargetModSkill:new(spec.name)
|
2023-02-26 07:01:14 +00:00
|
|
|
|
readStatusSpecToSkill(skill, spec)
|
2023-06-20 05:37:03 +00:00
|
|
|
|
if spec.bypass_times then
|
|
|
|
|
skill.bypassTimesCheck = spec.bypass_times
|
2023-06-18 16:20:50 +00:00
|
|
|
|
end
|
2023-01-29 10:11:41 +00:00
|
|
|
|
if spec.residue_func then
|
|
|
|
|
skill.getResidueNum = spec.residue_func
|
|
|
|
|
end
|
2023-06-20 05:37:03 +00:00
|
|
|
|
if spec.bypass_distances then
|
|
|
|
|
skill.bypassDistancesCheck = spec.bypass_distances
|
|
|
|
|
end
|
2023-01-29 10:11:41 +00:00
|
|
|
|
if spec.distance_limit_func then
|
|
|
|
|
skill.getDistanceLimit = spec.distance_limit_func
|
|
|
|
|
end
|
|
|
|
|
if spec.extra_target_func then
|
|
|
|
|
skill.getExtraTargetNum = spec.extra_target_func
|
|
|
|
|
end
|
2023-02-15 13:20:40 +00:00
|
|
|
|
|
2023-01-29 10:11:41 +00:00
|
|
|
|
return skill
|
|
|
|
|
end
|
|
|
|
|
|
2023-02-15 11:54:35 +00:00
|
|
|
|
---@class FilterSpec: StatusSkillSpec
|
2023-12-03 11:35:14 +00:00
|
|
|
|
---@field public card_filter? fun(self: FilterSkill, card: Card, player: Player): boolean?
|
|
|
|
|
---@field public view_as? fun(self: FilterSkill, card: Card, player: Player): Card?
|
2023-02-15 11:54:35 +00:00
|
|
|
|
|
|
|
|
|
---@param spec FilterSpec
|
|
|
|
|
---@return FilterSkill
|
|
|
|
|
function fk.CreateFilterSkill(spec)
|
|
|
|
|
assert(type(spec.name) == "string")
|
|
|
|
|
|
|
|
|
|
local skill = FilterSkill:new(spec.name)
|
2023-02-26 07:01:14 +00:00
|
|
|
|
readStatusSpecToSkill(skill, spec)
|
2023-02-15 11:54:35 +00:00
|
|
|
|
skill.cardFilter = spec.card_filter
|
|
|
|
|
skill.viewAs = spec.view_as
|
|
|
|
|
|
|
|
|
|
return skill
|
|
|
|
|
end
|
|
|
|
|
|
2023-03-20 12:49:23 +00:00
|
|
|
|
---@class InvaliditySpec: StatusSkillSpec
|
2023-12-03 11:35:14 +00:00
|
|
|
|
---@field public invalidity_func? fun(self: InvaliditySkill, from: Player, skill: Skill): boolean?
|
2023-03-20 12:49:23 +00:00
|
|
|
|
|
|
|
|
|
---@param spec InvaliditySpec
|
|
|
|
|
---@return InvaliditySkill
|
|
|
|
|
function fk.CreateInvaliditySkill(spec)
|
|
|
|
|
assert(type(spec.name) == "string")
|
|
|
|
|
|
|
|
|
|
local skill = InvaliditySkill:new(spec.name)
|
|
|
|
|
readStatusSpecToSkill(skill, spec)
|
|
|
|
|
skill.getInvalidity = spec.invalidity_func
|
|
|
|
|
|
|
|
|
|
return skill
|
|
|
|
|
end
|
|
|
|
|
|
2022-04-30 07:27:56 +00:00
|
|
|
|
---@class CardSpec: Card
|
2023-12-03 11:35:14 +00:00
|
|
|
|
---@field public skill? Skill
|
|
|
|
|
---@field public equip_skill? Skill
|
|
|
|
|
---@field public special_skills? string[]
|
|
|
|
|
---@field public is_damage_card? boolean
|
|
|
|
|
---@field public multiple_targets? boolean
|
2022-04-30 07:27:56 +00:00
|
|
|
|
|
|
|
|
|
local defaultCardSkill = fk.CreateActiveSkill{
|
|
|
|
|
name = "default_card_skill",
|
|
|
|
|
on_use = function(self, room, use)
|
|
|
|
|
if not use.tos or #TargetGroup:getRealTargets(use.tos) == 0 then
|
|
|
|
|
use.tos = { { use.from } }
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-12 18:25:04 +00:00
|
|
|
|
local defaultEquipSkill = fk.CreateActiveSkill{
|
|
|
|
|
name = "default_equip_skill",
|
2023-12-09 13:57:47 +00:00
|
|
|
|
prompt = function(_, selected_cards, _)
|
|
|
|
|
return "#default_equip_skill:::" .. Fk:getCardById(selected_cards).name .. ":" .. Fk:getCardById(selected_cards):getSubtypeString()
|
|
|
|
|
end,
|
2023-08-13 04:34:36 +00:00
|
|
|
|
mod_target_filter = function(self, to_select, selected, user, card, distance_limited)
|
|
|
|
|
return #Fk:currentRoom():getPlayerById(to_select):getAvailableEquipSlots(card.sub_type) > 0
|
|
|
|
|
end,
|
2023-08-12 18:25:04 +00:00
|
|
|
|
can_use = function(self, player, card)
|
2023-08-13 04:34:36 +00:00
|
|
|
|
return self:modTargetFilter(player.id, {}, player.id, card, true) and not player:isProhibited(player, card)
|
2023-08-12 18:25:04 +00:00
|
|
|
|
end,
|
|
|
|
|
on_use = function(self, room, use)
|
|
|
|
|
if not use.tos or #TargetGroup:getRealTargets(use.tos) == 0 then
|
|
|
|
|
use.tos = { { use.from } }
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-13 12:17:39 +00:00
|
|
|
|
local function preprocessCardSpec(spec)
|
2022-04-30 07:27:56 +00:00
|
|
|
|
assert(type(spec.name) == "string" or type(spec.class_name) == "string")
|
|
|
|
|
if not spec.name then spec.name = spec.class_name
|
|
|
|
|
elseif not spec.class_name then spec.class_name = spec.name end
|
|
|
|
|
if spec.suit then assert(type(spec.suit) == "number") end
|
|
|
|
|
if spec.number then assert(type(spec.number) == "number") end
|
2023-04-13 12:17:39 +00:00
|
|
|
|
end
|
2022-04-30 07:27:56 +00:00
|
|
|
|
|
2023-04-13 12:17:39 +00:00
|
|
|
|
local function readCardSpecToCard(card, spec)
|
2023-08-12 18:25:04 +00:00
|
|
|
|
card.skill = spec.skill or (card.type == Card.TypeEquip and defaultEquipSkill or defaultCardSkill)
|
2023-06-11 04:45:12 +00:00
|
|
|
|
card.skill.cardSkill = true
|
2023-03-20 12:15:24 +00:00
|
|
|
|
card.special_skills = spec.special_skills
|
2023-04-13 12:17:39 +00:00
|
|
|
|
card.is_damage_card = spec.is_damage_card
|
2023-07-11 15:16:46 +00:00
|
|
|
|
card.multiple_targets = spec.multiple_targets
|
2023-04-13 12:17:39 +00:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
---@param spec CardSpec
|
|
|
|
|
---@return BasicCard
|
|
|
|
|
function fk.CreateBasicCard(spec)
|
|
|
|
|
preprocessCardSpec(spec)
|
|
|
|
|
local card = BasicCard:new(spec.name, spec.suit, spec.number)
|
|
|
|
|
readCardSpecToCard(card, spec)
|
2022-04-30 07:27:56 +00:00
|
|
|
|
return card
|
2022-03-31 05:29:23 +00:00
|
|
|
|
end
|
|
|
|
|
|
2022-04-02 13:39:44 +00:00
|
|
|
|
---@param spec CardSpec
|
2022-03-31 05:29:23 +00:00
|
|
|
|
---@return TrickCard
|
|
|
|
|
function fk.CreateTrickCard(spec)
|
2023-04-13 12:17:39 +00:00
|
|
|
|
preprocessCardSpec(spec)
|
2023-02-26 08:51:29 +00:00
|
|
|
|
local card = TrickCard:new(spec.name, spec.suit, spec.number)
|
2023-04-13 12:17:39 +00:00
|
|
|
|
readCardSpecToCard(card, spec)
|
2022-04-30 07:27:56 +00:00
|
|
|
|
return card
|
2022-03-31 05:29:23 +00:00
|
|
|
|
end
|
|
|
|
|
|
2022-04-02 13:39:44 +00:00
|
|
|
|
---@param spec CardSpec
|
2022-04-08 10:39:58 +00:00
|
|
|
|
---@return DelayedTrickCard
|
|
|
|
|
function fk.CreateDelayedTrickCard(spec)
|
2023-04-13 12:17:39 +00:00
|
|
|
|
preprocessCardSpec(spec)
|
2022-04-30 07:27:56 +00:00
|
|
|
|
local card = DelayedTrickCard:new(spec.name, spec.suit, spec.number)
|
2023-04-13 12:17:39 +00:00
|
|
|
|
readCardSpecToCard(card, spec)
|
2022-04-30 07:27:56 +00:00
|
|
|
|
return card
|
2022-04-08 10:39:58 +00:00
|
|
|
|
end
|
|
|
|
|
|
2023-04-13 12:17:39 +00:00
|
|
|
|
local function readCardSpecToEquip(card, spec)
|
|
|
|
|
card.equip_skill = spec.equip_skill
|
|
|
|
|
|
|
|
|
|
if spec.on_install then card.onInstall = spec.on_install end
|
|
|
|
|
if spec.on_uninstall then card.onUninstall = spec.on_uninstall end
|
|
|
|
|
end
|
|
|
|
|
|
2022-04-08 10:39:58 +00:00
|
|
|
|
---@param spec CardSpec
|
|
|
|
|
---@return Weapon
|
|
|
|
|
function fk.CreateWeapon(spec)
|
2023-04-13 12:17:39 +00:00
|
|
|
|
preprocessCardSpec(spec)
|
|
|
|
|
if spec.attack_range then
|
|
|
|
|
assert(type(spec.attack_range) == "number" and spec.attack_range >= 0)
|
|
|
|
|
end
|
2022-04-30 07:27:56 +00:00
|
|
|
|
|
|
|
|
|
local card = Weapon:new(spec.name, spec.suit, spec.number, spec.attack_range)
|
2023-04-13 12:17:39 +00:00
|
|
|
|
readCardSpecToCard(card, spec)
|
|
|
|
|
readCardSpecToEquip(card, spec)
|
2022-04-30 07:27:56 +00:00
|
|
|
|
return card
|
2022-04-08 10:39:58 +00:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
---@param spec CardSpec
|
|
|
|
|
---@return Armor
|
|
|
|
|
function fk.CreateArmor(spec)
|
2023-04-13 12:17:39 +00:00
|
|
|
|
preprocessCardSpec(spec)
|
2022-04-30 07:27:56 +00:00
|
|
|
|
local card = Armor:new(spec.name, spec.suit, spec.number)
|
2023-04-13 12:17:39 +00:00
|
|
|
|
readCardSpecToCard(card, spec)
|
|
|
|
|
readCardSpecToEquip(card, spec)
|
2022-04-30 07:27:56 +00:00
|
|
|
|
return card
|
2022-04-08 10:39:58 +00:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
---@param spec CardSpec
|
|
|
|
|
---@return DefensiveRide
|
|
|
|
|
function fk.CreateDefensiveRide(spec)
|
2023-04-13 12:17:39 +00:00
|
|
|
|
preprocessCardSpec(spec)
|
2022-04-30 07:27:56 +00:00
|
|
|
|
local card = DefensiveRide:new(spec.name, spec.suit, spec.number)
|
2023-04-13 12:17:39 +00:00
|
|
|
|
readCardSpecToCard(card, spec)
|
|
|
|
|
readCardSpecToEquip(card, spec)
|
2022-04-30 07:27:56 +00:00
|
|
|
|
return card
|
2022-04-08 10:39:58 +00:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
---@param spec CardSpec
|
|
|
|
|
---@return OffensiveRide
|
|
|
|
|
function fk.CreateOffensiveRide(spec)
|
2023-04-13 12:17:39 +00:00
|
|
|
|
preprocessCardSpec(spec)
|
2022-04-30 07:27:56 +00:00
|
|
|
|
local card = OffensiveRide:new(spec.name, spec.suit, spec.number)
|
2023-04-13 12:17:39 +00:00
|
|
|
|
readCardSpecToCard(card, spec)
|
|
|
|
|
readCardSpecToEquip(card, spec)
|
2022-04-30 07:27:56 +00:00
|
|
|
|
return card
|
2022-04-08 10:39:58 +00:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
---@param spec CardSpec
|
|
|
|
|
---@return Treasure
|
|
|
|
|
function fk.CreateTreasure(spec)
|
2023-04-13 12:17:39 +00:00
|
|
|
|
preprocessCardSpec(spec)
|
2022-04-30 07:27:56 +00:00
|
|
|
|
local card = Treasure:new(spec.name, spec.suit, spec.number)
|
2023-04-13 12:17:39 +00:00
|
|
|
|
readCardSpecToCard(card, spec)
|
|
|
|
|
readCardSpecToEquip(card, spec)
|
2022-04-30 07:27:56 +00:00
|
|
|
|
return card
|
2022-04-01 12:51:01 +00:00
|
|
|
|
end
|
2023-03-13 16:12:02 +00:00
|
|
|
|
|
|
|
|
|
---@param spec GameMode
|
|
|
|
|
---@return GameMode
|
|
|
|
|
function fk.CreateGameMode(spec)
|
|
|
|
|
assert(type(spec.name) == "string")
|
|
|
|
|
assert(type(spec.minPlayer) == "number")
|
|
|
|
|
assert(type(spec.maxPlayer) == "number")
|
|
|
|
|
local ret = GameMode:new(spec.name, spec.minPlayer, spec.maxPlayer)
|
2023-07-14 14:17:54 +00:00
|
|
|
|
ret.whitelist = spec.whitelist
|
|
|
|
|
ret.blacklist = spec.blacklist
|
2023-03-13 16:12:02 +00:00
|
|
|
|
ret.rule = spec.rule
|
|
|
|
|
ret.logic = spec.logic
|
2023-07-02 12:39:42 +00:00
|
|
|
|
|
|
|
|
|
if spec.winner_getter then
|
|
|
|
|
assert(type(spec.winner_getter) == "function")
|
|
|
|
|
ret.getWinner = spec.winner_getter
|
|
|
|
|
end
|
|
|
|
|
if spec.surrender_func then
|
|
|
|
|
assert(type(spec.surrender_func) == "function")
|
|
|
|
|
ret.surrenderFunc = spec.surrender_func
|
|
|
|
|
end
|
2023-08-01 18:19:51 +00:00
|
|
|
|
if spec.is_counted then
|
|
|
|
|
assert(type(spec.is_counted) == "function")
|
|
|
|
|
ret.countInFunc = spec.is_counted
|
|
|
|
|
end
|
2023-03-13 16:12:02 +00:00
|
|
|
|
return ret
|
|
|
|
|
end
|
2023-09-19 06:27:54 +00:00
|
|
|
|
|
|
|
|
|
-- other
|
|
|
|
|
|
|
|
|
|
---@class PoxiSpec
|
|
|
|
|
---@field name string
|
2023-12-03 11:35:14 +00:00
|
|
|
|
---@field card_filter fun(to_select: int, selected: int[], data: any, extra_data: any): boolean?
|
|
|
|
|
---@field feasible fun(selected: int[], data: any, extra_data: any): boolean?
|
|
|
|
|
---@field post_select? fun(selected: int[], data: any, extra_data: any): int[]
|
|
|
|
|
---@field default_choice? fun(data: any, extra_data: any): int[]
|
|
|
|
|
---@field prompt? string | fun(data: any, extra_data: any): string
|
2023-12-06 13:07:35 +00:00
|
|
|
|
|
|
|
|
|
---@class QmlMarkSpec
|
|
|
|
|
---@field name string
|
|
|
|
|
---@field qml_path string
|
|
|
|
|
---@field how_to_show function(name: string, value?: any): string?
|