diff --git a/CMakeLists.txt b/CMakeLists.txt index d698134c..c3242122 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.16) -project(FreeKill VERSION 0.0.4) +project(FreeKill VERSION 0.0.6) add_definitions(-DFK_VERSION=\"${CMAKE_PROJECT_VERSION}\") if (NOT ${CMAKE_SYSTEM_NAME} MATCHES "Emscripten") diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 89caa5ef..0dea08ed 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="6" + android:versionName="0.0.6"> diff --git a/android/copy_assets.sh b/android/copy_assets.sh index c0648e8d..8335c19c 100755 --- a/android/copy_assets.sh +++ b/android/copy_assets.sh @@ -1,6 +1,6 @@ #!/bin/sh -rm -rf res assets +rm -rf res/mipmap assets if [ ! -e res/mipmap ]; then mkdir -p res/mipmap diff --git a/image/lady.png b/image/lady.png new file mode 100644 index 00000000..fb81694f Binary files /dev/null and b/image/lady.png differ diff --git a/image/lobby/profile.png b/image/lobby/profile.png new file mode 100644 index 00000000..52d78008 Binary files /dev/null and b/image/lobby/profile.png differ diff --git a/image/widelogo.png b/image/widelogo.png new file mode 100644 index 00000000..94d4c458 Binary files /dev/null and b/image/widelogo.png differ diff --git a/lang/zh_CN.ts b/lang/zh_CN.ts index 6eeaa254..5c14e8f7 100644 --- a/lang/zh_CN.ts +++ b/lang/zh_CN.ts @@ -36,8 +36,8 @@ 用户名 - Password - 密码 + Show Password + 显示密码 Join Server @@ -51,12 +51,56 @@ PackageManage 管理拓展包 + + Welcome back! + 欢迎回来! + + + Server Addr + 服务器IP + + + FAQ + 常见疑问 + + + $LoginFAQ + + 登录过程中的常见问题: + + 1. 怎么联机? + 将服务器IP输入到指定区域,然后填用户名和密码即可加入服务器。 + + 2. 怎么注册? + 无需注册,只要填写好用户名和密码,服务器就会自动为您注册。你的用户名和密码保存在服务端,下次登入还是需要输入一样的用户名和密码。但是无需担心,FK会自动为你记住密码。 + + updated packages for md5 已为您与服务器同步拓展包,请尝试再次连入 + + Splash + + Free + 自由 + + + Open + 开放 + + + Flexible + 可拓展 + + + Press Any Key... + 请按任意键... + + + Logic diff --git a/lua/client/i18n/zh_CN.lua b/lua/client/i18n/zh_CN.lua index 2cc49b8b..3e6c95a9 100644 --- a/lua/client/i18n/zh_CN.lua +++ b/lua/client/i18n/zh_CN.lua @@ -14,11 +14,13 @@ Fk:loadTranslationTable{ ["Lobby BG"] = "大厅壁纸", ["Room BG"] = "房间背景", ["Game BGM"] = "游戏BGM", + ["Poster Girl"] = "看板娘", ["Create Room"] = "创建房间", ["Room Name"] = "房间名字", ["$RoomName"] = "%1的房间", ["Player num"] = "玩家数目", + ["Select general num"] = "选将数目", ["Game Mode"] = "游戏模式", ["Enable free assign"] = "自由选将", ["General Settings"] = "通常设置", @@ -31,33 +33,67 @@ Fk:loadTranslationTable{ ["Scenarios Overview"] = "玩法一览", ["Replay"] = "录像", ["About"] = "关于", - ["about_freekill_description"] = "关于FreeKill
" .. - "以便于DIY为首要目的的开源三国杀游戏。
" .. - "
项目链接: https://github.com/Notify-ctrl/FreeKill", - ["about_qt_description"] = "关于Qt
" .. - "Qt是一个C++图形界面应用程序开发框架,拥有强大的跨平台能力以及易于使用的API。
" .. - "
本程序使用Qt 6.2+,主要利用QtQuick开发UI,同时也使用Qt的网络库开发服务端程序。
" .. - "
官网: https://www.qt.io", - ["about_lua_description"] = "关于Lua
" .. - "Lua是一种小巧、灵活、高效的脚本语言,广泛用于游戏开发中。
" .. - "
本程序使用Lua 5.4,利用其完全实现了整个游戏逻辑。
" .. - "
官网: https://www.lua.org", - ["about_ossl_description"] = "关于OpenSSL
" .. - "OpenSSL是一个开源包,用来提供安全通信与各种加密支持。
" .. - "
本程序目前用到了crypto库,以获得RSA加密算法支持。
" .. - "
官网: https://www.openssl.org", - ["about_gplv3_description"] = "关于GPLv3
" .. - "GNU通用公共许可协议(简称GPL)是一个广泛使用的自由软件许可证条款,它确保广大用户自由地使用、学习、共享或修改软件。
" .. - "
由于Qt是按照GPLv3协议开源的库,与此同时本程序用到的readline库也属于GPLv3库,再加上QSanguosha也是以GPLv3协议开源的软件(从中借鉴了不少代码和思路),因此这个项目也使用GPLv3协议开源。
" .. - "
官网: https://gplv3.fsf.org", - ["about_sqlite_description"] = "关于SQLite
" .. - "SQLite是一个轻量级的数据库,具有占用资源低、运行效率快、嵌入性好等优点。
" .. - "
FreeKill使用sqlite3在服务端保存用户的各种信息。
" .. - "
官网: https://www.sqlite.org", - ["about_git2_description"] = "关于Libgit2
" .. - "Libgit2是一个轻量级的、跨平台的、纯C实现的库,支持Git的大部分核心操作,并且支持几乎任何能与C语言交互的编程语言。
" .. - "
FreeKill使用的是libgit2的C API,与此同时使用Git完成拓展包的下载、更新、管理等等功能。
" .. - "
官网: https://libgit2.org", + ["about_freekill_description"] = [[ +# 关于FreeKill + +以便于DIY为首要目的的开源三国杀游戏。 + +项目链接: https://github.com/Notify-ctrl/FreeKill + ]], + ["about_qt_description"] = [[ +# 关于Qt + +Qt是一个C++图形界面应用程序开发框架,拥有强大的跨平台能力以及易于使用的API。 + +本程序使用Qt 6.2+,主要利用QtQuick开发UI,同时也使用Qt的网络库开发服务端程序。 + +官网: https://www.qt.io + ]], + ["about_lua_description"] = [[ +# 关于Lua + +Lua是一种小巧、灵活、高效的脚本语言,广泛用于游戏开发中。 + +本程序使用Lua 5.4,利用其完全实现了整个游戏逻辑。 + +官网: https://www.lua.org + ]], + ["about_ossl_description"] = [[ +# 关于OpenSSL + +OpenSSL是一个开源包,用来提供安全通信与各种加密支持。 + +本程序目前用到了crypto库,以获得RSA加密算法支持。 + +官网: https://www.openssl.org + ]], + ["about_gplv3_description"] = [[ +# 关于GPLv3 + +GNU通用公共许可协议(简称GPL)是一个广泛使用的自由软件许可证条款,它确保广大用户自由地使用、学习、共享或修改软件。 + +由于Qt是按照GPLv3协议开源的库,与此同时本程序用到的readline库也属于GPLv3库,再加上QSanguosha也是以GPLv3协议开源的软件(从中借鉴了不少代码和思路),因此这个项目也使用GPLv3协议开源。 + +官网: https://gplv3.fsf.org + ]], + ["about_sqlite_description"] = [[ +# 关于SQLite + +SQLite是一个轻量级的数据库,具有占用资源低、运行效率快、嵌入性好等优点。 + +FreeKill使用sqlite3在服务端保存用户的各种信息。 + +官网: https://www.sqlite.org + ]], + ["about_git2_description"] = [[ +# 关于Libgit2 + +Libgit2是一个轻量级的、跨平台的、纯C实现的库,支持Git的大部分核心操作,并且支持几乎任何能与C语言交互的编程语言。 + +FreeKill使用的是libgit2的C API,与此同时使用Git完成拓展包的下载、更新、管理等等功能。 + +官网: https://libgit2.org + ]], ["Exit Lobby"] = "退出大厅", @@ -114,6 +150,7 @@ Fk:loadTranslationTable{ ["$GameOver"] = "游戏结束", ["$Winner"] = "%1 获胜", + ["$NoWinner"] = "平局!", ["Back To Lobby"] = "返回大厅", } diff --git a/lua/server/gamelogic.lua b/lua/server/gamelogic.lua index 83971f73..b4159c59 100644 --- a/lua/server/gamelogic.lua +++ b/lua/server/gamelogic.lua @@ -67,6 +67,7 @@ end function GameLogic:chooseGenerals() local room = self.room + local generalNum = room.settings.generalNum local function setPlayerGeneral(player, general) if Fk.generals[general] == nil then return end player.general = general @@ -78,7 +79,7 @@ function GameLogic:chooseGenerals() local lord_general = nil if lord ~= nil then room.current = lord - local generals = Fk:getGeneralsRandomly(3) + local generals = Fk:getGeneralsRandomly(generalNum) for i = 1, #generals do generals[i] = generals[i].name end @@ -88,14 +89,13 @@ function GameLogic:chooseGenerals() end local nonlord = room:getOtherPlayers(lord, true) - local generals = Fk:getGeneralsRandomly(#nonlord * 3, nil, {lord_general}) + local generals = Fk:getGeneralsRandomly(#nonlord * generalNum, nil, {lord_general}) table.shuffle(generals) for _, p in ipairs(nonlord) do - local arg = { - (table.remove(generals, 1)).name, - (table.remove(generals, 1)).name, - (table.remove(generals, 1)).name, - } + local arg = {} + for i = 1, generalNum do + table.insert(arg, table.remove(generals, 1).name) + end p.request_data = json.encode(arg) p.default_reply = arg[1] end diff --git a/lua/server/room.lua b/lua/server/room.lua index 1df7862f..437e4e03 100644 --- a/lua/server/room.lua +++ b/lua/server/room.lua @@ -87,6 +87,11 @@ function Room:initialize(_room) -- If ret == true, then when err_msg is true, that means no request end + + if not self.game_finished then + self:doBroadcastNotify("GameOver", "") + self.room:gameOver() + end end self.players = {} @@ -274,6 +279,9 @@ function Room:getNCards(num, from) while num > 0 do if #self.draw_pile < 1 then self:shuffleDrawPile() + if #self.draw_pile < 1 then + self:gameOver("") + end end local index = from == "top" and 1 or #self.draw_pile diff --git a/packages/standard/game_rule.lua b/packages/standard/game_rule.lua index fad0a1cf..7ee12d2f 100644 --- a/packages/standard/game_rule.lua +++ b/packages/standard/game_rule.lua @@ -6,7 +6,7 @@ local function getWinner(victim) if victim.role == "lord" then if #alive == 1 and alive[1].role == "renegade" then - winner = "renegede" + winner = "renegade" else winner = "rebel" end diff --git a/packages/standard/i18n/zh_CN.lua b/packages/standard/i18n/zh_CN.lua index d4a55896..728321c4 100644 --- a/packages/standard/i18n/zh_CN.lua +++ b/packages/standard/i18n/zh_CN.lua @@ -137,6 +137,171 @@ Fk:loadTranslationTable{ [":biyue"] = "结束阶段开始时,你可以摸一张牌。", ["aaa_role_mode"] = "身份模式", + [":aaa_role_mode"] = [========================================[ +# 身份模式简介 + +___ + +(原文地址: https://sgs.52pk.com/zl/201205/5299813.shtml ) + +你即将开始学习一款集角色扮演、战斗、伪装等要素于一体的多人卡牌游戏。它能让你通过扮演耳熟能详的三国角色,在颠覆性的历史舞台中,演义一段扑朔迷离并充满刺激的较量。你将会充分体验到与玩家博弈的乐趣,它将是你聚会休闲的最佳伙伴,它就是――三国杀。 + +___ + +## 游戏配件 + +游戏需要用到的牌为:身份牌,体力牌,武将牌,游戏牌。 + +身份牌8张:1主公,2忠臣,4反贼,1内奸;武将牌25张;游戏牌104张+EX游戏牌4张;体力牌8张。 + +___ + +## 游戏目标 + +玩家的游戏目标是由拿到的身份牌决定的,每种身份的胜利条件如下: + +- 主公:消灭所有的反贼和内奸,平定天下。 +- 忠臣:不惜一切代价保护主公,胜利条件与主公相同。 +- 反贼:杀死主公,推翻统治。 +- 内奸:除掉除自己之外的所有人,成为最后的生还者。 + +___ + +## 决定身份 + +参考下表,抽取等于玩家人数的身份牌: + +| 玩家人数 | 主公 | 忠臣 | 反贼 | 内奸 | +| -------- | ---- | ---- | ---- | ---- | +| 2 | 1 | 0 | 1 | 0 | +| 3 | 1 | 0 | 1 | 1 | +| 4 | 1 | 1 | 1 | 1 | +| 5 | 1 | 1 | 2 | 1 | +| 6 | 1 | 1 | 3 | 1 | +| 7 | 1 | 2 | 3 | 1 | +| 8 | 1 | 2 | 4 | 1 | + +随机分给每个玩家一张身份牌,抽到主公身份的玩家需立即亮出身份(将主公牌正面朝上放在面前),其它身份的玩家保存好自己的身份牌,不让别的玩家知道自己的身份。 + +___ + +## 挑选武将 + +带有特殊能力的武将牌为游戏提供了丰富的变化和乐趣,但同时也让游戏变得相对复杂,如果是第一次玩三国杀,建议暂且不使用武将牌。请略过这部分,继续看“分发体力牌”。若对三国杀已经有了大致的了解,可以尝试加入武将进行游戏,按如下的步骤挑选武将。 + +首先分给获得主公身份的玩家(以下简称主公玩家)“曹操”、“刘备”、“孙权”,和另外随机抽取的2张武将牌,一共5张武将牌。由主公玩家挑选一个武将扮演,并将选好的武将牌展示给其他玩家。 + +将剩余的24张武将牌洗混,随机发给其余每位玩家各3张(10人游戏时每人发2张)。接着每人从3张牌里挑选一张扣在自己面前,待所有玩家都挑选好后同时亮出。将剩余未选的武将牌洗混,面朝下放在一旁。 + +___ + +## 分发体力牌 + +拿取对应人物体力上限的体力牌(看阴阳鱼的数量),用武将牌盖住左侧。 + +主公在其体力上限的基础上再增加一点(四人游戏时不再额外增加)。 + +例如三点体力的角色当主公时,他/她的体力上限就是四点,使用四点体力的体力牌。 + +将体力牌置于武将牌下方,露出当前体力值。扣减体力时,将武将牌右移挡住被扣减的体力。 + +___ + +## 回合流程 + +将游戏牌洗混,随机分给每个玩家4张,此为起始手牌(手牌:拿在手里的牌)。 + +将剩余游戏牌放在桌子中央,作为牌堆(玩家在游戏中弃置的牌放在一旁,组成弃牌堆,弃牌堆里的牌全部正面朝上放置)。 + +进行游戏时,由主公开始,按逆时针方向以回合的方式进行。 + +即:每名玩家有一个自己的回合,一名玩家回合结束后,其右边玩家的回合开始,依次轮流进行。 + +每个玩家的回合可以分为六个阶段: + +1. 准备阶段 + +2. 判定阶段 + +3. 摸牌阶段 + +4. 出牌阶段 + +5. 弃牌阶段 + +6. 结束阶段 + +下面对这几个阶段进行描述: + +### 准备阶段 + +通常可以跳过,有些武将可以使用此阶段的技能。 + +### 判定阶段 + +若你的面前横置着延时类锦囊,你必须依次对这些延时类锦囊进行判定。 + +*若你的面前横置有两种或更多的延时类锦囊,你从最后一个施加给你的锦囊开始判定(最早放置的最后判定)。* + +### 摸牌阶段 + +你从牌堆顶摸两张牌。 + +*在游戏里,若没有特殊说明,“摸…张牌”指的就是从牌堆最上方摸牌。* + +*当需要摸牌或将要对牌堆产生影响时,牌堆没牌,则立即将弃牌堆洗混后形成新的摸牌堆。* + +### 出牌阶段 + +你可以使用0到任意张牌,加强自己或攻击他人,但必须遵守以下两条规则: + +1. 每个出牌阶段仅限使用一次【杀】。 + +2. 任何一个玩家面前的判定区或装备区里不能放有两张同名的牌。 + +每使用一张牌,即执行该牌之效果,详见“游戏牌详解”。如无特殊说明,游戏牌在使用后均需弃置(放入弃牌堆)。 + +### 弃牌阶段 + +在出牌阶段中,不想出或没法出牌时,就进入弃牌阶段,此时检查你的手牌数是否超出你当前的体力值(你的手牌上限等于你当前的体力值),每超出一张,需要弃一张手牌。 + +### 结束阶段 + +通常可以跳过,有些武将可以使用此阶段的技能。 + +___ + +## 补充说明 + +1. 在游戏里,若无特殊说明,摸牌即是说从游戏牌堆顶摸牌。 + +2. 玩家在游戏中使用、打出或弃置的游戏牌放在一旁,组成弃牌堆,弃牌堆里的牌全部正面朝上放置。 + +3. 当牌堆没牌时,则立即将弃牌堆洗混后形成新的牌堆。 + +4. “体力上限”与“当前体力值”不一样,请注意区别。 + +___ + +## 武将死亡 + +当一个武将的体力降到0或更低时,即进入濒死状态,除非自己或他人在此时用【桃】来挽救该武将,否则该武将死亡。武将死亡后,弃置该武将所有牌及判定区里的牌,并亮出身份牌。 + +任何人杀死一名反贼(即凶手也是反贼),立即摸三张牌。 + +若主公杀死了忠臣,主公需要立即弃掉所有手牌和已装备的牌。 + +___ + +## 游戏结束 + +当以下任意一种情况发生时,游戏立即结束: + +1、主公被杀。此时若内奸是唯一存活的角色(有且仅有一名内奸存活),则内奸获胜,除此之外的情况为反贼获胜(不论反贼角色死活)。 + +2、所有的反贼和内奸都已死亡:主公和忠臣(不论死活)都获胜。 + + ]========================================], } -- aux skills diff --git a/qml/Config.qml b/qml/Config.qml index 85e2be20..efa4fd09 100644 --- a/qml/Config.qml +++ b/qml/Config.qml @@ -16,6 +16,8 @@ QtObject { property var disabledPack: [] property string preferedMode property int preferedPlayerNum + property int preferredGeneralNum + property string ladyImg // Player property of client property string serverAddr @@ -44,6 +46,8 @@ QtObject { disabledPack = conf.disabledPack || [ "test_p_0" ]; preferedMode = conf.preferedMode || "aaa_role_mode"; preferedPlayerNum = conf.preferedPlayerNum || 2; + preferredGeneralNum = conf.preferredGeneralNum || 3; + ladyImg = conf.ladyImg || AppPath + "/image/lady"; } function saveConf() { @@ -60,6 +64,8 @@ QtObject { conf.disabledPack = disabledPack; conf.preferedMode = preferedMode; conf.preferedPlayerNum = preferedPlayerNum; + conf.ladyImg = ladyImg; + conf.preferredGeneralNum = preferredGeneralNum; Backend.saveConf(JSON.stringify(conf, undefined, 2)); } diff --git a/qml/Pages/About.qml b/qml/Pages/About.qml index 87b69fc8..d320a830 100644 --- a/qml/Pages/About.qml +++ b/qml/Pages/About.qml @@ -29,7 +29,7 @@ Item { Item { Rectangle { anchors.centerIn: parent - color: "#88888888" + color: "#88EEEEEE" radius: 2 width: root.width * 0.8 height: root.height * 0.8 @@ -50,7 +50,7 @@ Item { width: parent.width * 0.65 text: Backend.translate("about_" + dest + "_description") wrapMode: Text.WordWrap - textFormat: Text.RichText + textFormat: Text.MarkdownText font.pixelSize: 18 } } diff --git a/qml/Pages/Init.qml b/qml/Pages/Init.qml index d8c1a86a..78f99589 100644 --- a/qml/Pages/Init.qml +++ b/qml/Pages/Init.qml @@ -1,92 +1,179 @@ import QtQuick +import QtQuick.Layouts import QtQuick.Controls Item { id: root - Frame { - id: join_server + Item { + width: 960 * 0.8 + height: 540 * 0.8 anchors.centerIn: parent - scale: 1.5 - background: Rectangle { - color: "#88888888" - radius: 2 + + Item { + id: left + width: 300 + height: parent.height + + Image { + id: lady + width: parent.width + 20 + height: parent.height + fillMode: Image.PreserveAspectFit + } + + Image { + anchors.bottom: parent.bottom + anchors.bottomMargin: 12 + width: parent.width + source: AppPath + "/image/widelogo" + } } - Column { - spacing: 8 - ComboBox { - id: server_addr - model: [] - editable: true + Rectangle { + id: right + anchors.left: left.right + width: parent.width - left.width + height: parent.height + color: "#88EEEEEE" + radius: 16 - onEditTextChanged: { - if (model.indexOf(editText) === -1) { - passwordEdit.text = ""; - } else { - let data = config.savedPassword[editText]; - screenNameEdit.text = data.username; - passwordEdit.text = data.shorten_password; + ColumnLayout { + width: parent.width * 0.8 + height: parent.height * 0.8 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: 40 + //spacing + + Text { + text: qsTr("Welcome back!") + font.pixelSize: 28 + Layout.alignment: Qt.AlignHCenter + } + + GridLayout { + columns: 2 + rowSpacing: 20 + + Text { + text: qsTr("Server Addr") + } + ComboBox { + id: server_addr + Layout.fillWidth: true + model: [] + editable: true + + onEditTextChanged: { + if (model.indexOf(editText) === -1) { + passwordEdit.text = ""; + } else { + let data = config.savedPassword[editText]; + screenNameEdit.text = data.username; + passwordEdit.text = data.shorten_password; + } + } + } + + Text { + text: qsTr("Username") + } + TextField { + id: screenNameEdit + Layout.fillWidth: true + placeholderText: qsTr("Username") + text: "" + onTextChanged: { + passwordEdit.text = ""; + let data = config.savedPassword[server_addr.editText]; + if (data) { + if (text === data.username) { + passwordEdit.text = data.shorten_password; + } + } + } + } + + CheckBox { + id: showPasswordCheck + text: qsTr("Show Password") + } + TextField { + id: passwordEdit + Layout.fillWidth: true + placeholderText: qsTr("Password") + text: "" + echoMode: showPasswordCheck.checked ? TextInput.Normal : TextInput.Password + passwordCharacter: "*" } } - } - TextField { - id: screenNameEdit - placeholderText: qsTr("Username") - text: "" - onTextChanged: { - passwordEdit.text = ""; - let data = config.savedPassword[server_addr.editText]; - if (data) { - if (text === data.username) { - passwordEdit.text = data.shorten_password; + + Button { + text: qsTr("Join Server") + Layout.fillWidth: true + enabled: passwordEdit.text !== "" + onClicked: { + config.serverAddr = server_addr.editText; + config.screenName = screenNameEdit.text; + config.password = passwordEdit.text; + mainWindow.busy = true; + Backend.joinServer(server_addr.editText); + } + } + + RowLayout { + Button { + Layout.preferredWidth: 180 + text: qsTr("Console start") + enabled: passwordEdit.text !== "" + onClicked: { + config.serverAddr = "127.0.0.1"; + config.screenName = screenNameEdit.text; + config.password = passwordEdit.text; + mainWindow.busy = true; + Backend.startServer(9527); + Backend.joinServer("127.0.0.1"); + } + } + + Button { + Layout.fillWidth: true + text: qsTr("PackageManage") + onClicked: { + mainStack.push(packageManage); } } } } - /*TextField { - id: avatarEdit - text: "liubei" - }*/ - TextField { - id: passwordEdit - placeholderText: qsTr("Password") - text: "" - echoMode: TextInput.Password - passwordCharacter: "*" - } - Button { - text: qsTr("Join Server") - enabled: passwordEdit.text !== "" - onClicked: { - config.serverAddr = server_addr.editText; - config.screenName = screenNameEdit.text; - config.password = passwordEdit.text; - mainWindow.busy = true; - Backend.joinServer(server_addr.editText); - } - } - Button { - text: qsTr("Console start") - enabled: passwordEdit.text !== "" - onClicked: { - config.serverAddr = "127.0.0.1"; - config.screenName = screenNameEdit.text; - config.password = passwordEdit.text; - mainWindow.busy = true; - Backend.startServer(9527); - Backend.joinServer("127.0.0.1"); - } - } - } - } - Button { - anchors.right: parent.right - anchors.bottom: parent.bottom - text: qsTr("PackageManage") - onClicked: { - mainStack.push(packageManage); + Text { + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.leftMargin: 12 + anchors.bottomMargin: 12 + text: "FreeKill " + FkVersion + font.pixelSize: 16 + font.bold: true + } + + Text { + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.rightMargin: 8 + anchors.bottomMargin: 8 + text: qsTr("FAQ") + color: "blue" + font.pixelSize: 24 + font.underline: true + + TapHandler { + onTapped: { + errDialog.txt = qsTr("$LoginFAQ"); + errDialog.open(); + } + } + } } } @@ -96,12 +183,17 @@ Item { Component.onCompleted: { config.loadConf(); + + lady.source = config.ladyImg; + server_addr.model = Object.keys(config.savedPassword); server_addr.onModelChanged(); server_addr.currentIndex = server_addr.model.indexOf(config.lastLoginServer); let data = config.savedPassword[config.lastLoginServer]; - screenNameEdit.text = data.username; - passwordEdit.text = data.shorten_password; + if (data) { + screenNameEdit.text = data.username; + passwordEdit.text = data.shorten_password; + } } } diff --git a/qml/Pages/Lobby.qml b/qml/Pages/Lobby.qml index e996c763..cdb765c8 100644 --- a/qml/Pages/Lobby.qml +++ b/qml/Pages/Lobby.qml @@ -2,6 +2,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Window import QtQuick.Layouts +import "LobbyElement" import "Logic.js" as Logic Item { @@ -73,8 +74,13 @@ Item { id: roomModel } + PersonalSettings { + } + RowLayout { - anchors.fill: parent + anchors.centerIn: parent + width: childrenRect.width + height: parent.height Item { Layout.preferredWidth: root.width * 0.6 Layout.fillHeight: true @@ -82,7 +88,7 @@ Item { width: parent.width * 0.8 height: parent.height * 0.8 anchors.centerIn: parent - color: "#88888888" + color: "#88EEEEEE" radius: 16 Text { width: parent.width @@ -101,7 +107,7 @@ Item { } } } - +/* GridLayout { flow: GridLayout.TopToBottom rows: 4 @@ -163,6 +169,57 @@ Item { } } } +*/ + } + + RowLayout { + anchors.right: parent.right + anchors.bottom: parent.bottom + Button { + text: Backend.translate("Create Room") + onClicked: { + lobby_dialog.source = "LobbyElement/CreateRoom.qml"; + lobby_drawer.open(); + config.observing = false; + } + } + Button { + text: Backend.translate("Generals Overview") + onClicked: { + mainStack.push(mainWindow.generalsOverviewPage); + mainStack.currentItem.loadPackages(); + } + } + Button { + text: Backend.translate("Cards Overview") + onClicked: { + mainStack.push(mainWindow.cardsOverviewPage); + mainStack.currentItem.loadPackages(); + } + } + Button { + text: Backend.translate("Scenarios Overview") + onClicked: { + mainStack.push(mainWindow.modesOverviewPage); + } + } + Button { + text: Backend.translate("Replay") + } + Button { + text: Backend.translate("About") + onClicked: { + mainStack.push(mainWindow.aboutPage); + } + } + Button { + text: Backend.translate("Exit Lobby") + onClicked: { + toast.show("Goodbye."); + Backend.quitLobby(); + mainStack.pop(); + } + } } Drawer { diff --git a/qml/Pages/LobbyElement/EditProfile.qml b/qml/Pages/LobbyElement/EditProfile.qml index 2cc4a875..be536218 100644 --- a/qml/Pages/LobbyElement/EditProfile.qml +++ b/qml/Pages/LobbyElement/EditProfile.qml @@ -9,7 +9,6 @@ Item { signal finished() ColumnLayout { - spacing: 20 anchors.centerIn: parent RowLayout { @@ -142,6 +141,25 @@ Item { } } + RowLayout { + anchors.rightMargin: 8 + spacing: 16 + Text { + text: Backend.translate("Poster Girl") + } + TextField { + text: config.ladyImg + } + Button { + text: "..." + onClicked: { + fdialog.nameFilters = ["Image Files (*.jpg *.png)"]; + fdialog.configKey = "ladyImg"; + fdialog.open(); + } + } + } + RowLayout { anchors.rightMargin: 8 spacing: 16 diff --git a/qml/Pages/LobbyElement/PersonalSettings.qml b/qml/Pages/LobbyElement/PersonalSettings.qml new file mode 100644 index 00000000..a47e694c --- /dev/null +++ b/qml/Pages/LobbyElement/PersonalSettings.qml @@ -0,0 +1,54 @@ +import QtQuick +import QtQuick.Layouts +import "../skin-bank.js" as SkinBank + +Item { + id: root + width: bg.width + height: bg.height + + Image { + id: bg + x: -32 + height: 69 + source: SkinBank.LOBBY_IMG_DIR + "profile" + fillMode: Image.PreserveAspectFit + } + + RowLayout { + Item { Layout.preferredWidth: 16 } + + Image { + Layout.preferredWidth: 64 + Layout.preferredHeight: 64 + source: SkinBank.getGeneralPicture(Self.avatar) + sourceSize.width: 250 + sourceSize.height: 292 + sourceClipRect: Qt.rect(61, 0, 128, 128) + + Rectangle { + anchors.fill: parent + color: "transparent" + border.width: 1 + } + } + + Item { Layout.preferredWidth: 8 } + + Text { + Layout.alignment: Qt.AlignTop + text: Self.screenName + font.pixelSize: 22 + font.family: fontLibian.name + color: "#F0DFAF" + style: Text.Outline + } + } + + TapHandler { + onTapped: { + lobby_dialog.source = "LobbyElement/EditProfile.qml"; + lobby_drawer.open(); + } + } +} diff --git a/qml/Pages/LobbyElement/RoomGeneralSettings.qml b/qml/Pages/LobbyElement/RoomGeneralSettings.qml index 5af70467..5276903e 100644 --- a/qml/Pages/LobbyElement/RoomGeneralSettings.qml +++ b/qml/Pages/LobbyElement/RoomGeneralSettings.qml @@ -28,6 +28,7 @@ ColumnLayout { id: playerNum from: 2 to: 8 + value: config.preferedPlayerNum onValueChanged: { config.preferedPlayerNum = value; @@ -58,6 +59,24 @@ ColumnLayout { } } + RowLayout { + anchors.rightMargin: 8 + spacing: 16 + Text { + text: Backend.translate("Select general num") + } + SpinBox { + id: generalNum + from: 3 + to: 8 + value: config.preferredGeneralNum + + onValueChanged: { + config.preferredGeneralNum = value; + } + } + } + CheckBox { id: freeAssignCheck checked: Debugging ? true : false @@ -78,6 +97,7 @@ ColumnLayout { enableFreeAssign: freeAssignCheck.checked, gameMode: config.preferedMode, disabledPack: config.disabledPack, + generalNum: config.preferredGeneralNum, }]) ); } diff --git a/qml/Pages/ModesOverview.qml b/qml/Pages/ModesOverview.qml new file mode 100644 index 00000000..c86cd4cc --- /dev/null +++ b/qml/Pages/ModesOverview.qml @@ -0,0 +1,73 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls + +Item { + RowLayout { + anchors.fill: parent + spacing: 10 + + ListView { + id: listView + width: parent.width * 0.2 + height: parent.height + model: ListModel { + id: modeList + } + highlight: Rectangle { color: "lightsteelblue"; radius: 5 } + delegate: Item { + width: parent.width + height: 40 + + Text { + text: name + anchors.centerIn: parent + } + + TapHandler { + onTapped: { + listView.currentIndex = index; + } + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.fillHeight: true + color: "#88EEEEEE" + Flickable { + width: parent.width - 16 + height: parent.height - 16 + anchors.centerIn: parent + contentHeight: modeDesc.height + ScrollBar.vertical: ScrollBar {} + clip: true + + Text { + id: modeDesc + width: parent.width - 16 + wrapMode: Text.WordWrap + text: Backend.translate(":" + modeList.get(listView.currentIndex).orig_name) + textFormat: Text.MarkdownText + font.pixelSize: 16 + } + } + } + } + + Button { + text: qsTr("Quit") + anchors.bottom: parent.bottom + onClicked: { + mainStack.pop(); + } + } + + Component.onCompleted: { + let mode_data = JSON.parse(Backend.callLuaFunction("GetGameModes", [])); + for (let d of mode_data) { + modeList.append(d); + } + } +} diff --git a/qml/Pages/PackageManage.qml b/qml/Pages/PackageManage.qml index 3b8a5bc0..d3d15171 100644 --- a/qml/Pages/PackageManage.qml +++ b/qml/Pages/PackageManage.qml @@ -73,7 +73,7 @@ Item { Layout.fillHeight: true Rectangle { anchors.fill: parent - color: "#88888888" + color: "#88EEEEEE" } ListView { id: packageList diff --git a/qml/Pages/RoomElement/ChooseGeneralBox.qml b/qml/Pages/RoomElement/ChooseGeneralBox.qml index 4e0cc835..50ab8f78 100644 --- a/qml/Pages/RoomElement/ChooseGeneralBox.qml +++ b/qml/Pages/RoomElement/ChooseGeneralBox.qml @@ -27,8 +27,8 @@ GraphicsBox { Item { id: generalArea - width: (generalList.count >= 5 ? Math.ceil(generalList.count / 2) : Math.max(3, generalList.count)) * 97 - height: generalList.count >= 5 ? 290 : 150 + width: (generalList.count > 8 ? Math.ceil(generalList.count / 2) : Math.max(3, generalList.count)) * 97 + height: generalList.count > 8 ? 290 : 150 z: 1 Repeater { @@ -38,8 +38,8 @@ GraphicsBox { Item { width: 93 height: 130 - x: (index % Math.ceil(generalList.count / (generalList.count >= 5 ? 2 : 1))) * 98 + (generalList.count >= 5 && index > generalList.count / 2 && generalList.count % 2 == 1 ? 50 : 0) - y: generalList.count < 5 ? 0 : (index < generalList.count / 2 ? 0 : 135) + x: (index % Math.ceil(generalList.count / (generalList.count > 8 ? 2 : 1))) * 98 + (generalList.count > 8 && index > generalList.count / 2 && generalList.count % 2 == 1 ? 50 : 0) + y: generalList.count <= 8 ? 0 : (index < generalList.count / 2 ? 0 : 135) } } } diff --git a/qml/Pages/RoomElement/GameOverBox.qml b/qml/Pages/RoomElement/GameOverBox.qml index 3b938482..191c10b6 100644 --- a/qml/Pages/RoomElement/GameOverBox.qml +++ b/qml/Pages/RoomElement/GameOverBox.qml @@ -16,7 +16,7 @@ GraphicsBox { spacing: 10 Text { - text: Backend.translate("$Winner").arg(Backend.translate(winner)) + text: winner !== "" ? Backend.translate("$Winner").arg(Backend.translate(winner)) : Backend.translate("$NoWinner") color: "#E4D5A0" } diff --git a/qml/Pages/skin-bank.js b/qml/Pages/skin-bank.js index e1441bd1..ecd03092 100644 --- a/qml/Pages/skin-bank.js +++ b/qml/Pages/skin-bank.js @@ -14,6 +14,7 @@ var DELAYED_TRICK_DIR = AppPath + "/image/card/delayedTrick/"; var EQUIP_ICON_DIR = AppPath + "/image/card/equipIcon/"; var PIXANIM_DIR = AppPath + "/image/anim/" var TILE_ICON_DIR = AppPath + "/image/button/tileicon/" +var LOBBY_IMG_DIR = AppPath + "/image/lobby/"; function getGeneralPicture(name) { let data = JSON.parse(Backend.callLuaFunction("GetGeneralData", [name])); diff --git a/qml/Splash.qml b/qml/Splash.qml new file mode 100644 index 00000000..5421ea0d --- /dev/null +++ b/qml/Splash.qml @@ -0,0 +1,195 @@ +import QtQuick +import QtQuick.Layouts + +Rectangle { + id: splash + color: "#EEEEEE" + z: 100 + + property bool loading: true + property alias animationRunning: animation.running + + signal disappearing + signal disappeared + + Grid { + id: main + anchors.centerIn: parent + rows: splash.width >= splash.height ? 1 : 2 + columns: splash.width >= splash.height ? 2 : 1 + horizontalItemAlignment: Grid.AlignHCenter + verticalItemAlignment: Grid.AlignVCenter + spacing: 25 + + Image { + id: logo + source: AppPath + "/image/icon.png" + width: 96 + height: width + opacity: 0 + } + + Column { + spacing: 6 + + Text { + id: fktext + text: "FreeKill" + // color: "#ffffff" + font.pixelSize: 40 + opacity: 0 + } + + RowLayout { + width: parent.width + spacing: 8 + + Text { + id: free + text: qsTr("Free") + // color: "#ffffff" + font.pixelSize: 20 + opacity: 0 + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + } + + Text { + id: open + text: qsTr("Open") + // color: "#ffffff" + font.pixelSize: 20 + opacity: 0 + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + } + + Text { + id: flexible + text: qsTr("Flexible") + // color: "#ffffff" + font.pixelSize: 20 + opacity: 0 + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + } + } + } + } + + Text { + id: text + text: qsTr("Press Any Key...") + // color: "#ffffff" + opacity: 0 + font.pointSize: 15 + horizontalAlignment: Text.AlignHCenter + anchors.horizontalCenter: parent.horizontalCenter + y: (main.y + main.height + parent.height - height) / 2 + SequentialAnimation on opacity { + id: textAni + running: false + loops: Animation.Infinite + NumberAnimation { from: 0; to: 1; duration: 1600; easing.type: Easing.InOutQuad; } + NumberAnimation { from: 1; to: 0; duration: 1600; easing.type: Easing.InOutQuad; } + } + } + + SequentialAnimation { + id: animation + running: true + + PauseAnimation { + duration: 400 + } + + ParallelAnimation { + NumberAnimation { + target: fktext + property: "opacity" + duration: 500 + easing.type: Easing.InOutQuad + to: 1 + } + + NumberAnimation { + target: logo + property: "opacity" + duration: 500 + easing.type: Easing.InOutQuad + to: 1 + } + } + + NumberAnimation { + target: free + property: "opacity" + duration: 400 + easing.type: Easing.InOutQuad + to: 1 + } + + NumberAnimation { + target: open + property: "opacity" + duration: 400 + easing.type: Easing.InOutQuad + to: 1 + } + + NumberAnimation { + target: flexible + property: "opacity" + duration: 400 + easing.type: Easing.InOutQuad + to: 1 + } + + + ScriptAction { script: textAni.start(); } + + PropertyAction { target: splash; property: "loading"; value: false } + } + + /* + Text { + text: qsTr("Powered by Mogara") + color: "#f39292" + font.pixelSize: 20 + anchors.bottom: parent.bottom + anchors.right: parent.right + } + */ + + //--------------------Disappear-------------- + Behavior on opacity { + SequentialAnimation { + NumberAnimation { duration: 1200; easing.type: Easing.InOutQuad } + ScriptAction { script: disappeared() } + } + } + MouseArea { + acceptedButtons: Qt.AllButtons + anchors.fill: parent + onClicked: { + disappear(); + } + } + + Keys.onPressed: { + disappear(); + event.accepted = true + } + + NumberAnimation { + id: logoMover + target: logo + property: "x" + to: -splash.width + duration: 1000 + easing.type: Easing.InOutQuad + } + + function disappear() { + disappearing(); + logoMover.start(); + opacity = 0; + } +} diff --git a/qml/Toast.qml b/qml/Toast.qml index 9dd421bf..a2266b23 100644 --- a/qml/Toast.qml +++ b/qml/Toast.qml @@ -19,11 +19,9 @@ Rectangle { radius: 16 opacity: 0 - color: "#F2808A87" Text { id: message - color: "white" horizontalAlignment: Text.AlignHCenter anchors.centerIn: parent } diff --git a/qml/main.qml b/qml/main.qml index 980f712b..136c83f6 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -49,11 +49,13 @@ Item { Component { id: lobby; Lobby {} } Component { id: generalsOverview; GeneralsOverview {} } Component { id: cardsOverview; CardsOverview {} } + Component { id: modesOverview; ModesOverview {} } Component { id: room; Room {} } Component { id: aboutPage; About {} } property var generalsOverviewPage property var cardsOverviewPage + property alias modesOverviewPage: modesOverview property alias aboutPage: aboutPage property bool busy: false property string busyText: "" @@ -71,7 +73,7 @@ Item { width: parent.width Rectangle { anchors.fill: parent - color: "#88888888" + color: "#88EEEEEE" } Text { anchors.centerIn: parent @@ -198,9 +200,20 @@ Item { } } + Loader { + id: splashLoader + anchors.fill: parent + } + Component.onCompleted: { if (OS !== "Web") { mainStack.push(init); + if (!Debugging) { + splashLoader.source = "Splash.qml"; + splashLoader.item.disappeared.connect(() => { + splashLoader.source = ""; + }); + } } else { mainStack.push(webinit); } diff --git a/src/main.cpp b/src/main.cpp index 93f91160..f90f755e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,4 @@ +#include "client.h" #ifndef Q_OS_WASM #include "server.h" #include "packman.h" @@ -219,6 +220,7 @@ int main(int argc, char *argv[]) Pacman = new PackMan; # endif + engine->rootContext()->setContextProperty("FkVersion", FK_VERSION); engine->rootContext()->setContextProperty("Backend", &backend); engine->rootContext()->setContextProperty("Pacman", Pacman);