Gamemode (#75)

提供拓展游戏模式的接口
This commit is contained in:
notify 2023-03-14 00:12:02 +08:00 committed by GitHub
parent 694deecdf1
commit 6ae86a1e3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 162 additions and 2 deletions

27
doc/dev/scenario.md Normal file
View File

@ -0,0 +1,27 @@
# 关于扩展FreeKill玩法的思考
___
要扩展玩法,大概就这些:
1. 扩展新规则,覆盖本来的身份版规则
2. 直接大改gamelogic把流程都改了
要将扩展的玩法放进游戏:
1. 首先创房间的时候有下拉菜单给人选模式
2. Room正式开始之后根据模式加载相应的Logic
3. 加载GameRule后根据模式加载特殊规则
4. 开始玩
___
## 拓展新规
首先就是如何覆盖老规则这个可以通过设置一个特殊tag
___
## 拓展logic
从GameLogic继承然后重写有关函数就行

View File

@ -376,4 +376,18 @@ function GetExpandPileOfSkill(skillName)
return skill and (skill.expand_pile or "") or "" return skill and (skill.expand_pile or "") or ""
end end
function GetGameModes()
local ret = {}
for k, v in pairs(Fk.game_modes) do
table.insert(ret, {
name = Fk:translate(v.name),
orig_name = v.name,
minPlayer = v.minPlayer,
maxPlayer = v.maxPlayer,
})
end
table.sort(ret, function(a, b) return a.name > b.name end)
return json.encode(ret)
end
dofile "lua/client/i18n/init.lua" dofile "lua/client/i18n/init.lua"

View File

@ -19,6 +19,7 @@ Fk:loadTranslationTable{
["Room Name"] = "房间名字", ["Room Name"] = "房间名字",
["$RoomName"] = "%1的房间", ["$RoomName"] = "%1的房间",
["Player num"] = "玩家数目", ["Player num"] = "玩家数目",
["Game Mode"] = "游戏模式",
["Enable free assign"] = "自由选将", ["Enable free assign"] = "自由选将",
["Generals Overview"] = "武将一览", ["Generals Overview"] = "武将一览",

View File

@ -9,6 +9,7 @@
---@field lords string[] ---@field lords string[]
---@field cards Card[] ---@field cards Card[]
---@field translations table<string, table<string, string>> ---@field translations table<string, table<string, string>>
---@field game_modes table<string, GameMode>
local Engine = class("Engine") local Engine = class("Engine")
function Engine:initialize() function Engine:initialize()
@ -30,6 +31,7 @@ function Engine:initialize()
self.lords = {} -- lordName[] self.lords = {} -- lordName[]
self.cards = {} -- Card[] self.cards = {} -- Card[]
self.translations = {} -- srcText --> translated self.translations = {} -- srcText --> translated
self.game_modes = {}
self:loadPackages() self:loadPackages()
self:addSkills(AuxSkills) self:addSkills(AuxSkills)
@ -51,6 +53,7 @@ function Engine:loadPackage(pack)
self:addGenerals(pack.generals) self:addGenerals(pack.generals)
end end
self:addSkills(pack:getSkills()) self:addSkills(pack:getSkills())
self:addGameModes(pack.game_modes)
end end
function Engine:loadPackages() function Engine:loadPackages()
@ -178,6 +181,22 @@ function Engine:cloneCard(name, suit, number)
return ret return ret
end end
---@param game_modes GameMode[]
function Engine:addGameModes(game_modes)
for _, s in ipairs(game_modes) do
self:addGameMode(s)
end
end
---@param game_mode GameMode
function Engine:addGameMode(game_mode)
assert(game_mode:isInstanceOf(GameMode))
if self.game_modes[game_mode.name] ~= nil then
error(string.format("Duplicate game_mode %s detected", game_mode.name))
end
self.game_modes[game_mode.name] = game_mode
end
---@param num integer ---@param num integer
---@param generalPool General[] ---@param generalPool General[]
---@param except string[] ---@param except string[]

15
lua/core/game_mode.lua Normal file
View File

@ -0,0 +1,15 @@
---@class GameMode: Object
---@field name string
---@field minPlayer integer
---@field maxPlayer integer
---@field rule TriggerSkill
---@field logic GameLogic
local GameMode = class("GameMode")
function GameMode:initialize(name, min, max)
self.name = name
self.minPlayer = math.max(min, 2)
self.maxPlayer = math.min(max, 8)
end
return GameMode

View File

@ -6,6 +6,7 @@
---@field extra_skills Skill[] ---@field extra_skills Skill[]
---@field related_skills table<string, string> ---@field related_skills table<string, string>
---@field cards Card[] ---@field cards Card[]
---@field game_modes GameMode[]
local Package = class("Package") local Package = class("Package")
---@alias PackageType integer ---@alias PackageType integer
@ -25,6 +26,7 @@ function Package:initialize(name, _type)
self.extra_skills = {} -- skill not belongs to any generals, like "jixi" self.extra_skills = {} -- skill not belongs to any generals, like "jixi"
self.related_skills = {} self.related_skills = {}
self.cards = {} self.cards = {}
self.game_modes = {}
end end
---@return Skill[] ---@return Skill[]
@ -60,4 +62,8 @@ function Package:addCards(cards)
end end
end end
function Package:addGameMode(game_mode)
table.insert(self.game_modes, game_mode)
end
return Package return Package

View File

@ -465,3 +465,15 @@ function fk.CreateTreasure(spec)
if spec.on_uninstall then card.onUninstall = spec.on_uninstall end if spec.on_uninstall then card.onUninstall = spec.on_uninstall end
return card return card
end end
---@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)
ret.rule = spec.rule
ret.logic = spec.logic
return ret
end

View File

@ -25,6 +25,7 @@ Skill = require "core.skill"
UsableSkill = require "core.skill_type.usable_skill" UsableSkill = require "core.skill_type.usable_skill"
StatusSkill = require "core.skill_type.status_skill" StatusSkill = require "core.skill_type.status_skill"
Player = require "core.player" Player = require "core.player"
GameMode = require "core.game_mode"
-- load config -- load config
local function loadConf() local function loadConf()

View File

@ -15,6 +15,7 @@
---@field card_place table<integer, CardArea> ---@field card_place table<integer, CardArea>
---@field owner_map table<integer, integer> ---@field owner_map table<integer, integer>
---@field status_skills Skill[] ---@field status_skills Skill[]
---@field settings table
local Room = class("Room") local Room = class("Room")
-- load classes used by the game -- load classes used by the game
@ -58,6 +59,7 @@ function Room:initialize(_room)
self.room.startGame = function(_self) self.room.startGame = function(_self)
Room.initialize(self, _room) -- clear old data Room.initialize(self, _room) -- clear old data
self.settings = json.decode(_room:settings())
local main_co = coroutine.create(function() local main_co = coroutine.create(function()
self:run() self:run()
end) end)
@ -114,7 +116,9 @@ function Room:run()
table.insert(self.players, player) table.insert(self.players, player)
end end
self.logic = GameLogic:new(self) local mode = Fk.game_modes[self.settings.gameMode]
self.logic = (mode.logic or GameLogic):new(self)
if mode.rule then self.logic:addTriggerSkill(mode.rule) end
self.logic:run() self.logic:run()
end end
@ -307,6 +311,21 @@ function Room:removePlayerMark(player, mark, count)
self:setPlayerMark(player, mark, math.max(num - count, 0)) self:setPlayerMark(player, mark, math.max(num - count, 0))
end end
---@param tag_name string
function Room:setTag(tag_name, value)
self.tag[tag_name] = value
end
---@param tag_name string
function Room:getTag(tag_name)
return self.tag[tag_name]
end
---@param tag_name string
function Room:removeTag(tag_name)
self.tag[tag_name] = nil
end
------------------------------------------------------------------------ ------------------------------------------------------------------------
-- network functions, notify function -- network functions, notify function
------------------------------------------------------------------------ ------------------------------------------------------------------------

View File

@ -135,6 +135,8 @@ Fk:loadTranslationTable{
[":lijian"] = "阶段技,你可以弃置一张牌并选择两名其他男性角色,后选择的角色视为对先选择的角色使用了一张不能被无懈可击的决斗。", [":lijian"] = "阶段技,你可以弃置一张牌并选择两名其他男性角色,后选择的角色视为对先选择的角色使用了一张不能被无懈可击的决斗。",
["biyue"] = "闭月", ["biyue"] = "闭月",
[":biyue"] = "结束阶段开始时,你可以摸一张牌。", [":biyue"] = "结束阶段开始时,你可以摸一张牌。",
["aaa_role_mode"] = "身份模式",
} }
-- aux skills -- aux skills

View File

@ -921,6 +921,13 @@ local diaochan = General:new(extension, "diaochan", "qun", 3, 3, General.Female)
diaochan:addSkill(lijian) diaochan:addSkill(lijian)
diaochan:addSkill(biyue) diaochan:addSkill(biyue)
local role_mode = fk.CreateGameMode{
name = "aaa_role_mode", -- just to let it at the top of list
minPlayer = 2,
maxPlayer = 8,
}
extension:addGameMode(role_mode)
-- load translations of this package -- load translations of this package
dofile "packages/standard/i18n/init.lua" dofile "packages/standard/i18n/init.lua"

View File

@ -39,6 +39,27 @@ Item {
} }
} }
RowLayout {
anchors.rightMargin: 8
spacing: 16
Text {
text: Backend.translate("Game Mode")
}
ComboBox {
id: gameModeCombo
textRole: "name"
model: ListModel {
id: gameModeList
}
onCurrentIndexChanged: {
let data = gameModeList.get(currentIndex);
playerNum.from = data.minPlayer;
playerNum.to = data.maxPlayer;
}
}
}
CheckBox { CheckBox {
id: freeAssignCheck id: freeAssignCheck
checked: Debugging ? true : false checked: Debugging ? true : false
@ -56,7 +77,8 @@ Item {
ClientInstance.notifyServer( ClientInstance.notifyServer(
"CreateRoom", "CreateRoom",
JSON.stringify([roomName.text, playerNum.value, { JSON.stringify([roomName.text, playerNum.value, {
enableFreeAssign: freeAssignCheck.checked enableFreeAssign: freeAssignCheck.checked,
gameMode: gameModeList.get(gameModeCombo.currentIndex).orig_name,
}]) }])
); );
} }
@ -69,4 +91,12 @@ Item {
} }
} }
} }
Component.onCompleted: {
let mode_data = JSON.parse(Backend.callLuaFunction("GetGameModes", []));
for (let d of mode_data) {
gameModeList.append(d);
}
gameModeCombo.currentIndex = 0;
}
} }

View File

@ -56,6 +56,12 @@ public:
bool hasRequest() const; bool hasRequest() const;
}; };
%extend Room {
QString settings() {
return $self->getSettings();
}
}
%{ %{
void Room::initLua() void Room::initLua()
{ {

View File

@ -116,6 +116,7 @@ bool QmlBackend::isDir(const QString &file) {
} }
QString QmlBackend::translate(const QString &src) { QString QmlBackend::translate(const QString &src) {
if (!ClientInstance) return src;
lua_State *L = ClientInstance->getLuaState(); lua_State *L = ClientInstance->getLuaState();
lua_getglobal(L, "Translate"); lua_getglobal(L, "Translate");
auto bytes = src.toUtf8(); auto bytes = src.toUtf8();