Compare commits
2 Commits
master
...
Dynamic-de
Author | SHA1 | Date |
---|---|---|
Ho-spair | 495c08cb0f | |
Ho-spair | 3f5f8a7f16 |
|
@ -85,10 +85,7 @@ jobs:
|
||||||
cd assets/res
|
cd assets/res
|
||||||
cp -r /etc/ssl/certs .
|
cp -r /etc/ssl/certs .
|
||||||
cp /usr/share/ca-certificates/mozilla/* certs/
|
cp /usr/share/ca-certificates/mozilla/* certs/
|
||||||
cd ../..
|
echo ${FKVER%)} > fk_ver
|
||||||
echo ${FKVER%)} > ../fk_ver
|
|
||||||
../genfkver.sh
|
|
||||||
cp ../fk_ver assets/res
|
|
||||||
|
|
||||||
- name: Configure CMake Project
|
- name: Configure CMake Project
|
||||||
working-directory: ${{github.workspace}}
|
working-directory: ${{github.workspace}}
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
/*.kdev4
|
/*.kdev4
|
||||||
/.cache/
|
/.cache/
|
||||||
/tags
|
/tags
|
||||||
/.luarc.json
|
|
||||||
|
|
||||||
# file produced by game
|
# file produced by game
|
||||||
/FreeKill
|
/FreeKill
|
||||||
|
|
31
CHANGELOG.md
31
CHANGELOG.md
|
@ -1,36 +1,5 @@
|
||||||
# ChangeLog
|
# ChangeLog
|
||||||
|
|
||||||
## v0.4.16
|
|
||||||
|
|
||||||
在引入freekill-core之后的第一次版本更新,甚至无法保证这次更新是否正常
|
|
||||||
|
|
||||||
1. 改进processPrompt,支持双将和暗将
|
|
||||||
2. 副将长名旋转
|
|
||||||
3. 国战体力上限优化,包括一览和选将框
|
|
||||||
4. 空格添加结束出牌阶段,Escape键呼出菜单
|
|
||||||
5. 武将一览左栏文本换行
|
|
||||||
6. 同名替换影响已选择的武将
|
|
||||||
7. 再次排序手牌时按照点数排序
|
|
||||||
8. Logic.js翻译
|
|
||||||
9. 进入房间翻译删去句号,跟房间内其他toast风格统一
|
|
||||||
10. 常见疑问最后一张“下一条”改为“OK!”
|
|
||||||
11. 录像回放“从文件打开”翻译
|
|
||||||
12. interaction自动弹出和关闭,comboBox补技能名
|
|
||||||
13. 卡牌音效添加装备效果音效和使用音效,小小重构
|
|
||||||
14. activeSkill的prompt的selected_targets实装
|
|
||||||
15. 禁用扩展包文本ui限制长度
|
|
||||||
16. 右键技能呼出气泡
|
|
||||||
17. 搬运了ArrangeCards。
|
|
||||||
18. 优化了GuanxingBox的操作
|
|
||||||
19. 修复了不能及时更新技能prompt的bug
|
|
||||||
20. 取消目标后会刷新目标选择
|
|
||||||
21. 完备了借刀的牌名
|
|
||||||
22. CPP代码进行大的重构,配有少量文档
|
|
||||||
23. (底层)调度机制大改
|
|
||||||
24. 大厅UI开始调整,但是仍未完工
|
|
||||||
|
|
||||||
___
|
|
||||||
|
|
||||||
## v0.4.13 & 14 & 15
|
## v0.4.13 & 14 & 15
|
||||||
|
|
||||||
- 优化重连逻辑
|
- 优化重连逻辑
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
project(FreeKill VERSION 0.4.16)
|
project(FreeKill VERSION 0.4.15)
|
||||||
add_definitions(-DFK_VERSION=\"${CMAKE_PROJECT_VERSION}\")
|
add_definitions(-DFK_VERSION=\"${CMAKE_PROJECT_VERSION}\")
|
||||||
|
|
||||||
find_package(Qt6 REQUIRED COMPONENTS
|
find_package(Qt6 REQUIRED COMPONENTS
|
||||||
|
@ -39,6 +39,11 @@ include_directories(include/lua)
|
||||||
include_directories(include)
|
include_directories(include)
|
||||||
include_directories(include/libgit2)
|
include_directories(include/libgit2)
|
||||||
include_directories(src)
|
include_directories(src)
|
||||||
|
include_directories(src/client)
|
||||||
|
include_directories(src/core)
|
||||||
|
include_directories(src/network)
|
||||||
|
include_directories(src/server)
|
||||||
|
include_directories(src/ui)
|
||||||
|
|
||||||
file(GLOB SWIG_FILES "${PROJECT_SOURCE_DIR}/src/swig/*.i")
|
file(GLOB SWIG_FILES "${PROJECT_SOURCE_DIR}/src/swig/*.i")
|
||||||
if (DEFINED FK_SERVER_ONLY)
|
if (DEFINED FK_SERVER_ONLY)
|
||||||
|
@ -73,7 +78,6 @@ add_custom_command(
|
||||||
POST_BUILD
|
POST_BUILD
|
||||||
COMMENT "Generating version file fk_ver"
|
COMMENT "Generating version file fk_ver"
|
||||||
COMMAND echo ${CMAKE_PROJECT_VERSION} > ${PROJECT_SOURCE_DIR}/fk_ver
|
COMMAND echo ${CMAKE_PROJECT_VERSION} > ${PROJECT_SOURCE_DIR}/fk_ver
|
||||||
COMMAND ${PROJECT_SOURCE_DIR}/genfkver.sh
|
|
||||||
)
|
)
|
||||||
|
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
|
|
|
@ -40,13 +40,8 @@ Flickable {
|
||||||
|
|
||||||
extra_data.generals.forEach((g) => {
|
extra_data.generals.forEach((g) => {
|
||||||
const data = lcall("GetGeneralDetail", g);
|
const data = lcall("GetGeneralDetail", g);
|
||||||
skillDesc.append(luatr(data.kingdom) + " " + luatr(g) + " " + (data.hp === data.maxHp
|
skillDesc.append(luatr(data.kingdom) + " " + luatr(g) + " " + data.hp +
|
||||||
? ((g.startsWith('hs__') || g.startsWith('ld__') || g.includes('heg__'))
|
"/" + data.maxHp);
|
||||||
? ((data.mainMaxHp != 0 || data.deputyMaxHp != 0)
|
|
||||||
? ((data.hp + data.mainMaxHp) / 2 + '/' + (data.hp + data.deputyMaxHp) / 2)
|
|
||||||
: data.hp / 2)
|
|
||||||
: data.hp)
|
|
||||||
: data.hp + "/" + data.maxHp));
|
|
||||||
if (data.companions.length > 0){
|
if (data.companions.length > 0){
|
||||||
let ret = '';
|
let ret = '';
|
||||||
ret +="<font color=\"slategrey\"><b>" + luatr("Companions") + "</b>: ";
|
ret +="<font color=\"slategrey\"><b>" + luatr("Companions") + "</b>: ";
|
||||||
|
|
|
@ -50,16 +50,6 @@ Item {
|
||||||
if (idx < extra_data.cards.count) {
|
if (idx < extra_data.cards.count) {
|
||||||
extra_data.cards.set(idx, { name: modelData });
|
extra_data.cards.set(idx, { name: modelData });
|
||||||
}
|
}
|
||||||
|
|
||||||
idx = 0;
|
|
||||||
extra_data.choices.forEach( s => {
|
|
||||||
if (s === gname) {
|
|
||||||
extra_data.choices[idx] = modelData;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
idx++;
|
|
||||||
});
|
|
||||||
|
|
||||||
root.finish();
|
root.finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,8 +43,6 @@ QtObject {
|
||||||
property string password: ""
|
property string password: ""
|
||||||
property string cipherText
|
property string cipherText
|
||||||
property string aeskey
|
property string aeskey
|
||||||
// string => { roomId => config }
|
|
||||||
property var roomConfigCache: ({})
|
|
||||||
|
|
||||||
// Client data
|
// Client data
|
||||||
property string serverMotd: ""
|
property string serverMotd: ""
|
||||||
|
|
|
@ -184,12 +184,6 @@ Item {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
delegate: Text {
|
delegate: Text {
|
||||||
width: parent.width / 2
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
fontSizeMode: Text.HorizontalFit
|
|
||||||
minimumPixelSize: 14
|
|
||||||
elide: Text.ElideRight
|
|
||||||
height: 24
|
|
||||||
text: luatr(modelData)
|
text: luatr(modelData)
|
||||||
font.pixelSize: 16
|
font.pixelSize: 16
|
||||||
}
|
}
|
||||||
|
|
19
Fk/Logic.js
19
Fk/Logic.js
|
@ -81,25 +81,6 @@ callbacks["ErrorMsg"] = (jsonData) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
callbacks["ErrorDlg"] = (jsonData) => {
|
|
||||||
let log;
|
|
||||||
try {
|
|
||||||
const a = JSON.parse(jsonData);
|
|
||||||
log = qsTr(a[0]).arg(a[1]);
|
|
||||||
} catch (e) {
|
|
||||||
log = qsTr(jsonData);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("ERROR: " + log);
|
|
||||||
Backend.showDialog("warning", log, jsonData);
|
|
||||||
mainWindow.busy = false;
|
|
||||||
if (sheduled_download !== "") {
|
|
||||||
mainWindow.busy = true;
|
|
||||||
Pacman.loadSummary(JSON.stringify(sheduled_download), true);
|
|
||||||
sheduled_download = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["UpdatePackage"] = (jsonData) => sheduled_download = jsonData;
|
callbacks["UpdatePackage"] = (jsonData) => sheduled_download = jsonData;
|
||||||
|
|
||||||
callbacks["UpdateBusyText"] = (jsonData) => {
|
callbacks["UpdateBusyText"] = (jsonData) => {
|
||||||
|
|
|
@ -289,29 +289,18 @@ Item {
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
text: {
|
text: {
|
||||||
if (audioType === "male") {
|
if (gender === "male") {
|
||||||
return luatr("Male Audio");
|
return luatr("Male Audio");
|
||||||
} else if (audioType === "female") {
|
} else {
|
||||||
return luatr("Female Audio");
|
return luatr("Female Audio");
|
||||||
} else if (audioType === "equip_effect") {
|
|
||||||
return luatr("Equip Effect Audio");
|
|
||||||
} {
|
|
||||||
return luatr("Equip Use Audio");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
font.pixelSize: 14
|
font.pixelSize: 14
|
||||||
}
|
}
|
||||||
onClicked: {
|
onClicked: {
|
||||||
const data = lcall("GetCardData", cardDetail.cid);
|
const data = lcall("GetCardData", cardDetail.cid);
|
||||||
if (audioType === "male" || audioType === "female") {
|
Backend.playSound("./packages/" + extension + "/audio/card/"
|
||||||
Backend.playSound("./packages/" + extension + "/audio/card/"
|
+ gender + "/" + data.name);
|
||||||
+ audioType + "/" + data.name);
|
|
||||||
} else if (audioType === "equip_effect") {
|
|
||||||
Backend.playSound("./packages/" + extension + "/audio/card/"
|
|
||||||
+ "/" + data.name);
|
|
||||||
} else {
|
|
||||||
Backend.playSound("./audio/card/common/" + extension);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -328,41 +317,27 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadAudio(cardName, type, extension, orig_extension) {
|
|
||||||
const prefix = AppPath + "/packages/";
|
|
||||||
const suffix = cardName + ".mp3";
|
|
||||||
let midfix = type + "/";
|
|
||||||
if (type === "equip_effect") {
|
|
||||||
midfix = "";
|
|
||||||
}
|
|
||||||
let fname = prefix + extension + "/audio/card/" + midfix + suffix;
|
|
||||||
if (Backend.exists(fname)) {
|
|
||||||
audioRow.append( { audioType: type, extension: extension } );
|
|
||||||
} else {
|
|
||||||
fname = prefix + orig_extension + "/audio/card/" + midfix + suffix;
|
|
||||||
if (Backend.exists(fname)) {
|
|
||||||
audioRow.append( { audioType: type, extension: orig_extension} );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addCardAudio(card) {
|
function addCardAudio(card) {
|
||||||
const extension = card.extension;
|
const extension = card.extension;
|
||||||
const orig_extension = lcall("GetCardExtensionByName", card.name);
|
const orig_extension = lcall("GetCardExtensionByName", card.name);
|
||||||
loadAudio(card.name, "male", extension, orig_extension);
|
const prefix = AppPath + "/packages/";
|
||||||
loadAudio(card.name, "female", extension, orig_extension);
|
const suffix = card.name + ".mp3";
|
||||||
if (audioRow.count === 0 && card.type === 3) {
|
let fname = prefix + extension + "/audio/card/male/" + suffix;
|
||||||
loadAudio(card.name, "equip_effect", extension, orig_extension);
|
if (Backend.exists(fname)) {
|
||||||
if (audioRow.count === 0) {
|
audioRow.append( { gender: "male", extension: extension } );
|
||||||
let subType = "";
|
} else {
|
||||||
if (card.subtype === "defensive_horse" || card.subtype === "offensive_horse") {
|
fname = prefix + orig_extension + "/audio/card/male/" + suffix;
|
||||||
subType = "horse";
|
if (Backend.exists(fname)) {
|
||||||
} else if (card.subtype === "weapon") {
|
audioRow.append( {gender: "male", extension: orig_extension} );
|
||||||
subType = "weapon";
|
}
|
||||||
} else {
|
}
|
||||||
subType = "armor";
|
fname = prefix + extension + "/audio/card/female/" + suffix;
|
||||||
}
|
if (Backend.exists(fname)) {
|
||||||
audioRow.append( { audioType: "equip_use", extension: subType } );
|
audioRow.append( { gender: "female", extension: extension } );
|
||||||
|
}else {
|
||||||
|
fname = prefix + orig_extension + "/audio/card/female/" + suffix;
|
||||||
|
if (Backend.exists(fname)) {
|
||||||
|
audioRow.append( { gender: "female", extension: orig_extension } );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -503,8 +503,6 @@ Item {
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: generalInfo
|
id: generalInfo
|
||||||
x: 5
|
|
||||||
y: 10
|
|
||||||
width: 150
|
width: 150
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
@ -518,7 +516,6 @@ Item {
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
textFormat: TextEdit.RichText
|
textFormat: TextEdit.RichText
|
||||||
font.pixelSize: 16
|
font.pixelSize: 16
|
||||||
function trans(str) {
|
function trans(str) {
|
||||||
|
|
|
@ -183,6 +183,17 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Temp
|
||||||
|
Button {
|
||||||
|
text: qsTr("Making Mod")
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
visible: Debugging
|
||||||
|
onClicked: {
|
||||||
|
mainStack.push(modMaker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function downloadComplete() {
|
function downloadComplete() {
|
||||||
toast.show(qsTr("updated packages for md5"));
|
toast.show(qsTr("updated packages for md5"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,234 +11,28 @@ import "Logic.js" as Logic
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
property alias roomModel: roomModel
|
property alias roomModel: roomModel
|
||||||
property var roomInfoCache: ({})
|
|
||||||
|
|
||||||
property string password
|
property string password
|
||||||
|
|
||||||
Component {
|
|
||||||
id: roomDelegate
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
radius: 8
|
|
||||||
height: 124 - 8
|
|
||||||
width: 124 - 8
|
|
||||||
color: outdated ? "#E2E2E2" : "lightgreen"
|
|
||||||
|
|
||||||
Text {
|
|
||||||
id: roomNameText
|
|
||||||
horizontalAlignment: Text.AlignLeft
|
|
||||||
width: parent.width - 16
|
|
||||||
height: contentHeight
|
|
||||||
maximumLineCount: 2
|
|
||||||
wrapMode: Text.WrapAnywhere
|
|
||||||
textFormat: Text.PlainText
|
|
||||||
text: roomName
|
|
||||||
// color: outdated ? "gray" : "black"
|
|
||||||
font.pixelSize: 16
|
|
||||||
// elide: Label.ElideRight
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.margins: 8
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
id: roomIdText
|
|
||||||
text: luatr(gameMode) + ' #' + roomId
|
|
||||||
anchors.top: roomNameText.bottom
|
|
||||||
anchors.left: roomNameText.left
|
|
||||||
}
|
|
||||||
|
|
||||||
Image {
|
|
||||||
source: AppPath + "/image/button/skill/locked.png"
|
|
||||||
visible: hasPassword
|
|
||||||
scale: 0.8
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.margins: -4
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
color: (playerNum == capacity) ? "red" : "black"
|
|
||||||
text: playerNum + "/" + capacity
|
|
||||||
font.pixelSize: 18
|
|
||||||
font.bold: true
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.bottomMargin: 8
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: 8
|
|
||||||
}
|
|
||||||
|
|
||||||
TapHandler {
|
|
||||||
gesturePolicy: TapHandler.WithinBounds
|
|
||||||
enabled: !opTimer.running && !outdated
|
|
||||||
|
|
||||||
onTapped: {
|
|
||||||
lobby_dialog.sourceComponent = roomDetailDialog;
|
|
||||||
lobby_dialog.item.roomData = {
|
|
||||||
roomId, roomName, gameMode, playerNum, capacity,
|
|
||||||
hasPassword, outdated,
|
|
||||||
};
|
|
||||||
lobby_dialog.item.roomConfig = config.roomConfigCache?.[config.serverAddr]?.[roomId]
|
|
||||||
lobby_drawer.open();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: roomDetailDialog
|
|
||||||
ColumnLayout {
|
|
||||||
property var roomData: ({
|
|
||||||
roomName: "",
|
|
||||||
hasPassword: true,
|
|
||||||
})
|
|
||||||
property var roomConfig: undefined
|
|
||||||
signal finished()
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 16
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: roomData.roomName
|
|
||||||
font.pixelSize: 18
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
font.pixelSize: 18
|
|
||||||
text: {
|
|
||||||
let ret = luatr(roomData.gameMode);
|
|
||||||
ret += (' #' + roomData.roomId);
|
|
||||||
ret += (' ' + roomData.playerNum + '/' + roomData.capacity);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item { Layout.fillHeight: true }
|
|
||||||
|
|
||||||
// Dummy
|
|
||||||
Text {
|
|
||||||
text: "在未来的版本中这一块区域将增加更多实用的功能,<br>"+
|
|
||||||
"例如直接查看房间的各种配置信息<br>"+
|
|
||||||
"以及更多与禁将有关的实用功能!"+
|
|
||||||
"<font color='gray'>注:绿色按钮为试做型UI 后面优化</font>"
|
|
||||||
font.pixelSize: 18
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Text {
|
|
||||||
visible: roomData.hasPassword
|
|
||||||
text: luatr("Please input room's password")
|
|
||||||
}
|
|
||||||
|
|
||||||
TextField {
|
|
||||||
id: passwordEdit
|
|
||||||
visible: roomData.hasPassword
|
|
||||||
Layout.fillWidth: true
|
|
||||||
onTextChanged: root.password = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
visible: !roomData.hasPassword
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
// text: "OK"
|
|
||||||
text: (roomData.playerNum < roomData.capacity) ? luatr("Enter") : luatr("Observe")
|
|
||||||
onClicked: {
|
|
||||||
enterRoom(roomData.roomId, roomData.playerNum, roomData.capacity,
|
|
||||||
roomData.hasPassword ? root.password : "");
|
|
||||||
lobby_dialog.item.finished();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
passwordEdit.text = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ListModel {
|
|
||||||
id: roomModel
|
|
||||||
}
|
|
||||||
|
|
||||||
PersonalSettings {}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: opTimer
|
|
||||||
interval: 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: roomListLayout
|
|
||||||
height: root.height - 72
|
|
||||||
y: 16
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: root.width * 0.03 + root.width * 0.94 * 0.8 % 128 / 2
|
|
||||||
width: {
|
|
||||||
let ret = root.width * 0.94 * 0.8;
|
|
||||||
ret -= ret % 128;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Item { Layout.fillWidth: true }
|
|
||||||
Button {
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
text: luatr("Refresh Room List").arg(roomModel.count)
|
|
||||||
enabled: !opTimer.running
|
|
||||||
onClicked: {
|
|
||||||
opTimer.start();
|
|
||||||
ClientInstance.notifyServer("RefreshRoomList", "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
text: luatr("Create Room")
|
|
||||||
onClicked: {
|
|
||||||
lobby_dialog.sourceComponent =
|
|
||||||
Qt.createComponent("../LobbyElement/CreateRoom.qml");
|
|
||||||
lobby_drawer.open();
|
|
||||||
config.observing = false;
|
|
||||||
config.replaying = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GridView {
|
|
||||||
id: roomList
|
|
||||||
cellWidth: 128
|
|
||||||
cellHeight: 128
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
ScrollBar.vertical: ScrollBar {}
|
|
||||||
delegate: roomDelegate
|
|
||||||
clip: true
|
|
||||||
model: roomModel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: serverInfoLayout
|
width: parent.width / 2 - roomListLayout.width / 2 - 50
|
||||||
height: root.height - 112
|
height: parent.height * 0.7
|
||||||
y: 56
|
anchors.top: exitButton.bottom
|
||||||
width: root.width * 0.94 * 0.2
|
anchors.bottom: createRoomButton.top
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: root.width * 0.03
|
anchors.rightMargin: 20
|
||||||
// anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
color: "#88EEEEEE"
|
color: "#88EEEEEE"
|
||||||
property bool chatShown: true
|
radius: 6
|
||||||
|
|
||||||
Flickable {
|
Flickable {
|
||||||
|
id: flickableContainer
|
||||||
ScrollBar.vertical: ScrollBar {}
|
ScrollBar.vertical: ScrollBar {}
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.topMargin: 10
|
anchors.topMargin: 10
|
||||||
flickableDirection: Flickable.VerticalFlick
|
flickableDirection: Flickable.VerticalFlick
|
||||||
width: parent.width - 10
|
width: parent.width - 10
|
||||||
height: parent.height - 10 - (parent.chatShown ? 200 : 0)
|
height: parent.height - 10
|
||||||
contentHeight: bulletin_info.height
|
contentHeight: bulletin_info.height
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
|
@ -247,57 +41,162 @@ Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
wrapMode: TextEdit.WordWrap
|
wrapMode: TextEdit.WordWrap
|
||||||
textFormat: Text.MarkdownText
|
textFormat: Text.MarkdownText
|
||||||
text: config.serverMotd + "\n\n___\n\n" + luatr('Bulletin Info')
|
text: config.serverMotd + "\n___\n" + luatr('Bulletin Info')
|
||||||
onLinkActivated: Qt.openUrlExternally(link);
|
onLinkActivated: Qt.openUrlExternally(link);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MetroButton {
|
Component {
|
||||||
text: "🗨️" + (parent.chatShown ? "➖" : "➕")
|
id: roomDelegate
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.bottom: lobbyChat.top
|
Item {
|
||||||
onClicked: {
|
height: 48
|
||||||
parent.chatShown = !parent.chatShown
|
width: roomList.width
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 16
|
||||||
|
Text {
|
||||||
|
text: roomId
|
||||||
|
color: "grey"
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
horizontalAlignment: Text.AlignLeft
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: {
|
||||||
|
let ret = roomName;
|
||||||
|
if (outdated) {
|
||||||
|
ret = '<font color="grey"><del>' + ret + '</del></font>';
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
font.pixelSize: 20
|
||||||
|
elide: Label.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.preferredWidth: 16
|
||||||
|
Image {
|
||||||
|
source: AppPath + "/image/button/skill/locked.png"
|
||||||
|
visible: hasPassword
|
||||||
|
anchors.centerIn: parent
|
||||||
|
scale: 0.8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: luatr(gameMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
color: (playerNum == capacity) ? "red" : "black"
|
||||||
|
text: playerNum + "/" + capacity
|
||||||
|
font.pixelSize: 20
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: (playerNum < capacity) ? luatr("Enter") :
|
||||||
|
luatr("Observe")
|
||||||
|
|
||||||
|
enabled: !opTimer.running && !outdated
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
opTimer.start();
|
||||||
|
if (hasPassword) {
|
||||||
|
lobby_dialog.sourceComponent = enterPassword;
|
||||||
|
lobby_dialog.item.roomId = roomId;
|
||||||
|
lobby_dialog.item.playerNum = playerNum;
|
||||||
|
lobby_dialog.item.capacity = capacity;
|
||||||
|
lobby_drawer.open();
|
||||||
|
} else {
|
||||||
|
enterRoom(roomId, playerNum, capacity, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ChatBox {
|
ListModel {
|
||||||
id: lobbyChat
|
id: roomModel
|
||||||
width: parent.width
|
}
|
||||||
height: parent.chatShown ? 200 : 0
|
|
||||||
Behavior on height { NumberAnimation { duration: 200 } }
|
PersonalSettings {
|
||||||
anchors.bottom: parent.bottom
|
}
|
||||||
isLobby: true
|
|
||||||
color: "#88EEEEEE"
|
Timer {
|
||||||
clip: true
|
id: opTimer
|
||||||
|
interval: 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: roomListLayout
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: 10
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
width: root.width * 0.48
|
||||||
|
height: root.height - 80
|
||||||
|
Button {
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
text: luatr("Refresh Room List")
|
||||||
|
enabled: !opTimer.running
|
||||||
|
onClicked: {
|
||||||
|
opTimer.start();
|
||||||
|
ClientInstance.notifyServer("RefreshRoomList", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.centerIn: parent
|
||||||
|
color: "#88EEEEEE"
|
||||||
|
radius: 16
|
||||||
|
Text {
|
||||||
|
width: parent.width
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
text: luatr("Room List").arg(roomModel.count)
|
||||||
|
}
|
||||||
|
ListView {
|
||||||
|
id: roomList
|
||||||
|
height: parent.height * 0.9
|
||||||
|
width: parent.width * 0.95
|
||||||
|
contentHeight: roomDelegate.height * count
|
||||||
|
ScrollBar.vertical: ScrollBar {}
|
||||||
|
anchors.centerIn: parent
|
||||||
|
delegate: roomDelegate
|
||||||
|
clip: true
|
||||||
|
model: roomModel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: createRoomButton
|
||||||
|
anchors.bottom: buttonRow.top
|
||||||
|
anchors.right: parent.right
|
||||||
|
width: 120
|
||||||
|
display: AbstractButton.TextUnderIcon
|
||||||
|
icon.name: "media-playback-start"
|
||||||
|
text: luatr("Create Room")
|
||||||
|
onClicked: {
|
||||||
|
lobby_dialog.sourceComponent =
|
||||||
|
Qt.createComponent("../LobbyElement/CreateRoom.qml");
|
||||||
|
lobby_drawer.open();
|
||||||
|
config.observing = false;
|
||||||
|
config.replaying = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: buttonRow
|
id: buttonRow
|
||||||
anchors.left: parent.left
|
anchors.right: parent.right
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
width: parent.width
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.preferredWidth: childrenRect.width + 48
|
|
||||||
|
|
||||||
gradient: Gradient {
|
|
||||||
orientation: Gradient.Horizontal
|
|
||||||
GradientStop { position: 0.8; color: "white" }
|
|
||||||
GradientStop { position: 1.0; color: "transparent" }
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
x: 16; y: 4
|
|
||||||
font.pixelSize: 16
|
|
||||||
text: luatr("$OnlineInfo")
|
|
||||||
.arg(lobbyPlayerNum).arg(serverPlayerNum) + "\n"
|
|
||||||
+ "Powered by FreeKill " + FkVersion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item { Layout.fillWidth: true }
|
|
||||||
Button {
|
Button {
|
||||||
text: luatr("Generals Overview")
|
text: luatr("Generals Overview")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
@ -377,6 +276,39 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: enterPassword
|
||||||
|
ColumnLayout {
|
||||||
|
property int roomId
|
||||||
|
property int playerNum
|
||||||
|
property int capacity
|
||||||
|
signal finished()
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 16
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: luatr("Please input room's password")
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: passwordEdit
|
||||||
|
onTextChanged: root.password = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "OK"
|
||||||
|
onClicked: {
|
||||||
|
enterRoom(roomId, playerNum, capacity, root.password);
|
||||||
|
parent.finished();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
passwordEdit.text = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function enterRoom(roomId, playerNum, capacity, pw) {
|
function enterRoom(roomId, playerNum, capacity, pw) {
|
||||||
config.replaying = false;
|
config.replaying = false;
|
||||||
if (playerNum < capacity) {
|
if (playerNum < capacity) {
|
||||||
|
@ -401,15 +333,40 @@ Item {
|
||||||
property int lobbyPlayerNum: 0
|
property int lobbyPlayerNum: 0
|
||||||
property int serverPlayerNum: 0
|
property int serverPlayerNum: 0
|
||||||
|
|
||||||
/*
|
|
||||||
function updateOnlineInfo() {
|
function updateOnlineInfo() {
|
||||||
}
|
}
|
||||||
|
|
||||||
onLobbyPlayerNumChanged: updateOnlineInfo();
|
onLobbyPlayerNumChanged: updateOnlineInfo();
|
||||||
onServerPlayerNumChanged: updateOnlineInfo();
|
onServerPlayerNumChanged: updateOnlineInfo();
|
||||||
|
|
||||||
/*
|
Rectangle {
|
||||||
*/
|
id: info
|
||||||
|
color: "#88EEEEEE"
|
||||||
|
width: root.width * 0.23 // childrenRect.width + 8
|
||||||
|
height: childrenRect.height + 4
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
radius: 4
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
x: 4; y: 2
|
||||||
|
font.pixelSize: 16
|
||||||
|
text: luatr("$OnlineInfo")
|
||||||
|
.arg(lobbyPlayerNum).arg(serverPlayerNum) + "\n"
|
||||||
|
+ "Powered by FreeKill " + FkVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatBox {
|
||||||
|
id: lobbyChat
|
||||||
|
anchors.bottom: info.top
|
||||||
|
width: info.width
|
||||||
|
height: root.height * 0.6
|
||||||
|
isLobby: true
|
||||||
|
color: "#88EEEEEE"
|
||||||
|
radius: 4
|
||||||
|
}
|
||||||
|
|
||||||
Danmaku {
|
Danmaku {
|
||||||
id: danmaku
|
id: danmaku
|
||||||
|
|
|
@ -3,13 +3,13 @@
|
||||||
callbacks["UpdateAvatar"] = (jsonData) => {
|
callbacks["UpdateAvatar"] = (jsonData) => {
|
||||||
mainWindow.busy = false;
|
mainWindow.busy = false;
|
||||||
Self.avatar = jsonData;
|
Self.avatar = jsonData;
|
||||||
toast.show(luatr("Update avatar done."));
|
toast.show("Update avatar done.");
|
||||||
}
|
}
|
||||||
|
|
||||||
callbacks["UpdatePassword"] = (jsonData) => {
|
callbacks["UpdatePassword"] = (jsonData) => {
|
||||||
mainWindow.busy = false;
|
mainWindow.busy = false;
|
||||||
if (jsonData === "1")
|
if (jsonData === "1")
|
||||||
toast.show(luatr("Update password done."));
|
toast.show("Update password done.");
|
||||||
else
|
else
|
||||||
toast.show(luatr("Old password wrong!"), 5000);
|
toast.show("Old password wrong!", 5000);
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ Item {
|
||||||
id: menu
|
id: menu
|
||||||
y: bar.height
|
y: bar.height
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: luatr("Replay from File")
|
text: qsTr("Replay from file")
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
fdialog.open();
|
fdialog.open();
|
||||||
}
|
}
|
||||||
|
|
|
@ -810,7 +810,7 @@ Item {
|
||||||
skillInteraction.item.choices = data.choices;
|
skillInteraction.item.choices = data.choices;
|
||||||
skillInteraction.item.detailed = data.detailed;
|
skillInteraction.item.detailed = data.detailed;
|
||||||
skillInteraction.item.all_choices = data.all_choices;
|
skillInteraction.item.all_choices = data.all_choices;
|
||||||
skillInteraction.item.clicked();
|
// skillInteraction.item.clicked();
|
||||||
break;
|
break;
|
||||||
case "spin":
|
case "spin":
|
||||||
skillInteraction.sourceComponent =
|
skillInteraction.sourceComponent =
|
||||||
|
@ -818,14 +818,12 @@ Item {
|
||||||
skillInteraction.item.skill = skill_name;
|
skillInteraction.item.skill = skill_name;
|
||||||
skillInteraction.item.from = data.from;
|
skillInteraction.item.from = data.from;
|
||||||
skillInteraction.item.to = data.to;
|
skillInteraction.item.to = data.to;
|
||||||
skillInteraction.item.clicked();
|
|
||||||
break;
|
break;
|
||||||
case "custom":
|
case "custom":
|
||||||
skillInteraction.sourceComponent =
|
skillInteraction.sourceComponent =
|
||||||
Qt.createComponent(AppPath + "/" + data.qml_path + ".qml");
|
Qt.createComponent(AppPath + "/" + data.qml_path + ".qml");
|
||||||
skillInteraction.item.skill = skill_name;
|
skillInteraction.item.skill = skill_name;
|
||||||
skillInteraction.item.extra_data = data;
|
skillInteraction.item.extra_data = data;
|
||||||
skillInteraction.item.clicked();
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
skillInteraction.sourceComponent = undefined;
|
skillInteraction.sourceComponent = undefined;
|
||||||
|
@ -839,8 +837,6 @@ Item {
|
||||||
cancelButton.enabled = true;
|
cancelButton.enabled = true;
|
||||||
} else {
|
} else {
|
||||||
skillInteraction.sourceComponent = undefined;
|
skillInteraction.sourceComponent = undefined;
|
||||||
if (roomScene.popupBox.item)
|
|
||||||
roomScene.popupBox.item.close();
|
|
||||||
Logic.doCancelButton();
|
Logic.doCancelButton();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1064,17 +1060,8 @@ Item {
|
||||||
|
|
||||||
Shortcut {
|
Shortcut {
|
||||||
sequence: "Space"
|
sequence: "Space"
|
||||||
enabled: cancelButton.enabled || endPhaseButton.visible;
|
enabled: cancelButton.enabled
|
||||||
onActivated: if (cancelButton.enabled) {
|
onActivated: Logic.doCancelButton();
|
||||||
Logic.doCancelButton();
|
|
||||||
} else {
|
|
||||||
Logic.replyToServer("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "Escape"
|
|
||||||
onActivated: menuContainer.open();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentCardUseMethod() {
|
function getCurrentCardUseMethod() {
|
||||||
|
|
|
@ -314,10 +314,6 @@ function resortHandcards() {
|
||||||
["treasure"]: Card.SubtypeTreasure,
|
["treasure"]: Card.SubtypeTreasure,
|
||||||
}
|
}
|
||||||
|
|
||||||
const hand = dashboard.handcardArea.cards.map(c => {
|
|
||||||
return c.cid;
|
|
||||||
})
|
|
||||||
|
|
||||||
dashboard.handcardArea.cards.sort((prev, next) => {
|
dashboard.handcardArea.cards.sort((prev, next) => {
|
||||||
if (prev.footnote === next.footnote) {
|
if (prev.footnote === next.footnote) {
|
||||||
if (prev.type === next.type) {
|
if (prev.type === next.type) {
|
||||||
|
@ -345,34 +341,6 @@ function resortHandcards() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let i = 0;
|
|
||||||
let resort = true;
|
|
||||||
dashboard.handcardArea.cards.forEach(c => {
|
|
||||||
if (hand[i] !== c.cid) {
|
|
||||||
resort = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
})
|
|
||||||
|
|
||||||
if (resort) {
|
|
||||||
dashboard.handcardArea.cards.sort((prev, next) => {
|
|
||||||
if (prev.footnote === next.footnote) {
|
|
||||||
if (prev.number === next.number) { // 按点数排
|
|
||||||
if (prev.suit === next.suit) {
|
|
||||||
return prev.cid - next.cid;
|
|
||||||
} else {
|
|
||||||
return prev.suit - next.suit;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return prev.number - next.number;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return prev.footnote > next.footnote ? 1 : -1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
dashboard.handcardArea.updateCardPosition(true);
|
dashboard.handcardArea.updateCardPosition(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -520,33 +488,23 @@ function doIndicate(from, tos) {
|
||||||
line.running = true;
|
line.running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPlayerStr(playerid) {
|
|
||||||
const photo = getPhoto(playerid);
|
|
||||||
if (photo.general === "anjiang" && (photo.deputyGeneral === "anjiang" || !p.deputyGeneral)) {
|
|
||||||
return luatr("seat#" + photo.seatNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
let ret = photo.general;
|
|
||||||
ret = luatr(ret);
|
|
||||||
if (photo.deputyGeneral && photo.deputyGeneral !== "") {
|
|
||||||
ret = ret + "/" + luatr(photo.deputyGeneral);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
function processPrompt(prompt) {
|
function processPrompt(prompt) {
|
||||||
const data = prompt.split(":");
|
const data = prompt.split(":");
|
||||||
let raw = luatr(data[0]);
|
let raw = luatr(data[0]);
|
||||||
const src = parseInt(data[1]);
|
const src = parseInt(data[1]);
|
||||||
const dest = parseInt(data[2]);
|
const dest = parseInt(data[2]);
|
||||||
if (raw.match("%src"))
|
if (raw.match("%src"))
|
||||||
raw = raw.replace(/%src/g, getPlayerStr(src));
|
raw = raw.replace(/%src/g, luatr(getPhoto(src).general));
|
||||||
if (raw.match("%dest"))
|
if (raw.match("%dest"))
|
||||||
raw = raw.replace(/%dest/g, getPlayerStr(dest));
|
raw = raw.replace(/%dest/g, luatr(getPhoto(dest).general));
|
||||||
if (raw.match("%arg2"))
|
|
||||||
raw = raw.replace(/%arg2/g, luatr(data[4]));
|
if (data.length > 3) {
|
||||||
if (raw.match("%arg"))
|
for (let i = data.length - 1; i > 3; i--) {
|
||||||
raw = raw.replace(/%arg/g, luatr(data[3]));
|
raw = raw.replace(new RegExp("%arg" + (i - 2), "g"), luatr(data[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
raw = raw.replace(new RegExp("%arg", "g"), luatr(data[3]));
|
||||||
|
}
|
||||||
return raw;
|
return raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -718,49 +676,6 @@ function updateSelectedTargets(playerid, selected) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (candidate) {
|
if (candidate) {
|
||||||
if (!selected) {
|
|
||||||
const remain_targets = [];
|
|
||||||
selected_targets.forEach(id => {
|
|
||||||
const photo = getPhoto(id);
|
|
||||||
const ret = lcall("CanUseCardToTarget", card, id, remain_targets,
|
|
||||||
JSON.stringify(roomScene.extra_data));
|
|
||||||
let bool = ret;
|
|
||||||
if (roomScene.extra_data instanceof Object) {
|
|
||||||
const must = roomScene.extra_data.must_targets;
|
|
||||||
const included = roomScene.extra_data.include_targets;
|
|
||||||
const exclusive = roomScene.extra_data.exclusive_targets;
|
|
||||||
if (exclusive instanceof Array) {
|
|
||||||
if (exclusive.indexOf(id) === -1) bool = false;
|
|
||||||
}
|
|
||||||
if (must instanceof Array) {
|
|
||||||
if (must.filter((val) => {
|
|
||||||
return remain_targets.indexOf(val) === -1;
|
|
||||||
}).length !== 0 && must.indexOf(id) === -1) bool = false;
|
|
||||||
}
|
|
||||||
if (included instanceof Array) {
|
|
||||||
if (included.filter((val) => {
|
|
||||||
return remain_targets.indexOf(val) !== -1;
|
|
||||||
}).length === 0 && included.indexOf(id) === -1) bool = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (bool) {
|
|
||||||
remain_targets.push(id);
|
|
||||||
} else {
|
|
||||||
photo.selected = false;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
selected_targets = remain_targets;
|
|
||||||
}
|
|
||||||
|
|
||||||
roomScene.resetPrompt(); // update prompt due to selected_targets
|
|
||||||
const prompt = lcall("ActiveSkillPrompt",
|
|
||||||
dashboard.pending_skill !== "" ? dashboard.pending_skill: lcall("GetCardSkill", card),
|
|
||||||
dashboard.pending_skill !== "" ? dashboard.pendings : [card],
|
|
||||||
selected_targets);
|
|
||||||
if (prompt !== "") {
|
|
||||||
roomScene.setPrompt(Util.processPrompt(prompt));
|
|
||||||
}
|
|
||||||
|
|
||||||
all_photos.forEach(photo => {
|
all_photos.forEach(photo => {
|
||||||
if (photo.selected) return;
|
if (photo.selected) return;
|
||||||
const id = photo.playerid;
|
const id = photo.playerid;
|
||||||
|
@ -813,10 +728,6 @@ function updateSelectedTargets(playerid, selected) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const prompt = lcall("CardPrompt", card, selected_targets);
|
|
||||||
if (prompt !== "") {
|
|
||||||
roomScene.setPrompt(Util.processPrompt(prompt));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
all_photos.forEach(photo => {
|
all_photos.forEach(photo => {
|
||||||
photo.state = "normal";
|
photo.state = "normal";
|
||||||
|
@ -1052,31 +963,8 @@ callbacks["AskForSkillInvoke"] = (data) => {
|
||||||
roomScene.cancelButton.enabled = true;
|
roomScene.cancelButton.enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
callbacks["AskForArrangeCards"] = (data) => {
|
|
||||||
roomScene.state = "replying";
|
|
||||||
roomScene.popupBox.sourceComponent =
|
|
||||||
Qt.createComponent("../RoomElement/ArrangeCardsBox.qml");
|
|
||||||
const box = roomScene.popupBox.item;
|
|
||||||
const cards = data.cards;
|
|
||||||
box.cards = cards.reduce((newArray, elem) => {
|
|
||||||
return newArray.concat(elem.map(cid => lcall("GetCardData", cid)));
|
|
||||||
}, []);
|
|
||||||
box.org_cards = cards;
|
|
||||||
box.prompt = data.prompt;
|
|
||||||
box.size = data.size;
|
|
||||||
box.areaCapacities = data.capacities;
|
|
||||||
box.areaLimits = data.limits;
|
|
||||||
box.free_arrange = data.is_free;
|
|
||||||
box.areaNames = data.names;
|
|
||||||
box.pattern = data.pattern;
|
|
||||||
box.poxi_type = data.poxi_type;
|
|
||||||
box.cancelable = data.cancelable;
|
|
||||||
|
|
||||||
box.initializeCards();
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["AskForGuanxing"] = (data) => {
|
callbacks["AskForGuanxing"] = (data) => {
|
||||||
const cards = data.cards;
|
const cards = [];
|
||||||
const min_top_cards = data.min_top_cards;
|
const min_top_cards = data.min_top_cards;
|
||||||
const max_top_cards = data.max_top_cards;
|
const max_top_cards = data.max_top_cards;
|
||||||
const min_bottom_cards = data.min_bottom_cards;
|
const min_bottom_cards = data.min_bottom_cards;
|
||||||
|
@ -1087,9 +975,9 @@ callbacks["AskForGuanxing"] = (data) => {
|
||||||
roomScene.state = "replying";
|
roomScene.state = "replying";
|
||||||
roomScene.popupBox.sourceComponent =
|
roomScene.popupBox.sourceComponent =
|
||||||
Qt.createComponent("../RoomElement/GuanxingBox.qml");
|
Qt.createComponent("../RoomElement/GuanxingBox.qml");
|
||||||
|
data.cards.forEach(id => cards.push(lcall("GetCardData", id)));
|
||||||
const box = roomScene.popupBox.item;
|
const box = roomScene.popupBox.item;
|
||||||
box.prompt = prompt;
|
box.prompt = prompt;
|
||||||
box.free_arrange = data.is_free;
|
|
||||||
if (max_top_cards === 0) {
|
if (max_top_cards === 0) {
|
||||||
box.areaCapacities = [max_bottom_cards];
|
box.areaCapacities = [max_bottom_cards];
|
||||||
box.areaLimits = [min_bottom_cards];
|
box.areaLimits = [min_bottom_cards];
|
||||||
|
@ -1105,11 +993,8 @@ callbacks["AskForGuanxing"] = (data) => {
|
||||||
box.areaNames = [luatr(top_area_name), luatr(bottom_area_name)];
|
box.areaNames = [luatr(top_area_name), luatr(bottom_area_name)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
box.org_cards = cards;
|
box.cards = cards;
|
||||||
box.cards = cards.reduce((newArray, elem) => {
|
box.arrangeCards();
|
||||||
return newArray.concat(elem.map(cid => lcall("GetCardData", cid)));
|
|
||||||
}, []);
|
|
||||||
box.initializeCards();
|
|
||||||
box.accepted.connect(() => {
|
box.accepted.connect(() => {
|
||||||
replyToServer(JSON.stringify(box.getResult()));
|
replyToServer(JSON.stringify(box.getResult()));
|
||||||
});
|
});
|
||||||
|
@ -1125,7 +1010,6 @@ callbacks["AskForExchange"] = (data) => {
|
||||||
Qt.createComponent("../RoomElement/GuanxingBox.qml");
|
Qt.createComponent("../RoomElement/GuanxingBox.qml");
|
||||||
let for_i = 0;
|
let for_i = 0;
|
||||||
const box = roomScene.popupBox.item;
|
const box = roomScene.popupBox.item;
|
||||||
box.org_cards = data.piles;
|
|
||||||
data.piles.forEach(ids => {
|
data.piles.forEach(ids => {
|
||||||
if (ids.length > 0) {
|
if (ids.length > 0) {
|
||||||
ids.forEach(id => cards.push(lcall("GetCardData", id)));
|
ids.forEach(id => cards.push(lcall("GetCardData", id)));
|
||||||
|
@ -1135,11 +1019,11 @@ callbacks["AskForExchange"] = (data) => {
|
||||||
for_i ++;
|
for_i ++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
box.cards = cards;
|
|
||||||
box.areaCapacities = capacities
|
box.areaCapacities = capacities
|
||||||
box.areaLimits = limits
|
box.areaLimits = limits
|
||||||
box.areaNames = cards_name
|
box.areaNames = cards_name
|
||||||
box.initializeCards();
|
box.cards = cards;
|
||||||
|
box.arrangeCards();
|
||||||
box.accepted.connect(() => {
|
box.accepted.connect(() => {
|
||||||
replyToServer(JSON.stringify(box.getResult()));
|
replyToServer(JSON.stringify(box.getResult()));
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,423 +0,0 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import Fk
|
|
||||||
import Fk.Pages
|
|
||||||
import Fk.RoomElement
|
|
||||||
|
|
||||||
GraphicsBox {
|
|
||||||
id: root
|
|
||||||
property string prompt
|
|
||||||
property var cards: []
|
|
||||||
property var org_cards: []
|
|
||||||
property var result: []
|
|
||||||
property var areaCapacities: []
|
|
||||||
property var areaLimits: []
|
|
||||||
property var areaNames: []
|
|
||||||
property var dragging_card: ""
|
|
||||||
property var movepos: []
|
|
||||||
property bool free_arrange: true
|
|
||||||
property bool cancelable: false
|
|
||||||
property string poxi_type: ""
|
|
||||||
property string pattern: "."
|
|
||||||
property int size: 0
|
|
||||||
property int padding: 25
|
|
||||||
|
|
||||||
title.text: Backend.translate(prompt !== "" ? Util.processPrompt(prompt) : "Please arrange cards")
|
|
||||||
width: body.width + padding * 2
|
|
||||||
height: title.height + body.height + padding * 2
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: body
|
|
||||||
x: padding
|
|
||||||
y: parent.height - padding - height
|
|
||||||
spacing: 10
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
id: areaRepeater
|
|
||||||
model: areaCapacities
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: 7
|
|
||||||
|
|
||||||
property int areaCapacity: modelData
|
|
||||||
property string areaName: index < areaNames.length ? qsTr(Backend.translate(areaNames[index])) : ""
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
color: "#6B5D42"
|
|
||||||
width: 20
|
|
||||||
height: 100
|
|
||||||
radius: 5
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.fill: parent
|
|
||||||
width: 20
|
|
||||||
height: 100
|
|
||||||
text: areaName
|
|
||||||
color: "white"
|
|
||||||
font.family: fontLibian.name
|
|
||||||
font.pixelSize: 18
|
|
||||||
style: Text.Outline
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
id: cardRepeater
|
|
||||||
model: (size === 0) ? areaCapacity : 1
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
color: "#1D1E19"
|
|
||||||
width: (size === 0) ? 93 : size * 100 - 7
|
|
||||||
height: 130
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
property alias cardRepeater: cardRepeater
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
spacing: 32
|
|
||||||
|
|
||||||
MetroButton {
|
|
||||||
width: 120
|
|
||||||
height: 35
|
|
||||||
id: buttonConfirm
|
|
||||||
text: luatr("OK")
|
|
||||||
onClicked: {
|
|
||||||
close();
|
|
||||||
roomScene.state = "notactive";
|
|
||||||
const reply = JSON.stringify(getResult());
|
|
||||||
ClientInstance.replyToServer("", reply);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MetroButton {
|
|
||||||
width: 120
|
|
||||||
height: 35
|
|
||||||
text: luatr("Cancel")
|
|
||||||
visible: root.cancelable
|
|
||||||
onClicked: {
|
|
||||||
close();
|
|
||||||
roomScene.state = "notactive";
|
|
||||||
const ret = [];
|
|
||||||
let i;
|
|
||||||
for (i = 0; i < result.length; i++) {
|
|
||||||
ret.push([]);
|
|
||||||
}
|
|
||||||
const reply = JSON.stringify(ret);
|
|
||||||
ClientInstance.replyToServer("", reply);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
id: cardItem
|
|
||||||
model: cards
|
|
||||||
|
|
||||||
CardItem {
|
|
||||||
x: index
|
|
||||||
y: -1
|
|
||||||
cid: modelData.cid
|
|
||||||
name: modelData.name
|
|
||||||
suit: modelData.suit
|
|
||||||
number: modelData.number
|
|
||||||
draggable: true
|
|
||||||
onReleased: updateCardReleased(this);
|
|
||||||
onDraggingChanged: {
|
|
||||||
if (!dragging) return;
|
|
||||||
dragging_card = this;
|
|
||||||
let i, card
|
|
||||||
for (i = 0; i < cardItem.count; i++) {
|
|
||||||
card = cardItem.itemAt(i);
|
|
||||||
if (card !== this)
|
|
||||||
card.draggable = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onXChanged : updateCardDragging(this);
|
|
||||||
onYChanged : updateCardDragging(this);
|
|
||||||
onSelectedChanged : updateCardSelected(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateCardDragging(_card) {
|
|
||||||
if (!_card.dragging) return;
|
|
||||||
_card.goBackAnim.stop();
|
|
||||||
_card.opacity = 0.5
|
|
||||||
let i, j
|
|
||||||
let box, pos, pile;
|
|
||||||
movepos = [];
|
|
||||||
for (j = 0; j <= result.length; j++) {
|
|
||||||
if (j >= result.length) {
|
|
||||||
arrangeCards();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pile = areaRepeater.itemAt(j);
|
|
||||||
if (pile.y === 0) {
|
|
||||||
pile.y = j * 140
|
|
||||||
}
|
|
||||||
box = pile.cardRepeater.itemAt(0);
|
|
||||||
pos = mapFromItem(pile, box.x, box.y);
|
|
||||||
if (Math.abs(pos.y - _card.y) < 130 / 2) break;
|
|
||||||
}
|
|
||||||
if (result[j].includes(_card)) {
|
|
||||||
if (j === 0 && !free_arrange) {
|
|
||||||
arrangeCards();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (!_card.selectable) {
|
|
||||||
arrangeCards();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let card;
|
|
||||||
let index = result[j].indexOf(_card);
|
|
||||||
if (index === -1 && result[j].length === areaCapacities[j]) {
|
|
||||||
for (i = result[j].length; i > 0; i--) {
|
|
||||||
card = result[j][i-1];
|
|
||||||
if (card === _card) continue;
|
|
||||||
if (Math.abs(card.x - _card.x) <= card.width / 2 && card.selectable) {
|
|
||||||
movepos = [j, i-1];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (i = 0; i < result[j].length; i++) {
|
|
||||||
card = result[j][i];
|
|
||||||
if (card.dragging) continue;
|
|
||||||
|
|
||||||
if (card.x > _card.x) {
|
|
||||||
movepos = [j, i - ((index !==-1 && index < i) ? 1 : 0)];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (movepos.length === 0)
|
|
||||||
movepos = [j, result[j].length - ((index === -1) ? 0 : 1)];
|
|
||||||
|
|
||||||
if (!free_arrange && j === 0 && org_cards[0].includes(_card.cid)) {
|
|
||||||
let a = 0, b = -1, c = -1;
|
|
||||||
i = 0;
|
|
||||||
while (i < result[j].length && a < org_cards[0].length) {
|
|
||||||
if (result[j][i].cid === org_cards[0][a]) {
|
|
||||||
if (b !==c) {
|
|
||||||
c = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
a++;
|
|
||||||
} else {
|
|
||||||
if (b === -1)
|
|
||||||
b = i;
|
|
||||||
if (org_cards[0].includes(result[j][i].cid)) {
|
|
||||||
a++;
|
|
||||||
} else {
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (b === -1) b = result[j].length;
|
|
||||||
if (c === -1) c = result[j].length;
|
|
||||||
|
|
||||||
if (b === c)
|
|
||||||
movepos = [j, b];
|
|
||||||
else if (movepos[1] < b || (movepos[1] > c && c !==-1))
|
|
||||||
movepos = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
arrangeCards();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateCardReleased(_card) {
|
|
||||||
let i, j;
|
|
||||||
if (movepos.length === 2) {
|
|
||||||
for (j = 0; j < result.length; j++) {
|
|
||||||
i = result[j].indexOf(_card);
|
|
||||||
if (i !==-1) {
|
|
||||||
if (j !==movepos[0] && result[movepos[0]].length === areaCapacities[movepos[0]]) {
|
|
||||||
result[j][i] = result[movepos[0]][movepos[1]];
|
|
||||||
result[movepos[0]][movepos[1]] = _card;
|
|
||||||
if (!free_arrange && result[0].length < areaCapacities[0])
|
|
||||||
result[0].sort((a, b) => org_cards[0].indexOf(a.cid) - org_cards[0].indexOf(b.cid));
|
|
||||||
} else {
|
|
||||||
result[j].splice(i, 1);
|
|
||||||
result[movepos[0]].splice(movepos[1], 0, _card);
|
|
||||||
}
|
|
||||||
movepos = [];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dragging_card = "";
|
|
||||||
arrangeCards();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateCardSelected(_card) {
|
|
||||||
let i = result[0].indexOf(_card);
|
|
||||||
let j;
|
|
||||||
if (i === -1) {
|
|
||||||
if (result[0].length < areaCapacities[0]) {
|
|
||||||
if (free_arrange || !org_cards[0].includes(_card.cid)) {
|
|
||||||
for (j = 1; j < result.length; j++) {
|
|
||||||
i = result[j].indexOf(_card);
|
|
||||||
if (i !==-1) {
|
|
||||||
result[j].splice(i, 1);
|
|
||||||
result[0].push(_card);
|
|
||||||
arrangeCards();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
i = 0;
|
|
||||||
j = 0;
|
|
||||||
while (i < result[0].length && j < org_cards[0].length) {
|
|
||||||
if (org_cards[0][j] === _card.cid) break;
|
|
||||||
if (result[0][i].cid === org_cards[0][j]) {
|
|
||||||
i++;
|
|
||||||
j++;
|
|
||||||
} else {
|
|
||||||
if (org_cards[0].includes(result[0][i].cid))
|
|
||||||
j++;
|
|
||||||
else
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let index;
|
|
||||||
for (j = 1; j < result.length; j++) {
|
|
||||||
index = result[j].indexOf(_card);
|
|
||||||
if (index !== -1) {
|
|
||||||
result[j].splice(index, 1);
|
|
||||||
result[0].splice(i, 0, _card);
|
|
||||||
arrangeCards();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (j = 1; j < result.length; j++) {
|
|
||||||
if (result[j].length < areaCapacities[j]) {
|
|
||||||
result[0].splice(i, 1);
|
|
||||||
result[j].push(_card);
|
|
||||||
arrangeCards();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function arrangeCards() {
|
|
||||||
let i, j, a, b;
|
|
||||||
let card, box, pos, pile;
|
|
||||||
let spacing;
|
|
||||||
let same_row;
|
|
||||||
let c_result = getResult();
|
|
||||||
let is_exchange = (movepos.length === 2 && !result[movepos[0]].includes(dragging_card) && result[movepos[0]].length === areaCapacities[movepos[0]])
|
|
||||||
for (j = 0; j < result.length; j++) {
|
|
||||||
pile = areaRepeater.itemAt(j);
|
|
||||||
box = pile.cardRepeater.itemAt(0);
|
|
||||||
if (pile.y === 0) {
|
|
||||||
pile.y = j * 140
|
|
||||||
}
|
|
||||||
a = result[j].length;
|
|
||||||
if (movepos.length === 2) {
|
|
||||||
if (movepos[0] === j && !result[j].includes(dragging_card) && result[j].length < areaCapacities[j]) {
|
|
||||||
a++;
|
|
||||||
} else if (movepos[0] !== j && result[j].includes(dragging_card)) {
|
|
||||||
a--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
spacing = (size > 0 && a > size) ? ((size - 1) * 100 / (a - 1)) : 100;
|
|
||||||
b = 0;
|
|
||||||
for (i = 0; i < result[j].length; i++) {
|
|
||||||
card = result[j][i];
|
|
||||||
if (card.dragging) {
|
|
||||||
if (movepos.length !== 2 || movepos[0] !== j)
|
|
||||||
b++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (movepos.length === 2 && movepos[0] === j && b === movepos[1] && !is_exchange)
|
|
||||||
b++;
|
|
||||||
pos = mapFromItem(pile, box.x, box.y);
|
|
||||||
card.glow.visible = false;
|
|
||||||
card.chosenInBox = (movepos.length === 2 && result[j].length === areaCapacities[j] && movepos[0] === j && movepos[1] === b);
|
|
||||||
card.origX = (movepos.length === 2 && movepos[0] === j && b > (movepos[1] - (is_exchange ? 0 : 1))) ? (pos.x + (b - 1) * spacing + 100) : (pos.x + b * spacing);
|
|
||||||
card.origY = pos.y;
|
|
||||||
card.opacity = 1;
|
|
||||||
card.z = i + 1;
|
|
||||||
card.initialZ = i + 1;
|
|
||||||
card.maxZ = cardItem.count;
|
|
||||||
|
|
||||||
if (poxi_type !== "")
|
|
||||||
card.selectable = lcall("PoxiFilter", poxi_type, card.cid, [dragging_card.cid], c_result, org_cards);
|
|
||||||
else if (pattern !== ".")
|
|
||||||
card.selectable = lcall("CardFitPattern", card.cid, pattern);
|
|
||||||
else {
|
|
||||||
if (free_arrange || dragging_card === "")
|
|
||||||
card.selectable = true;
|
|
||||||
else if (result[j].length < areaCapacities[j] + (result[j].includes(dragging_card) ? 1 : 0))
|
|
||||||
card.selectable = (j !== 0);
|
|
||||||
else {
|
|
||||||
if (j === 0)
|
|
||||||
card.selectable = !org_cards[0].includes(dragging_card.cid) || i === org_cards[0].indexOf(dragging_card.cid);
|
|
||||||
else {
|
|
||||||
if (result[0].includes(dragging_card))
|
|
||||||
card.selectable = result[0].length < areaCapacities[0] || !org_cards[0].includes(card.cid) || card.cid === org_cards[0][result[0].indexOf(dragging_card)]
|
|
||||||
else
|
|
||||||
card.selectable = org_cards[0].includes(dragging_card.cid) || card.cid === org_cards[0][result[0].indexOf(dragging_card)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
card.draggable = (dragging_card === "") && (free_arrange || j > 0 || card.selectable);
|
|
||||||
card.goBack(true);
|
|
||||||
b++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < areaRepeater.count; i++) {
|
|
||||||
if (result[i].length < areaLimits[i]) {
|
|
||||||
buttonConfirm.enabled = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
buttonConfirm.enabled = poxi_type ? lcall("PoxiFeasible", poxi_type, [], c_result, org_cards) : true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function initializeCards() {
|
|
||||||
result = new Array(areaCapacities.length);
|
|
||||||
let i, j;
|
|
||||||
let k = 0;
|
|
||||||
for (i = 0; i < result.length; i++){
|
|
||||||
result[i] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
let card;
|
|
||||||
|
|
||||||
for (j = 0; j < org_cards.length; j++){
|
|
||||||
for (i = 0; i < org_cards[j].length; i++){
|
|
||||||
result[j].push(cardItem.itemAt(k));
|
|
||||||
k++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
arrangeCards();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getResult() {
|
|
||||||
const ret = [];
|
|
||||||
result.forEach(t => {
|
|
||||||
const t2 = [];
|
|
||||||
t.forEach(v => t2.push(v.cid));
|
|
||||||
ret.push(t2);
|
|
||||||
});
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -112,7 +112,7 @@ GraphicsBox {
|
||||||
visible: !convertDisabled
|
visible: !convertDisabled
|
||||||
text: luatr("Same General Convert")
|
text: luatr("Same General Convert")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
roomScene.startCheat("SameConvert", { cards: generalList, choices: choices });
|
roomScene.startCheat("SameConvert", { cards: generalList });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,7 +263,6 @@ GraphicsBox {
|
||||||
item.selectable = hegemony ? isHegPair(selectedItem[0], item)
|
item.selectable = hegemony ? isHegPair(selectedItem[0], item)
|
||||||
: true;
|
: true;
|
||||||
if (hegemony) {
|
if (hegemony) {
|
||||||
item.inPosition = 0;
|
|
||||||
if (selectedItem[0]) {
|
if (selectedItem[0]) {
|
||||||
if (selectedItem[1]) {
|
if (selectedItem[1]) {
|
||||||
if (selectedItem[0] === item) {
|
if (selectedItem[0] === item) {
|
||||||
|
@ -300,23 +299,6 @@ GraphicsBox {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hegemony) {
|
|
||||||
if (selectedItem[0]) {
|
|
||||||
if (selectedItem[0].mainMaxHp < 0) {
|
|
||||||
selectedItem[0].inPosition = 1;
|
|
||||||
} else if (selectedItem[0].deputyMaxHp < 0) {
|
|
||||||
selectedItem[0].inPosition = -1;
|
|
||||||
}
|
|
||||||
if (selectedItem[1]) {
|
|
||||||
if (selectedItem[1].mainMaxHp < 0) {
|
|
||||||
selectedItem[1].inPosition = -1;
|
|
||||||
} else if (selectedItem[1].deputyMaxHp < 0) {
|
|
||||||
selectedItem[1].inPosition = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < generalList.count; i++) {
|
for (let i = 0; i < generalList.count; i++) {
|
||||||
if (lcall("GetSameGenerals", generalList.get(i).name).length > 0) {
|
if (lcall("GetSameGenerals", generalList.get(i).name).length > 0) {
|
||||||
convertBtn.enabled = true;
|
convertBtn.enabled = true;
|
||||||
|
|
|
@ -322,6 +322,11 @@ RowLayout {
|
||||||
|
|
||||||
const enabled_cards = [];
|
const enabled_cards = [];
|
||||||
const targets = roomScene.selected_targets;
|
const targets = roomScene.selected_targets;
|
||||||
|
const prompt = lcall("ActiveSkillPrompt", pending_skill, pendings,
|
||||||
|
targets);
|
||||||
|
if (prompt !== "") {
|
||||||
|
roomScene.setPrompt(Util.processPrompt(prompt));
|
||||||
|
}
|
||||||
|
|
||||||
handcardAreaItem.cards.forEach((card) => {
|
handcardAreaItem.cards.forEach((card) => {
|
||||||
if (card.selected || lcall("ActiveCardFilter", pending_skill, card.cid,
|
if (card.selected || lcall("ActiveCardFilter", pending_skill, card.cid,
|
||||||
|
@ -368,11 +373,6 @@ RowLayout {
|
||||||
pending_card = -1;
|
pending_card = -1;
|
||||||
cardSelected(pending_card);
|
cardSelected(pending_card);
|
||||||
}
|
}
|
||||||
const prompt = lcall("ActiveSkillPrompt", pending_skill, pendings,
|
|
||||||
targets);
|
|
||||||
if (prompt !== "") {
|
|
||||||
roomScene.setPrompt(Util.processPrompt(prompt));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function startPending(skill_name) {
|
function startPending(skill_name) {
|
||||||
|
|
|
@ -22,9 +22,6 @@ CardItem {
|
||||||
property int hp
|
property int hp
|
||||||
property int maxHp
|
property int maxHp
|
||||||
property int shieldNum
|
property int shieldNum
|
||||||
property int mainMaxHp
|
|
||||||
property int deputyMaxHp
|
|
||||||
property int inPosition: 0
|
|
||||||
property string pkgName: ""
|
property string pkgName: ""
|
||||||
property bool detailed: true
|
property bool detailed: true
|
||||||
property alias hasCompanions: companions.visible
|
property alias hasCompanions: companions.visible
|
||||||
|
@ -122,15 +119,12 @@ CardItem {
|
||||||
width: childrenRect.width
|
width: childrenRect.width
|
||||||
height: childrenRect.height
|
height: childrenRect.height
|
||||||
Image {
|
Image {
|
||||||
opacity: ((mainMaxHp < 0 || deputyMaxHp < 0) && (index * 2 + 1 === hp) && inPosition !== -1)
|
|
||||||
? (inPosition === 0 ? 0.5 : 0) :1
|
|
||||||
height: 12; fillMode: Image.PreserveAspectFit
|
height: 12; fillMode: Image.PreserveAspectFit
|
||||||
source: SkinBank.getGeneralCardDir(kingdom) + kingdom + "-magatama-l"
|
source: SkinBank.getGeneralCardDir(kingdom) + kingdom + "-magatama-l"
|
||||||
}
|
}
|
||||||
Image {
|
Image {
|
||||||
x: 4.4
|
x: 4.4
|
||||||
opacity: (index + 1) * 2 <= hp ? (((mainMaxHp < 0 || deputyMaxHp < 0) && inPosition !== -1 && ((index + 1) * 2 === hp))
|
opacity: (index + 1) * 2 <= hp ? 1 : 0
|
||||||
? (inPosition === 0 ? 0.5 : 0) : 1) : 0
|
|
||||||
height: 12; fillMode: Image.PreserveAspectFit
|
height: 12; fillMode: Image.PreserveAspectFit
|
||||||
source: {
|
source: {
|
||||||
const k = subkingdom ? subkingdom : kingdom;
|
const k = subkingdom ? subkingdom : kingdom;
|
||||||
|
@ -233,8 +227,6 @@ CardItem {
|
||||||
hp = data.hp;
|
hp = data.hp;
|
||||||
maxHp = data.maxHp;
|
maxHp = data.maxHp;
|
||||||
shieldNum = data.shield;
|
shieldNum = data.shield;
|
||||||
mainMaxHp = data.mainMaxHpAdjustedValue;
|
|
||||||
deputyMaxHp = data.deputyMaxHpAdjustedValue;
|
|
||||||
|
|
||||||
const splited = name.split("__");
|
const splited = name.split("__");
|
||||||
if (splited.length > 1) {
|
if (splited.length > 1) {
|
||||||
|
|
|
@ -8,14 +8,10 @@ GraphicsBox {
|
||||||
id: root
|
id: root
|
||||||
property string prompt
|
property string prompt
|
||||||
property var cards: []
|
property var cards: []
|
||||||
property var org_cards: []
|
|
||||||
property var movepos: []
|
|
||||||
property var dragging_card: ""
|
|
||||||
property var result: []
|
property var result: []
|
||||||
property var areaCapacities: []
|
property var areaCapacities: []
|
||||||
property var areaLimits: []
|
property var areaLimits: []
|
||||||
property var areaNames: []
|
property var areaNames: []
|
||||||
property bool free_arrange: true
|
|
||||||
property int padding: 25
|
property int padding: 25
|
||||||
|
|
||||||
title.text: luatr(prompt !== "" ? prompt : "Please arrange cards")
|
title.text: luatr(prompt !== "" ? prompt : "Please arrange cards")
|
||||||
|
@ -107,191 +103,59 @@ GraphicsBox {
|
||||||
number: modelData.number
|
number: modelData.number
|
||||||
mark: modelData.mark
|
mark: modelData.mark
|
||||||
draggable: true
|
draggable: true
|
||||||
onDraggingChanged: {
|
onReleased: arrangeCards();
|
||||||
if (!dragging) return;
|
|
||||||
dragging_card = this;
|
|
||||||
let i, card
|
|
||||||
for (i = 0; i < cardItem.count; i++) {
|
|
||||||
card = cardItem.itemAt(i);
|
|
||||||
if (card !== this)
|
|
||||||
card.draggable = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onReleased: updateCardReleased(this);
|
|
||||||
onXChanged : updateCardDragging(this);
|
|
||||||
onYChanged : updateCardDragging(this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCardDragging(_card) {
|
|
||||||
if (!_card.dragging) return;
|
|
||||||
_card.goBackAnim.stop();
|
|
||||||
_card.opacity = 0.5
|
|
||||||
let i, j
|
|
||||||
let box, pos, pile;
|
|
||||||
movepos = [];
|
|
||||||
for (j = 0; j <= result.length; j++) {
|
|
||||||
if (j >= result.length) {
|
|
||||||
arrangeCards();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pile = areaRepeater.itemAt(j);
|
|
||||||
if (pile.y === 0) {
|
|
||||||
pile.y = j * 150
|
|
||||||
}
|
|
||||||
box = pile.cardRepeater.itemAt(0);
|
|
||||||
pos = mapFromItem(pile, box.x, box.y);
|
|
||||||
if (Math.abs(pos.y - _card.y) < 130 / 2) break;
|
|
||||||
}
|
|
||||||
if (result[j].includes(_card)) {
|
|
||||||
if (j === 0 && !free_arrange) {
|
|
||||||
arrangeCards();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (!_card.selectable) {
|
|
||||||
arrangeCards();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let card;
|
|
||||||
let index = result[j].indexOf(_card);
|
|
||||||
if (index === -1 && result[j].length === areaCapacities[j]) {
|
|
||||||
for (i = result[j].length; i > 0; i--) {
|
|
||||||
card = result[j][i-1];
|
|
||||||
if (card === _card) continue;
|
|
||||||
if (Math.abs(card.x - _card.x) <= card.width / 2 && card.selectable) {
|
|
||||||
movepos = [j, i-1];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (i = 0; i < result[j].length; i++) {
|
|
||||||
card = result[j][i];
|
|
||||||
if (card.dragging) continue;
|
|
||||||
|
|
||||||
if (card.x > _card.x) {
|
|
||||||
movepos = [j, i - ((index !==-1 && index < i) ? 1 : 0)];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (movepos.length === 0)
|
|
||||||
movepos = [j, result[j].length - ((index === -1) ? 0 : 1)];
|
|
||||||
|
|
||||||
if (!free_arrange && j === 0 && org_cards[0].includes(_card.cid)) {
|
|
||||||
let a = 0, b = -1, c = -1;
|
|
||||||
i = 0;
|
|
||||||
while (i < result[j].length && a < org_cards[0].length) {
|
|
||||||
if (result[j][i].cid === org_cards[0][a]) {
|
|
||||||
if (b !==c) {
|
|
||||||
c = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
a++;
|
|
||||||
} else {
|
|
||||||
if (b === -1)
|
|
||||||
b = i;
|
|
||||||
if (org_cards[0].includes(result[j][i].cid)) {
|
|
||||||
a++;
|
|
||||||
} else {
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (b === -1) b = result[j].length;
|
|
||||||
if (c === -1) c = result[j].length;
|
|
||||||
|
|
||||||
if (b === c)
|
|
||||||
movepos = [j, b];
|
|
||||||
else if (movepos[1] < b || (movepos[1] > c && c !==-1))
|
|
||||||
movepos = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
arrangeCards();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateCardReleased(_card) {
|
|
||||||
let i, j;
|
|
||||||
if (movepos.length === 2) {
|
|
||||||
for (j = 0; j < result.length; j++) {
|
|
||||||
i = result[j].indexOf(_card);
|
|
||||||
if (i !==-1) {
|
|
||||||
if (j !==movepos[0] && result[movepos[0]].length === areaCapacities[movepos[0]]) {
|
|
||||||
result[j][i] = result[movepos[0]][movepos[1]];
|
|
||||||
result[movepos[0]][movepos[1]] = _card;
|
|
||||||
if (!free_arrange && result[0].length < areaCapacities[0])
|
|
||||||
result[0].sort((a, b) => org_cards[0].indexOf(a.cid) - org_cards[0].indexOf(b.cid));
|
|
||||||
} else {
|
|
||||||
result[j].splice(i, 1);
|
|
||||||
result[movepos[0]].splice(movepos[1], 0, _card);
|
|
||||||
}
|
|
||||||
movepos = [];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dragging_card = "";
|
|
||||||
arrangeCards();
|
|
||||||
}
|
|
||||||
|
|
||||||
function arrangeCards() {
|
function arrangeCards() {
|
||||||
let i, j, a, b;
|
result = new Array(areaCapacities.length);
|
||||||
let card, box, pos, pile;
|
let i;
|
||||||
let spacing;
|
for (i = 0; i < result.length; i++){
|
||||||
let same_row;
|
result[i] = [];
|
||||||
let c_result = getResult();
|
}
|
||||||
let is_exchange = (movepos.length === 2 && !result[movepos[0]].includes(dragging_card) && result[movepos[0]].length === areaCapacities[movepos[0]])
|
|
||||||
for (j = 0; j < result.length; j++) {
|
|
||||||
pile = areaRepeater.itemAt(j);
|
|
||||||
box = pile.cardRepeater.itemAt(0);
|
|
||||||
if (pile.y === 0) {
|
|
||||||
pile.y = j * 150
|
|
||||||
}
|
|
||||||
a = result[j].length;
|
|
||||||
if (movepos.length === 2) {
|
|
||||||
if (movepos[0] === j && !result[j].includes(dragging_card) && result[j].length < areaCapacities[j]) {
|
|
||||||
a++;
|
|
||||||
} else if (movepos[0] !== j && result[j].includes(dragging_card)) {
|
|
||||||
a--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
spacing = 100;
|
|
||||||
b = 0;
|
|
||||||
for (i = 0; i < result[j].length; i++) {
|
|
||||||
card = result[j][i];
|
|
||||||
if (card.dragging) {
|
|
||||||
if (movepos.length !== 2 || movepos[0] !== j)
|
|
||||||
b++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (movepos.length === 2 && movepos[0] === j && b === movepos[1] && !is_exchange)
|
|
||||||
b++;
|
|
||||||
pos = mapFromItem(pile, box.x, box.y);
|
|
||||||
card.glow.visible = false;
|
|
||||||
card.origX = (movepos.length === 2 && movepos[0] === j && b > (movepos[1] - (is_exchange ? 0 : 1))) ? (pos.x + (b - 1) * spacing + 100) : (pos.x + b * spacing);
|
|
||||||
card.origY = pos.y;
|
|
||||||
card.opacity = 1;
|
|
||||||
card.z = i + 1;
|
|
||||||
card.initialZ = i + 1;
|
|
||||||
card.maxZ = cardItem.count;
|
|
||||||
|
|
||||||
if (free_arrange || dragging_card === "")
|
let card, j, area, cards, stay;
|
||||||
card.selectable = true;
|
for (i = 0; i < cardItem.count; i++) {
|
||||||
else if (result[j].length < areaCapacities[j] + (result[j].includes(dragging_card) ? 1 : 0))
|
card = cardItem.itemAt(i);
|
||||||
card.selectable = (j !== 0);
|
|
||||||
else {
|
stay = true;
|
||||||
if (j === 0)
|
for (j = areaRepeater.count - 1; j >= 0; j--) {
|
||||||
card.selectable = !org_cards[0].includes(dragging_card.cid) || i === org_cards[0].indexOf(dragging_card.cid);
|
area = areaRepeater.itemAt(j);
|
||||||
else {
|
cards = result[j];
|
||||||
if (result[0].includes(dragging_card))
|
if (cards.length < areaCapacities[j] && card.y >= area.y) {
|
||||||
card.selectable = result[0].length < areaCapacities[0] || !org_cards[0].includes(card.cid) || card.cid === org_cards[0][result[0].indexOf(dragging_card)]
|
cards.push(card);
|
||||||
else
|
stay = false;
|
||||||
card.selectable = org_cards[0].includes(dragging_card.cid) || card.cid === org_cards[0][result[0].indexOf(dragging_card)]
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stay) {
|
||||||
|
for (j = 0; j < areaRepeater.count; j++) {
|
||||||
|
if (result[j].length < areaCapacities[j]) {
|
||||||
|
result[j].push(card);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
card.draggable = (dragging_card === "") && (free_arrange || j > 0 || card.selectable);
|
}
|
||||||
|
}
|
||||||
|
for(i = 0; i < result.length; i++)
|
||||||
|
result[i].sort((a, b) => a.x - b.x);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let box, pos, pile;
|
||||||
|
for (j = 0; j < areaRepeater.count; j++) {
|
||||||
|
pile = areaRepeater.itemAt(j);
|
||||||
|
if (pile.y === 0){
|
||||||
|
pile.y = j * 150
|
||||||
|
}
|
||||||
|
for (i = 0; i < result[j].length; i++) {
|
||||||
|
box = pile.cardRepeater.itemAt(i);
|
||||||
|
pos = mapFromItem(pile, box.x, box.y);
|
||||||
|
card = result[j][i];
|
||||||
|
card.origX = pos.x;
|
||||||
|
card.origY = pos.y;
|
||||||
card.goBack(true);
|
card.goBack(true);
|
||||||
b++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,26 +168,6 @@ GraphicsBox {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializeCards() {
|
|
||||||
result = new Array(areaCapacities.length);
|
|
||||||
let i, j;
|
|
||||||
let k = 0;
|
|
||||||
for (i = 0; i < result.length; i++){
|
|
||||||
result[i] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
let card;
|
|
||||||
|
|
||||||
for (j = 0; j < org_cards.length; j++){
|
|
||||||
for (i = 0; i < org_cards[j].length; i++){
|
|
||||||
result[j].push(cardItem.itemAt(k));
|
|
||||||
k++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
arrangeCards();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getResult() {
|
function getResult() {
|
||||||
const ret = [];
|
const ret = [];
|
||||||
result.forEach(t => {
|
result.forEach(t => {
|
||||||
|
|
|
@ -234,26 +234,7 @@ Item {
|
||||||
color: "white"
|
color: "white"
|
||||||
width: 24
|
width: 24
|
||||||
wrapMode: Text.WrapAnywhere
|
wrapMode: Text.WrapAnywhere
|
||||||
text: ""
|
text: luatr(deputyGeneral)
|
||||||
style: Text.Outline
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
id: longDeputyGeneralName
|
|
||||||
anchors.left: generalImage.right
|
|
||||||
anchors.leftMargin: -14
|
|
||||||
y: 23
|
|
||||||
font.family: fontLibian.name
|
|
||||||
font.pixelSize: 22
|
|
||||||
rotation: 90
|
|
||||||
transformOrigin: Item.BottomLeft
|
|
||||||
opacity: 0.9
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
lineHeight: 18
|
|
||||||
lineHeightMode: Text.FixedHeight
|
|
||||||
color: "white"
|
|
||||||
width: 24
|
|
||||||
text: ""
|
|
||||||
style: Text.Outline
|
style: Text.Outline
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -759,18 +740,6 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onDeputyGeneralChanged: {
|
|
||||||
if (!roomScene.isStarted) return;
|
|
||||||
const text = luatr(deputyGeneral);
|
|
||||||
if (text.length > 6) {
|
|
||||||
deputyGeneralName.text = "";
|
|
||||||
longDeputyGeneralName.text = text;
|
|
||||||
} else {
|
|
||||||
deputyGeneralName.text = text;
|
|
||||||
longDeputyGeneralName.text = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function chat(msg) {
|
function chat(msg) {
|
||||||
chat.text = msg;
|
chat.text = msg;
|
||||||
chat.visible = true;
|
chat.visible = true;
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Qt5Compat.GraphicalEffects
|
import Qt5Compat.GraphicalEffects
|
||||||
import QtQuick.Controls
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
@ -87,40 +86,7 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
TapHandler {
|
TapHandler {
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.NoButton
|
enabled: root.type !== "notactive" && root.enabled
|
||||||
onTapped: (p, btn) => {
|
onTapped: parent.pressed = !parent.pressed;
|
||||||
if ((btn === Qt.LeftButton || btn === Qt.NoButton) && root.type !== "notactive" && root.enabled) {
|
|
||||||
parent.pressed = !parent.pressed;
|
|
||||||
} else if (btn === Qt.RightButton) {
|
|
||||||
skillDetail.open();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Popup {
|
|
||||||
id: skillDetail
|
|
||||||
x: Math.round((parent.width - width) / 2)
|
|
||||||
y: Math.round((parent.height - height) / 2)
|
|
||||||
property string text: ""
|
|
||||||
width: Math.min(contentWidth, realMainWin.width * 0.4)
|
|
||||||
height: Math.min(contentHeight + 24, realMainWin.height * 0.9)
|
|
||||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
|
||||||
padding: 12
|
|
||||||
background: Rectangle {
|
|
||||||
color: "#EEEEEEEE"
|
|
||||||
radius: 5
|
|
||||||
border.color: "#A6967A"
|
|
||||||
border.width: 1
|
|
||||||
}
|
|
||||||
contentItem: Text {
|
|
||||||
text: "<b>" + luatr(orig) + "</b>: " + luatr(":" + orig)
|
|
||||||
font.pixelSize: 20
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
textFormat: TextEdit.RichText
|
|
||||||
|
|
||||||
TapHandler {
|
|
||||||
onTapped: skillDetail.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,6 @@ MetroButton {
|
||||||
const box = roomScene.popupBox.item;
|
const box = roomScene.popupBox.item;
|
||||||
box.options = choices;
|
box.options = choices;
|
||||||
box.all_options = all_choices;
|
box.all_options = all_choices;
|
||||||
box.skill_name = skill;
|
|
||||||
box.accepted.connect(() => {
|
box.accepted.connect(() => {
|
||||||
answer = all_choices[box.result];
|
answer = all_choices[box.result];
|
||||||
});
|
});
|
||||||
|
|
|
@ -64,15 +64,9 @@ Rectangle {
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
text: view.currentIndex + 1 == total ? qsTr("OK!") : qsTr("Next")
|
text: qsTr("Next")
|
||||||
enabled: view.currentIndex + 1 <= total
|
enabled: view.currentIndex + 1 < total
|
||||||
onClicked: {
|
onClicked: view.currentIndex++
|
||||||
if (view.currentIndex + 1 == total) {
|
|
||||||
mainStack.pop();
|
|
||||||
} else {
|
|
||||||
view.currentIndex++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,7 @@ Window {
|
||||||
|
|
||||||
Component { id: init; Init {} }
|
Component { id: init; Init {} }
|
||||||
Component { id: packageManage; PackageManage {} }
|
Component { id: packageManage; PackageManage {} }
|
||||||
|
Component { id: modMaker; ModMaker {} }
|
||||||
Component { id: lobby; Lobby {} }
|
Component { id: lobby; Lobby {} }
|
||||||
Component { id: generalsOverview; GeneralsOverview {} }
|
Component { id: generalsOverview; GeneralsOverview {} }
|
||||||
Component { id: cardsOverview; CardsOverview {} }
|
Component { id: cardsOverview; CardsOverview {} }
|
||||||
|
|
30
Fk/util.js
30
Fk/util.js
|
@ -12,33 +12,23 @@ function convertNumber(number) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPlayerStr(playerid) {
|
|
||||||
const photo = getPhoto(playerid);
|
|
||||||
if (photo.general === "anjiang" && (photo.deputyGeneral === "anjiang" || !p.deputyGeneral)) {
|
|
||||||
return luatr("seat#" + photo.seatNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
let ret = photo.general;
|
|
||||||
ret = luatr(ret);
|
|
||||||
if (photo.deputyGeneral && photo.deputyGeneral !== "") {
|
|
||||||
ret = ret + "/" + luatr(photo.deputyGeneral);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
function processPrompt(prompt) {
|
function processPrompt(prompt) {
|
||||||
const data = prompt.split(":");
|
const data = prompt.split(":");
|
||||||
let raw = luatr(data[0]);
|
let raw = luatr(data[0]);
|
||||||
const src = parseInt(data[1]);
|
const src = parseInt(data[1]);
|
||||||
const dest = parseInt(data[2]);
|
const dest = parseInt(data[2]);
|
||||||
if (raw.match("%src"))
|
if (raw.match("%src"))
|
||||||
raw = raw.replace(/%src/g, getPlayerStr(src));
|
raw = raw.replace(/%src/g, luatr(getPhoto(src).general));
|
||||||
if (raw.match("%dest"))
|
if (raw.match("%dest"))
|
||||||
raw = raw.replace(/%dest/g, getPlayerStr(dest));
|
raw = raw.replace(/%dest/g, luatr(getPhoto(dest).general));
|
||||||
if (raw.match("%arg2"))
|
|
||||||
raw = raw.replace(/%arg2/g, luatr(data[4]));
|
if (data.length > 3) {
|
||||||
if (raw.match("%arg"))
|
for (let i = 4; i < data.length; i++) {
|
||||||
raw = raw.replace(/%arg/g, luatr(data[3]));
|
raw = raw.replace(new RegExp("%arg" + (i - 2), "g"), data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
raw = raw.replace(new RegExp("%arg", "g"), data[3]);
|
||||||
|
}
|
||||||
return raw;
|
return raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.notify.FreeKill"
|
package="org.notify.FreeKill"
|
||||||
android:installLocation="preferExternal"
|
android:installLocation="preferExternal"
|
||||||
android:versionCode="416"
|
android:versionCode="415"
|
||||||
android:versionName="0.4.16">
|
android:versionName="0.4.15">
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
|
21
genfkver.sh
21
genfkver.sh
|
@ -1,21 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# 为fk_ver文件追加编译时相关文件列表
|
|
||||||
# 类似其他项目中flist.txt的功能
|
|
||||||
|
|
||||||
cd $(dirname $0)
|
|
||||||
sed -i '2,$d' ./fk_ver
|
|
||||||
|
|
||||||
fn() {
|
|
||||||
for f in $(ls -1 $1); do
|
|
||||||
if [ -d $1/$f ]; then
|
|
||||||
fn $1/$f
|
|
||||||
else
|
|
||||||
echo $1/$f >> ./fk_ver
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lua
|
|
||||||
fn Fk
|
|
||||||
cd -
|
|
|
@ -113,38 +113,6 @@
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
|
|
||||||
<context>
|
|
||||||
<name>QmlBackend</name>
|
|
||||||
<message>
|
|
||||||
<source>FreeKill</source>
|
|
||||||
<translation>新月杀</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>help: others logged in again with this name</source>
|
|
||||||
<translation>提示:请检查密码是否泄漏</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>help: unknown password error</source>
|
|
||||||
<translation>提示:请尝试重新启动程序</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>help: you have been banned!</source>
|
|
||||||
<translation>提示:此为永久封禁,请联系管理员说明</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>help: you have been temporarily banned!</source>
|
|
||||||
<translation>提示:此为暂时封禁,一般在约二十分钟后自动解禁</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>help: user name not in whitelist</source>
|
|
||||||
<translation>提示:请联系服主解决</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>help: username or password error</source>
|
|
||||||
<translation>提示:可能该用户名已被占用,或者密码错误,如果你是初次注册的话考虑用另一个用户名密码进行登入</translation>
|
|
||||||
</message>
|
|
||||||
</context>
|
|
||||||
|
|
||||||
<context>
|
<context>
|
||||||
<name>Init</name>
|
<name>Init</name>
|
||||||
<message>
|
<message>
|
||||||
|
@ -324,15 +292,7 @@
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>others logged in again with this name</source>
|
<source>others logged in again with this name</source>
|
||||||
<translation>其他人用你的用户名和密码登陆到了服务器</translation>
|
<translation>其他人用你的用户名和密码登陆到了服务器,请检查密码是否泄漏</translation>
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>unknown password error</source>
|
|
||||||
<translation>服务端解密密码时出现未知错误</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>user name not in whitelist</source>
|
|
||||||
<translation>你不在该服务器的白名单中!</translation>
|
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>invalid user name</source>
|
<source>invalid user name</source>
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
---@field public discard_pile integer[] @ 弃牌堆
|
---@field public discard_pile integer[] @ 弃牌堆
|
||||||
---@field public observing boolean
|
---@field public observing boolean
|
||||||
---@field public record any
|
---@field public record any
|
||||||
---@field public last_update_ui integer @ 上次刷新状态技UI的时间
|
|
||||||
Client = AbstractRoom:subclass('Client')
|
Client = AbstractRoom:subclass('Client')
|
||||||
|
|
||||||
-- load client classes
|
-- load client classes
|
||||||
|
@ -66,23 +65,6 @@ function Client:initialize()
|
||||||
else
|
else
|
||||||
self:notifyUI(command, data)
|
self:notifyUI(command, data)
|
||||||
end
|
end
|
||||||
|
|
||||||
if self.recording and command == "GameLog" then
|
|
||||||
--and os.getms() - self.last_update_ui > 60000 then
|
|
||||||
-- self.last_update_ui = os.getms()
|
|
||||||
-- TODO: create a function
|
|
||||||
-- 刷所有人手牌上限
|
|
||||||
for _, p in ipairs(self.alive_players) do
|
|
||||||
self:notifyUI("MaxCard", {
|
|
||||||
pcardMax = p:getMaxCards(),
|
|
||||||
id = p.id,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
-- 刷自己的手牌
|
|
||||||
for _, cid in ipairs(Self:getCardIds("h")) do
|
|
||||||
self:notifyUI("UpdateCard", cid)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
self.discard_pile = {}
|
self.discard_pile = {}
|
||||||
|
@ -90,7 +72,6 @@ function Client:initialize()
|
||||||
|
|
||||||
self.disabled_packs = {}
|
self.disabled_packs = {}
|
||||||
self.disabled_generals = {}
|
self.disabled_generals = {}
|
||||||
-- self.last_update_ui = os.getms()
|
|
||||||
|
|
||||||
self.recording = false
|
self.recording = false
|
||||||
end
|
end
|
||||||
|
@ -133,6 +114,10 @@ function Client:moveCards(moves)
|
||||||
for _, move in ipairs(moves) do
|
for _, move in ipairs(moves) do
|
||||||
if move.from and move.fromArea then
|
if move.from and move.fromArea then
|
||||||
local from = self:getPlayerById(move.from)
|
local from = self:getPlayerById(move.from)
|
||||||
|
self:notifyUI("MaxCard", {
|
||||||
|
pcardMax = from:getMaxCards(),
|
||||||
|
id = move.from,
|
||||||
|
})
|
||||||
if move.fromArea == Card.PlayerHand and not Self:isBuddy(self:getPlayerById(move.from)) then
|
if move.fromArea == Card.PlayerHand and not Self:isBuddy(self:getPlayerById(move.from)) then
|
||||||
for _ = 1, #move.ids do
|
for _ = 1, #move.ids do
|
||||||
table.remove(from.player_cards[Player.Hand])
|
table.remove(from.player_cards[Player.Hand])
|
||||||
|
@ -148,11 +133,13 @@ function Client:moveCards(moves)
|
||||||
|
|
||||||
if move.to and move.toArea then
|
if move.to and move.toArea then
|
||||||
local ids = move.ids
|
local ids = move.ids
|
||||||
if (move.toArea == Card.PlayerHand and not Self:isBuddy(self:getPlayerById(move.to))) or
|
self:notifyUI("MaxCard", {
|
||||||
(move.toArea == Card.PlayerSpecial and not move.moveVisible) then
|
pcardMax = self:getPlayerById(move.to):getMaxCards(),
|
||||||
ids = {-1}
|
id = move.to,
|
||||||
|
})
|
||||||
|
if (not Self:isBuddy(self:getPlayerById(move.to)) and move.toArea == Card.PlayerHand) or table.contains(ids, -1) then
|
||||||
|
ids = table.map(ids, function() return -1 end)
|
||||||
end
|
end
|
||||||
|
|
||||||
self:getPlayerById(move.to):addCards(move.toArea, ids, move.specialName)
|
self:getPlayerById(move.to):addCards(move.toArea, ids, move.specialName)
|
||||||
elseif move.toArea == Card.DiscardPile then
|
elseif move.toArea == Card.DiscardPile then
|
||||||
table.insert(self.discard_pile, move.ids[1])
|
table.insert(self.discard_pile, move.ids[1])
|
||||||
|
@ -353,7 +340,6 @@ fk.client_callback["AddObserver"] = function(data)
|
||||||
}
|
}
|
||||||
local p = ClientPlayer:new(player)
|
local p = ClientPlayer:new(player)
|
||||||
table.insert(ClientInstance.observers, p)
|
table.insert(ClientInstance.observers, p)
|
||||||
-- ClientInstance:notifyUI("ServerMessage", string.format(Fk:translate("$AddObserver"), name))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
fk.client_callback["RemoveObserver"] = function(data)
|
fk.client_callback["RemoveObserver"] = function(data)
|
||||||
|
@ -361,7 +347,6 @@ fk.client_callback["RemoveObserver"] = function(data)
|
||||||
for _, p in ipairs(ClientInstance.observers) do
|
for _, p in ipairs(ClientInstance.observers) do
|
||||||
if p.player:getId() == id then
|
if p.player:getId() == id then
|
||||||
table.removeOne(ClientInstance.observers, p)
|
table.removeOne(ClientInstance.observers, p)
|
||||||
-- ClientInstance:notifyUI("ServerMessage", string.format(Fk:translate("$RemoveObserver"), p.player:getScreenName()))
|
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -402,6 +387,10 @@ fk.client_callback["PropertyUpdate"] = function(data)
|
||||||
end
|
end
|
||||||
|
|
||||||
ClientInstance:notifyUI("PropertyUpdate", data)
|
ClientInstance:notifyUI("PropertyUpdate", data)
|
||||||
|
ClientInstance:notifyUI("MaxCard", {
|
||||||
|
pcardMax = ClientInstance:getPlayerById(id):getMaxCards(),
|
||||||
|
id = id,
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
fk.client_callback["AskForCardChosen"] = function(data)
|
fk.client_callback["AskForCardChosen"] = function(data)
|
||||||
|
@ -485,38 +474,7 @@ end
|
||||||
---@param moves CardsMoveStruct[]
|
---@param moves CardsMoveStruct[]
|
||||||
local function separateMoves(moves)
|
local function separateMoves(moves)
|
||||||
local ret = {} ---@type CardsMoveInfo[]
|
local ret = {} ---@type CardsMoveInfo[]
|
||||||
|
|
||||||
local function containArea(area, relevant, defaultVisible) --处理区的处理?
|
|
||||||
local areas = relevant
|
|
||||||
and {Card.PlayerEquip, Card.PlayerJudge, Card.PlayerHand}
|
|
||||||
or {Card.PlayerEquip, Card.PlayerJudge}
|
|
||||||
return table.contains(areas, area) or (defaultVisible and table.contains({Card.Processing, Card.DiscardPile}, area))
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, move in ipairs(moves) do
|
for _, move in ipairs(moves) do
|
||||||
local singleVisible = move.moveVisible
|
|
||||||
if not singleVisible then
|
|
||||||
if move.visiblePlayers then
|
|
||||||
local visiblePlayers = move.visiblePlayers
|
|
||||||
if type(visiblePlayers) == "number" then
|
|
||||||
if Self:isBuddy(visiblePlayers) then
|
|
||||||
singleVisible = true
|
|
||||||
end
|
|
||||||
elseif type(visiblePlayers) == "table" then
|
|
||||||
if table.find(visiblePlayers, function(pid) return Self:isBuddy(pid) end) then
|
|
||||||
singleVisible = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if move.to and move.toArea == Card.PlayerSpecial and Self:isBuddy(move.to) then
|
|
||||||
singleVisible = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if not singleVisible then
|
|
||||||
singleVisible = containArea(move.toArea, move.to and Self:isBuddy(move.to), move.moveVisible == nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, info in ipairs(move.moveInfo) do
|
for _, info in ipairs(move.moveInfo) do
|
||||||
table.insert(ret, {
|
table.insert(ret, {
|
||||||
ids = {info.cardId},
|
ids = {info.cardId},
|
||||||
|
@ -528,7 +486,6 @@ local function separateMoves(moves)
|
||||||
specialName = move.specialName,
|
specialName = move.specialName,
|
||||||
fromSpecialName = info.fromSpecialName,
|
fromSpecialName = info.fromSpecialName,
|
||||||
proposer = move.proposer,
|
proposer = move.proposer,
|
||||||
moveVisible = singleVisible or containArea(info.fromArea, move.from and Self:isBuddy(move.from), move.moveVisible == nil)
|
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -556,7 +513,7 @@ local function mergeMoves(moves)
|
||||||
proposer = move.proposer,
|
proposer = move.proposer,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
table.insert(temp[info].ids, move.moveVisible and move.ids[1] or -1)
|
table.insert(temp[info].ids, move.ids[1])
|
||||||
end
|
end
|
||||||
for _, v in pairs(temp) do
|
for _, v in pairs(temp) do
|
||||||
table.insert(ret, v)
|
table.insert(ret, v)
|
||||||
|
@ -652,27 +609,8 @@ local function sendMoveCardLog(move)
|
||||||
from = move.from,
|
from = move.from,
|
||||||
card = move.ids,
|
card = move.ids,
|
||||||
}
|
}
|
||||||
elseif move.toArea == Card.Processing then
|
-- elseif move.toArea == Card.Processing then
|
||||||
if move.fromArea == Card.DrawPile and (move.moveReason == fk.ReasonPut or move.moveReason == fk.ReasonJustMove) then
|
-- nop
|
||||||
if hidden then
|
|
||||||
client:appendLog{
|
|
||||||
type = "$ViewCardFromDrawPile",
|
|
||||||
from = move.proposer,
|
|
||||||
arg = #move.ids,
|
|
||||||
}
|
|
||||||
else
|
|
||||||
client:appendLog{
|
|
||||||
type = "$TurnOverCardFromDrawPile",
|
|
||||||
from = move.proposer,
|
|
||||||
card = move.ids,
|
|
||||||
arg = #move.ids,
|
|
||||||
}
|
|
||||||
client:setCardNote(move.ids, {
|
|
||||||
type = "$$TurnOverCard",
|
|
||||||
from = move.proposer,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif move.from and move.toArea == Card.DrawPile then
|
elseif move.from and move.toArea == Card.DrawPile then
|
||||||
msgtype = hidden and "$PutCard" or "$PutKnownCard"
|
msgtype = hidden and "$PutCard" or "$PutKnownCard"
|
||||||
client:appendLog{
|
client:appendLog{
|
||||||
|
@ -1091,7 +1029,7 @@ end
|
||||||
|
|
||||||
fk.client_callback["EnterLobby"] = function(jsonData)
|
fk.client_callback["EnterLobby"] = function(jsonData)
|
||||||
local c = ClientInstance
|
local c = ClientInstance
|
||||||
---[[
|
--[[
|
||||||
if c.recording and not c.observing then
|
if c.recording and not c.observing then
|
||||||
c.recording = false
|
c.recording = false
|
||||||
c.record[2] = table.concat({
|
c.record[2] = table.concat({
|
||||||
|
@ -1182,7 +1120,7 @@ local function loadPlayerSummary(pdata)
|
||||||
to = id,
|
to = id,
|
||||||
toArea = Card.PlayerSpecial,
|
toArea = Card.PlayerSpecial,
|
||||||
specialName = k,
|
specialName = k,
|
||||||
moveVisible = true,
|
specialVisible = Self.id == id,
|
||||||
}
|
}
|
||||||
table.insert(card_moves, move)
|
table.insert(card_moves, move)
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,8 +16,6 @@ function GetGeneralData(name)
|
||||||
subkingdom = general.subkingdom,
|
subkingdom = general.subkingdom,
|
||||||
hp = general.hp,
|
hp = general.hp,
|
||||||
maxHp = general.maxHp,
|
maxHp = general.maxHp,
|
||||||
mainMaxHpAdjustedValue = general.mainMaxHpAdjustedValue,
|
|
||||||
deputyMaxHpAdjustedValue = general.deputyMaxHpAdjustedValue,
|
|
||||||
shield = general.shield,
|
shield = general.shield,
|
||||||
hidden = general.hidden,
|
hidden = general.hidden,
|
||||||
total_hidden = general.total_hidden,
|
total_hidden = general.total_hidden,
|
||||||
|
@ -33,8 +31,6 @@ function GetGeneralDetail(name)
|
||||||
kingdom = general.kingdom,
|
kingdom = general.kingdom,
|
||||||
hp = general.hp,
|
hp = general.hp,
|
||||||
maxHp = general.maxHp,
|
maxHp = general.maxHp,
|
||||||
mainMaxHp = general.mainMaxHpAdjustedValue,
|
|
||||||
deputyMaxHp = general.deputyMaxHpAdjustedValue,
|
|
||||||
gender = general.gender,
|
gender = general.gender,
|
||||||
skill = {},
|
skill = {},
|
||||||
related_skill = {},
|
related_skill = {},
|
||||||
|
@ -386,22 +382,6 @@ function CardFeasible(card, selected_targets)
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param card string | integer
|
|
||||||
---@param selected_targets integer[] @ ids of selected players
|
|
||||||
function CardPrompt(card, selected_targets)
|
|
||||||
local c ---@type Card
|
|
||||||
local selected_cards
|
|
||||||
if type(card) == "number" then
|
|
||||||
c = Fk:getCardById(card)
|
|
||||||
selected_cards = {card}
|
|
||||||
else
|
|
||||||
local t = json.decode(card)
|
|
||||||
return ActiveSkillPrompt(t.skill, t.subcards, selected_targets)
|
|
||||||
end
|
|
||||||
|
|
||||||
return ActiveSkillPrompt(c.skill, selected_cards, selected_targets)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Handle skills
|
-- Handle skills
|
||||||
|
|
||||||
function GetSkillData(skill_name)
|
function GetSkillData(skill_name)
|
||||||
|
@ -641,7 +621,6 @@ end
|
||||||
function GetInteractionOfSkill(skill_name)
|
function GetInteractionOfSkill(skill_name)
|
||||||
local skill = Fk.skills[skill_name]
|
local skill = Fk.skills[skill_name]
|
||||||
if skill and skill.interaction then
|
if skill and skill.interaction then
|
||||||
skill.interaction.data = nil
|
|
||||||
return skill:interaction()
|
return skill:interaction()
|
||||||
end
|
end
|
||||||
return nil
|
return nil
|
||||||
|
@ -759,9 +738,10 @@ function GetCardProhibitReason(cid, method, pattern)
|
||||||
local skillName = s.name
|
local skillName = s.name
|
||||||
local ret = Fk:translate(skillName)
|
local ret = Fk:translate(skillName)
|
||||||
if ret ~= skillName then
|
if ret ~= skillName then
|
||||||
return ret .. Fk:translate("prohibit") .. Fk:translate(method == "use" and "method_use" or "method_response_play")
|
-- TODO: translate
|
||||||
|
return ret .. "禁" .. (method == "use" and "使用" or "打出")
|
||||||
elseif skillName:endsWith("_prohibit") and skillName:startsWith("#") then
|
elseif skillName:endsWith("_prohibit") and skillName:startsWith("#") then
|
||||||
return Fk:translate(skillName:sub(2, -10)) .. Fk:translate("prohibit") .. Fk:translate(method == "use" and "method_use" or "method_response_play")
|
return Fk:translate(skillName:sub(2, -10)) .. "禁" .. (method == "use" and "使用" or "打出")
|
||||||
else
|
else
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,10 +12,7 @@ Fk:loadTranslationTable({
|
||||||
-- ["Old Password"] = "旧密码",
|
-- ["Old Password"] = "旧密码",
|
||||||
-- ["New Password"] = "新密码",
|
-- ["New Password"] = "新密码",
|
||||||
-- ["Update Avatar"] = "更新头像",
|
-- ["Update Avatar"] = "更新头像",
|
||||||
-- ["Update avatar done."] = "头像已更新",
|
|
||||||
-- ["Update Password"] = "更新密码",
|
-- ["Update Password"] = "更新密码",
|
||||||
-- ["Update password done."] = "密码已更新",
|
|
||||||
-- ["Old password wrong!"] = "旧密码错误!",
|
|
||||||
-- ["Lobby BG"] = "大厅壁纸",
|
-- ["Lobby BG"] = "大厅壁纸",
|
||||||
-- ["Room BG"] = "房间背景",
|
-- ["Room BG"] = "房间背景",
|
||||||
-- ["Game BGM"] = "游戏BGM",
|
-- ["Game BGM"] = "游戏BGM",
|
||||||
|
@ -93,14 +90,9 @@ Fk:loadTranslationTable({
|
||||||
["Cards Overview"] = "Cards",
|
["Cards Overview"] = "Cards",
|
||||||
["Special card skills:"] = "<b>Special use method:</b>",
|
["Special card skills:"] = "<b>Special use method:</b>",
|
||||||
["Every suit & number:"] = "<b>All suit and number:</b>",
|
["Every suit & number:"] = "<b>All suit and number:</b>",
|
||||||
-- ["Male Audio"] = "男性音效",
|
|
||||||
-- ["Female Audio"] = "女性音效",
|
|
||||||
-- ["Equip Effect Audio"] = "效果音效",
|
|
||||||
-- ["Equip Use Audio"] = "使用音效",
|
|
||||||
["Scenarios Overview"] = "Game modes",
|
["Scenarios Overview"] = "Game modes",
|
||||||
-- ["Replay"] = "录像",
|
-- ["Replay"] = "录像",
|
||||||
-- ["Replay Manager"] = "来欣赏潇洒的录像吧!",
|
-- ["Replay Manager"] = "来欣赏潇洒的录像吧!",
|
||||||
-- ["Replay from File"] = "从文件打开",
|
|
||||||
["Game Win"] = "Win",
|
["Game Win"] = "Win",
|
||||||
["Game Lose"] = "Lose",
|
["Game Lose"] = "Lose",
|
||||||
["Play the Replay"] = "Play",
|
["Play the Replay"] = "Play",
|
||||||
|
@ -164,7 +156,7 @@ Fk:loadTranslationTable({
|
||||||
["IncludeDeputy"] = "<font color=\"red\">Deputy character enabled</font>",
|
["IncludeDeputy"] = "<font color=\"red\">Deputy character enabled</font>",
|
||||||
|
|
||||||
-- Room
|
-- Room
|
||||||
["$EnterRoom"] = "Successfully entered the room",
|
["$EnterRoom"] = "Successfully entered the room.",
|
||||||
["#currentRoundNum"] = "Round #%1",
|
["#currentRoundNum"] = "Round #%1",
|
||||||
["$Choice"] = "%1: Please choose",
|
["$Choice"] = "%1: Please choose",
|
||||||
["$ChooseGeneral"] = "Please choose %1 character(s)",
|
["$ChooseGeneral"] = "Please choose %1 character(s)",
|
||||||
|
@ -355,7 +347,7 @@ Fk:loadTranslationTable({
|
||||||
|
|
||||||
-- get/lose skill
|
-- get/lose skill
|
||||||
["#AcquireSkill"] = '%from acquired the skill "%arg"',
|
["#AcquireSkill"] = '%from acquired the skill "%arg"',
|
||||||
["#LoseSkill"] = '%from lost the skill "%arg"',
|
["#LoseSkill"] = '%from lost the skill "%arg"',
|
||||||
|
|
||||||
-- moveCards (they are sent by notifyMoveCards)
|
-- moveCards (they are sent by notifyMoveCards)
|
||||||
["$PutCard"] = "%arg card(s) of %from were put into draw pile",
|
["$PutCard"] = "%arg card(s) of %from were put into draw pile",
|
||||||
|
@ -413,8 +405,8 @@ Fk:loadTranslationTable({
|
||||||
|
|
||||||
-- turnOver
|
-- turnOver
|
||||||
["#TurnOver"] = "%from turned over character card, now his status is %arg",
|
["#TurnOver"] = "%from turned over character card, now his status is %arg",
|
||||||
["face_up"] = "face up",
|
["face_up"] = "face up",
|
||||||
["face_down"] = "face down",
|
["face_down"] = "face down",
|
||||||
|
|
||||||
-- damage, heal and lose HP
|
-- damage, heal and lose HP
|
||||||
["#Damage"] = "%to dealt %arg %arg2 DMG to %from",
|
["#Damage"] = "%to dealt %arg %arg2 DMG to %from",
|
||||||
|
@ -451,6 +443,4 @@ Fk:loadTranslationTable({
|
||||||
["##ResponsePlayCard"] = "%from plays",
|
["##ResponsePlayCard"] = "%from plays",
|
||||||
["##ShowCard"] = "%from shows",
|
["##ShowCard"] = "%from shows",
|
||||||
["##JudgeCard"] = "%arg judge",
|
["##JudgeCard"] = "%arg judge",
|
||||||
["##PindianCard"] = "%from point fights",
|
|
||||||
["##RecastCard"] = "%from recasts",
|
|
||||||
}, "en_US")
|
}, "en_US")
|
||||||
|
|
|
@ -12,10 +12,7 @@ Fk:loadTranslationTable{
|
||||||
["Old Password"] = "旧密码",
|
["Old Password"] = "旧密码",
|
||||||
["New Password"] = "新密码",
|
["New Password"] = "新密码",
|
||||||
["Update Avatar"] = "更新头像",
|
["Update Avatar"] = "更新头像",
|
||||||
["Update avatar done."] = "头像已更新",
|
|
||||||
["Update Password"] = "更新密码",
|
["Update Password"] = "更新密码",
|
||||||
["Update password done."] = "密码已更新",
|
|
||||||
["Old password wrong!"] = "旧密码错误!",
|
|
||||||
["Lobby BG"] = "大厅壁纸",
|
["Lobby BG"] = "大厅壁纸",
|
||||||
["Room BG"] = "房间背景",
|
["Room BG"] = "房间背景",
|
||||||
["Game BGM"] = "游戏BGM",
|
["Game BGM"] = "游戏BGM",
|
||||||
|
@ -34,7 +31,7 @@ Fk:loadTranslationTable{
|
||||||
["Search"] = "搜索",
|
["Search"] = "搜索",
|
||||||
["Back"] = "返回",
|
["Back"] = "返回",
|
||||||
|
|
||||||
["Refresh Room List"] = "刷新房间列表 (%1个房间)",
|
["Refresh Room List"] = "刷新房间列表",
|
||||||
|
|
||||||
["Disable Extension"] = "禁用Lua拓展 (重启后生效)",
|
["Disable Extension"] = "禁用Lua拓展 (重启后生效)",
|
||||||
["Create Room"] = "创建房间",
|
["Create Room"] = "创建房间",
|
||||||
|
@ -103,12 +100,9 @@ Fk:loadTranslationTable{
|
||||||
["Every suit & number:"] = "<b>所有的花色和点数:</b>",
|
["Every suit & number:"] = "<b>所有的花色和点数:</b>",
|
||||||
["Male Audio"] = "男性音效",
|
["Male Audio"] = "男性音效",
|
||||||
["Female Audio"] = "女性音效",
|
["Female Audio"] = "女性音效",
|
||||||
["Equip Effect Audio"] = "效果音效",
|
|
||||||
["Equip Use Audio"] = "使用音效",
|
|
||||||
["Scenarios Overview"] = "玩法一览",
|
["Scenarios Overview"] = "玩法一览",
|
||||||
["Replay"] = "录像",
|
["Replay"] = "录像",
|
||||||
["Replay Manager"] = "来欣赏潇洒的录像吧!",
|
["Replay Manager"] = "来欣赏潇洒的录像吧!",
|
||||||
["Replay from File"] = "从文件打开",
|
|
||||||
["Game Win"] = "胜利",
|
["Game Win"] = "胜利",
|
||||||
["Game Lose"] = "失败",
|
["Game Lose"] = "失败",
|
||||||
["Play the Replay"] = "重放",
|
["Play the Replay"] = "重放",
|
||||||
|
@ -218,7 +212,7 @@ FreeKill使用的是libgit2的C API,与此同时使用Git完成拓展包的下
|
||||||
["IncludeDeputy"] = "<font color=\"red\">启用副将机制</font>",
|
["IncludeDeputy"] = "<font color=\"red\">启用副将机制</font>",
|
||||||
|
|
||||||
-- Room
|
-- Room
|
||||||
["$EnterRoom"] = "成功加入房间",
|
["$EnterRoom"] = "成功加入房间。",
|
||||||
["#currentRoundNum"] = "第 %1 轮",
|
["#currentRoundNum"] = "第 %1 轮",
|
||||||
["$Choice"] = "%1:请选择",
|
["$Choice"] = "%1:请选择",
|
||||||
["$ChooseGeneral"] = "请选择 %1 名武将",
|
["$ChooseGeneral"] = "请选择 %1 名武将",
|
||||||
|
@ -228,7 +222,7 @@ FreeKill使用的是libgit2的C API,与此同时使用Git完成拓展包的下
|
||||||
|
|
||||||
["#PlayCard"] = "出牌阶段,请使用一张牌",
|
["#PlayCard"] = "出牌阶段,请使用一张牌",
|
||||||
["#AskForGeneral"] = "请选择 1 名武将",
|
["#AskForGeneral"] = "请选择 1 名武将",
|
||||||
["#AskForSkillInvoke"] = "你想发动〖%1〗吗?",
|
["#AskForSkillInvoke"] = "你想发动技能“%1”吗?",
|
||||||
["#AskForLuckCard"] = "你想使用手气卡吗?还可以使用 %1 次,剩余手气卡∞张",
|
["#AskForLuckCard"] = "你想使用手气卡吗?还可以使用 %1 次,剩余手气卡∞张",
|
||||||
["AskForLuckCard"] = "手气卡",
|
["AskForLuckCard"] = "手气卡",
|
||||||
["#AskForChoice"] = "%1:请选择",
|
["#AskForChoice"] = "%1:请选择",
|
||||||
|
@ -260,13 +254,13 @@ FreeKill使用的是libgit2的C API,与此同时使用Git完成拓展包的下
|
||||||
["$Equip"] = "装备区",
|
["$Equip"] = "装备区",
|
||||||
["$Judge"] = "判定区",
|
["$Judge"] = "判定区",
|
||||||
['$Selected'] = "已选择",
|
['$Selected'] = "已选择",
|
||||||
["#AskForUseActiveSkill"] = "请发动〖%1〗",
|
["#AskForUseActiveSkill"] = "请使用技能 %1",
|
||||||
["#AskForUseCard"] = "请使用【%1】",
|
["#AskForUseCard"] = "请使用卡牌 %1",
|
||||||
["#AskForResponseCard"] = "请打出【%1】",
|
["#AskForResponseCard"] = "请打出卡牌 %1",
|
||||||
["#AskForNullification"] = "是否为目标为 %dest 的【%arg】使用【无懈可击】?",
|
["#AskForNullification"] = "是否为目标为 %dest 的 %arg 使用无懈可击?",
|
||||||
["#AskForNullificationWithoutTo"] = "是否对 %src 使用的【%arg】使用【无懈可击】?",
|
["#AskForNullificationWithoutTo"] = "是否对 %src 使用的 %arg 使用无懈可击?",
|
||||||
["#AskForPeaches"] = "%src 生命危急,需要 %arg 个【桃】",
|
["#AskForPeaches"] = "%src 生命危急,需要 %arg 个桃",
|
||||||
["#AskForPeachesSelf"] = "你生命危急,需要 %arg 个【桃】或【酒】",
|
["#AskForPeachesSelf"] = "你生命危急,需要 %arg 个桃或酒",
|
||||||
|
|
||||||
["#AskForDiscard"] = "请弃置 %arg 张牌,最少 %arg2 张",
|
["#AskForDiscard"] = "请弃置 %arg 张牌,最少 %arg2 张",
|
||||||
["#AskForCard"] = "请选择 %arg 张牌,最少 %arg2 张",
|
["#AskForCard"] = "请选择 %arg 张牌,最少 %arg2 张",
|
||||||
|
@ -328,9 +322,6 @@ FreeKill使用的是libgit2的C API,与此同时使用Git完成拓展包的下
|
||||||
["Back To Lobby"] = "返回大厅",
|
["Back To Lobby"] = "返回大厅",
|
||||||
["Save Replay"] = "保存录像",
|
["Save Replay"] = "保存录像",
|
||||||
|
|
||||||
["$AddObserver"] = '玩家 <b>%s</b> 开始旁观',
|
|
||||||
["$RemoveObserver"] = '旁观者 <b>%s</b> 离开了房间',
|
|
||||||
|
|
||||||
["Speed Resume"] = "匀速",
|
["Speed Resume"] = "匀速",
|
||||||
["Speed Up"] = "加速",
|
["Speed Up"] = "加速",
|
||||||
["Speed Down"] = "减速",
|
["Speed Down"] = "减速",
|
||||||
|
@ -382,7 +373,6 @@ Fk:loadTranslationTable{
|
||||||
["pile_discard"] = "弃牌堆",
|
["pile_discard"] = "弃牌堆",
|
||||||
["processing_area"] = "处理区",
|
["processing_area"] = "处理区",
|
||||||
["Pile"] = "牌堆",
|
["Pile"] = "牌堆",
|
||||||
["toObtain"] = "获得的牌",
|
|
||||||
["Top"] = "牌堆顶",
|
["Top"] = "牌堆顶",
|
||||||
["Bottom"] = "牌堆底",
|
["Bottom"] = "牌堆底",
|
||||||
["Shuffle"] = "洗牌",
|
["Shuffle"] = "洗牌",
|
||||||
|
@ -418,8 +408,8 @@ Fk:loadTranslationTable{
|
||||||
["$GameEnd"] = "== 游戏结束 ==",
|
["$GameEnd"] = "== 游戏结束 ==",
|
||||||
|
|
||||||
-- get/lose skill
|
-- get/lose skill
|
||||||
["#AcquireSkill"] = "%from 获得了〖%arg〗",
|
["#AcquireSkill"] = "%from 获得了技能 “%arg”",
|
||||||
["#LoseSkill"] = "%from 失去了〖%arg〗",
|
["#LoseSkill"] = "%from 失去了技能 “%arg”",
|
||||||
|
|
||||||
-- moveCards (they are sent by notifyMoveCards)
|
-- moveCards (they are sent by notifyMoveCards)
|
||||||
["$GetCardsFromPile"] = "%from 从 %arg 中获得了 %arg2 张牌 %card",
|
["$GetCardsFromPile"] = "%from 从 %arg 中获得了 %arg2 张牌 %card",
|
||||||
|
@ -442,8 +432,6 @@ Fk:loadTranslationTable{
|
||||||
["$DiscardCards"] = "%from 弃置了 %arg 张牌 %card",
|
["$DiscardCards"] = "%from 弃置了 %arg 张牌 %card",
|
||||||
["$DiscardOther"] = "%to 弃置了 %from 的 %arg 张牌 %card",
|
["$DiscardOther"] = "%to 弃置了 %from 的 %arg 张牌 %card",
|
||||||
["$PutToDiscard"] = "%arg 张牌 %card 被置入弃牌堆",
|
["$PutToDiscard"] = "%arg 张牌 %card 被置入弃牌堆",
|
||||||
["$ViewCardFromDrawPile"] = "%from 观看了 %arg 张牌",
|
|
||||||
["$TurnOverCardFromDrawPile"] = "%from 亮出了 %arg 张牌 %card",
|
|
||||||
|
|
||||||
["#AbortArea"] = "%from 的 %arg 被废除",
|
["#AbortArea"] = "%from 的 %arg 被废除",
|
||||||
["#ResumeArea"] = "%from 的 %arg 被恢复",
|
["#ResumeArea"] = "%from 的 %arg 被恢复",
|
||||||
|
@ -476,30 +464,30 @@ Fk:loadTranslationTable{
|
||||||
["#FilterCard"] = "由于 %arg 的效果,与 %from 相关的 %arg2 被视为了 %arg3",
|
["#FilterCard"] = "由于 %arg 的效果,与 %from 相关的 %arg2 被视为了 %arg3",
|
||||||
|
|
||||||
-- skill
|
-- skill
|
||||||
["#InvokeSkill"] = "%from 发动了〖%arg〗",
|
["#InvokeSkill"] = "%from 发动了 “%arg”",
|
||||||
|
|
||||||
-- judge
|
-- judge
|
||||||
["#StartJudgeReason"] = "%from 开始了 %arg 的判定",
|
["#StartJudgeReason"] = "%from 开始了 %arg 的判定",
|
||||||
["#InitialJudge"] = "%from 的判定牌为 %arg",
|
["#InitialJudge"] = "%from 的判定牌为 %arg",
|
||||||
["#ChangedJudge"] = "%from 发动了〖%arg〗把 %to 的判定牌改为 %arg2",
|
["#ChangedJudge"] = "%from 发动“%arg”把 %to 的判定牌改为 %arg2",
|
||||||
["#JudgeResult"] = "%from 的判定结果为 %arg",
|
["#JudgeResult"] = "%from 的判定结果为 %arg",
|
||||||
|
|
||||||
-- turnOver
|
-- turnOver
|
||||||
["#TurnOver"] = "%from 将武将牌翻面,现在是 %arg",
|
["#TurnOver"] = "%from 将武将牌翻面,现在是 %arg",
|
||||||
["face_up"] = "正面朝上",
|
["face_up"] = "正面朝上",
|
||||||
["face_down"] = "背面朝上",
|
["face_down"] = "背面朝上",
|
||||||
|
|
||||||
-- damage, heal and lose HP
|
-- damage, heal and lose HP
|
||||||
["#Damage"] = "%to 对 %from 造成了 %arg 点 %arg2 伤害",
|
["#Damage"] = "%to 对 %from 造成了 %arg 点 %arg2 伤害",
|
||||||
["#DamageWithNoFrom"] = "%from 受到了 %arg 点 %arg2 伤害",
|
["#DamageWithNoFrom"] = "%from 受到了 %arg 点 %arg2 伤害",
|
||||||
["#LoseHP"] = "%from 失去了 %arg 点体力",
|
["#LoseHP"] = "%from 失去了 %arg 点体力",
|
||||||
["#HealHP"] = "%from 回复了 %arg 点体力",
|
["#HealHP"] = "%from 回复了 %arg 点体力",
|
||||||
["#ShowHPAndMaxHP"] = "%from 的体力值为 %arg,体力上限为 %arg2",
|
["#ShowHPAndMaxHP"] = "%from 现在的体力值为 %arg,体力上限为 %arg2",
|
||||||
["#LoseMaxHP"] = "%from 减了 %arg 点体力上限",
|
["#LoseMaxHP"] = "%from 减了 %arg 点体力上限",
|
||||||
["#HealMaxHP"] = "%from 加了 %arg 点体力上限",
|
["#HealMaxHP"] = "%from 加了 %arg 点体力上限",
|
||||||
|
|
||||||
-- dying and death
|
-- dying and death
|
||||||
["#EnterDying"] = "%from 进入了濒死状态",
|
["#EnterDying"] = "%from 进入了濒死阶段",
|
||||||
["#KillPlayer"] = "%from [%arg] 阵亡,凶手是 %to",
|
["#KillPlayer"] = "%from [%arg] 阵亡,凶手是 %to",
|
||||||
["#KillPlayerWithNoKiller"] = "%from [%arg] 阵亡,无伤害来源",
|
["#KillPlayerWithNoKiller"] = "%from [%arg] 阵亡,无伤害来源",
|
||||||
["#Revive"] = "%from 竟然复活了",
|
["#Revive"] = "%from 竟然复活了",
|
||||||
|
@ -511,7 +499,7 @@ Fk:loadTranslationTable{
|
||||||
["#GuanxingResult"] = "%from 的观星结果为 %arg 上 %arg2 下",
|
["#GuanxingResult"] = "%from 的观星结果为 %arg 上 %arg2 下",
|
||||||
["#ChainStateChange"] = "%from %arg 了武将牌",
|
["#ChainStateChange"] = "%from %arg 了武将牌",
|
||||||
["#ChainDamage"] = "%from 处于连环状态,将受到传导的伤害",
|
["#ChainDamage"] = "%from 处于连环状态,将受到传导的伤害",
|
||||||
["#ChangeKingdom"] = "%from 的势力从 %arg 变成了 %arg2",
|
["#ChangeKingdom"] = "%from 的国籍从 %arg 变成了 %arg2",
|
||||||
["#RoomOutdated"] = "服务器更新完毕!该房间已过期,将无法再次游玩",
|
["#RoomOutdated"] = "服务器更新完毕!该房间已过期,将无法再次游玩",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -519,13 +507,10 @@ Fk:loadTranslationTable{
|
||||||
Fk:loadTranslationTable{
|
Fk:loadTranslationTable{
|
||||||
["$$DiscardCards"] = "%from弃置",
|
["$$DiscardCards"] = "%from弃置",
|
||||||
["$$PutCard"] = "%from置于",
|
["$$PutCard"] = "%from置于",
|
||||||
["$$TurnOverCard"] = "%from亮出",
|
|
||||||
|
|
||||||
["##UseCard"] = "%from使用",
|
["##UseCard"] = "%from使用",
|
||||||
["##UseCardTo"] = "%from对%to",
|
["##UseCardTo"] = "%from对%to",
|
||||||
["##ResponsePlayCard"] = "%from打出",
|
["##ResponsePlayCard"] = "%from打出",
|
||||||
["##ShowCard"] = "%from展示",
|
["##ShowCard"] = "%from展示",
|
||||||
["##JudgeCard"] = "%arg判定",
|
["##JudgeCard"] = "%arg判定",
|
||||||
["##PindianCard"] = "%from拼点",
|
|
||||||
["##RecastCard"] = "%from重铸",
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,80 +2,33 @@
|
||||||
|
|
||||||
---@class EquipCard : Card
|
---@class EquipCard : Card
|
||||||
---@field public equip_skill Skill
|
---@field public equip_skill Skill
|
||||||
---@field public equip_skills Skill[]
|
|
||||||
---@field public dynamicEquipSkills fun(player: Player): Skill[]
|
|
||||||
local EquipCard = Card:subclass("EquipCard")
|
local EquipCard = Card:subclass("EquipCard")
|
||||||
|
|
||||||
function EquipCard:initialize(name, suit, number)
|
function EquipCard:initialize(name, suit, number)
|
||||||
Card.initialize(self, name, suit, number)
|
Card.initialize(self, name, suit, number)
|
||||||
self.type = Card.TypeEquip
|
self.type = Card.TypeEquip
|
||||||
self.equip_skill = nil
|
self.equip_skill = nil
|
||||||
self.equip_skills = nil
|
|
||||||
self.dynamicEquipSkills = nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param room Room
|
---@param room Room
|
||||||
---@param player Player
|
---@param player Player
|
||||||
function EquipCard:onInstall(room, player)
|
function EquipCard:onInstall(room, player)
|
||||||
local equipSkills = self:getEquipSkills(player)
|
if self.equip_skill then
|
||||||
if #equipSkills > 0 then
|
room:handleAddLoseSkills(player, self.equip_skill.name, nil, false, true)
|
||||||
local noTrigger = table.filter(equipSkills, function(skill) return skill.attached_equip end)
|
|
||||||
if #noTrigger > 0 then
|
|
||||||
noTrigger = table.map(noTrigger, function(skill) return skill.name end)
|
|
||||||
room:handleAddLoseSkills(player, table.concat(noTrigger, "|"), nil, false, true)
|
|
||||||
end
|
|
||||||
|
|
||||||
local toTrigger = table.filter(equipSkills, function(skill) return not skill.attached_equip end)
|
|
||||||
if #toTrigger > 0 then
|
|
||||||
toTrigger = table.map(toTrigger, function(skill) return skill.name end)
|
|
||||||
room:handleAddLoseSkills(player, table.concat(toTrigger, "|"), nil, false)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param room Room
|
---@param room Room
|
||||||
---@param player Player
|
---@param player Player
|
||||||
function EquipCard:onUninstall(room, player)
|
function EquipCard:onUninstall(room, player)
|
||||||
local equipSkills = self:getEquipSkills(player)
|
if self.equip_skill then
|
||||||
if #equipSkills > 0 then
|
room:handleAddLoseSkills(player, "-" .. self.equip_skill.name, nil, false, true)
|
||||||
local noTrigger = table.filter(equipSkills, function(skill) return skill.attached_equip end)
|
|
||||||
if #noTrigger > 0 then
|
|
||||||
noTrigger = table.map(noTrigger, function(skill) return '-' .. skill.name end)
|
|
||||||
room:handleAddLoseSkills(player, table.concat(noTrigger, "|"), nil, false, true)
|
|
||||||
end
|
|
||||||
|
|
||||||
local toTrigger = table.filter(equipSkills, function(skill) return not skill.attached_equip end)
|
|
||||||
if #toTrigger > 0 then
|
|
||||||
toTrigger = table.map(toTrigger, function(skill) return '-' .. skill.name end)
|
|
||||||
room:handleAddLoseSkills(player, table.concat(toTrigger, "|"), nil, false)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param player Player
|
|
||||||
---@return Skill[]
|
|
||||||
function EquipCard:getEquipSkills(player)
|
|
||||||
if self.dynamicEquipSkills then
|
|
||||||
local equipSkills = self:dynamicEquipSkills(player)
|
|
||||||
if equipSkills and #equipSkills > 0 then
|
|
||||||
return equipSkills
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if self.equip_skills then
|
|
||||||
return self.equip_skills
|
|
||||||
elseif self.equip_skill then
|
|
||||||
return { self.equip_skill }
|
|
||||||
end
|
|
||||||
|
|
||||||
return {}
|
|
||||||
end
|
|
||||||
|
|
||||||
function EquipCard:clone(suit, number)
|
function EquipCard:clone(suit, number)
|
||||||
local ret = Card.clone(self, suit, number)
|
local ret = Card.clone(self, suit, number)
|
||||||
ret.equip_skill = self.equip_skill
|
ret.equip_skill = self.equip_skill
|
||||||
ret.equip_skills = self.equip_skills
|
|
||||||
ret.dynamicEquipSkills = self.dynamicEquipSkills
|
|
||||||
ret.onInstall = self.onInstall
|
ret.onInstall = self.onInstall
|
||||||
ret.onUninstall = self.onUninstall
|
ret.onUninstall = self.onUninstall
|
||||||
return ret
|
return ret
|
||||||
|
@ -83,7 +36,6 @@ end
|
||||||
|
|
||||||
---@class Weapon : EquipCard
|
---@class Weapon : EquipCard
|
||||||
---@field public attack_range integer
|
---@field public attack_range integer
|
||||||
---@field public dynamicAttackRange? fun(player: Player): int
|
|
||||||
local Weapon = EquipCard:subclass("Weapon")
|
local Weapon = EquipCard:subclass("Weapon")
|
||||||
|
|
||||||
function Weapon:initialize(name, suit, number, attackRange)
|
function Weapon:initialize(name, suit, number, attackRange)
|
||||||
|
@ -95,21 +47,9 @@ end
|
||||||
function Weapon:clone(suit, number)
|
function Weapon:clone(suit, number)
|
||||||
local ret = EquipCard.clone(self, suit, number)
|
local ret = EquipCard.clone(self, suit, number)
|
||||||
ret.attack_range = self.attack_range
|
ret.attack_range = self.attack_range
|
||||||
ret.dynamicAttackRange = self.dynamicAttackRange
|
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
function Weapon:getAttackRange(player)
|
|
||||||
if type(self.dynamicAttackRange) == "function" then
|
|
||||||
local currentAttackRange = self:dynamicAttackRange(player)
|
|
||||||
if currentAttackRange then
|
|
||||||
return currentAttackRange
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return self.attack_range
|
|
||||||
end
|
|
||||||
|
|
||||||
---@class Armor : EquipCard
|
---@class Armor : EquipCard
|
||||||
local Armor = EquipCard:subclass("armor")
|
local Armor = EquipCard:subclass("armor")
|
||||||
|
|
||||||
|
|
|
@ -676,10 +676,9 @@ end
|
||||||
---
|
---
|
||||||
--- 其实就是翻译了 ":" .. name 罢了
|
--- 其实就是翻译了 ":" .. name 罢了
|
||||||
---@param name string @ 要获得描述的名字
|
---@param name string @ 要获得描述的名字
|
||||||
---@param lang? string @ 要使用的语言,默认读取config
|
|
||||||
---@return string @ 描述
|
---@return string @ 描述
|
||||||
function Engine:getDescription(name, lang)
|
function Engine:getDescription(name)
|
||||||
return self:translate(":" .. name, lang)
|
return self:translate(":" .. name)
|
||||||
end
|
end
|
||||||
|
|
||||||
return Engine
|
return Engine
|
||||||
|
|
|
@ -71,16 +71,4 @@ function GameMode:countInFunc(room)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 修改角色的属性
|
|
||||||
---@param player ServerPlayer
|
|
||||||
---@return table @ 返回表,键为调整的角色属性,值为调整后的属性
|
|
||||||
function GameMode:getAdjustedProperty (player)
|
|
||||||
local list = {}
|
|
||||||
if player.role == "lord" and player.role_shown and #player.room.players > 4 then
|
|
||||||
list.hp = player.hp + 1
|
|
||||||
list.maxHp = player.maxHp + 1
|
|
||||||
end
|
|
||||||
return list
|
|
||||||
end
|
|
||||||
|
|
||||||
return GameMode
|
return GameMode
|
||||||
|
|
|
@ -260,7 +260,13 @@ function Player:removeCards(playerArea, cardIds, specialName)
|
||||||
if #fromAreaIds == 0 then
|
if #fromAreaIds == 0 then
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
if not table.removeOne(fromAreaIds, id) and not table.removeOne(fromAreaIds, -1) then
|
|
||||||
|
if table.contains(fromAreaIds, id) then
|
||||||
|
table.removeOne(fromAreaIds, id)
|
||||||
|
-- FIXME: 为客户端移动id为-1的牌考虑,但总感觉有地方需要商讨啊!
|
||||||
|
elseif table.every(fromAreaIds, function(e) return e == -1 end) then
|
||||||
|
table.remove(fromAreaIds, 1)
|
||||||
|
elseif id == -1 then
|
||||||
table.remove(fromAreaIds, 1)
|
table.remove(fromAreaIds, 1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -449,7 +455,7 @@ function Player:getAttackRange()
|
||||||
baseValue = 0
|
baseValue = 0
|
||||||
for _, id in ipairs(weapons) do
|
for _, id in ipairs(weapons) do
|
||||||
local weapon = Fk:getCardById(id)
|
local weapon = Fk:getCardById(id)
|
||||||
baseValue = math.max(baseValue, weapon:getAttackRange(self) or 1)
|
baseValue = math.max(baseValue, weapon.attack_range or 1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -557,7 +563,7 @@ end
|
||||||
--- 比较距离
|
--- 比较距离
|
||||||
---@param other Player @ 终点角色
|
---@param other Player @ 终点角色
|
||||||
---@param num integer @ 比较基准
|
---@param num integer @ 比较基准
|
||||||
---@param operator "<"|">"|"<="|">="|"=="|"~=" @ 运算符
|
---@param operator string @ 运算符,有 ``"<"`` ``">"`` ``"<="`` ``">="`` ``"=="`` ``"~="``
|
||||||
---@return boolean @ 返回比较结果,不计入距离结果永远为false
|
---@return boolean @ 返回比较结果,不计入距离结果永远为false
|
||||||
function Player:compareDistance(other, num, operator)
|
function Player:compareDistance(other, num, operator)
|
||||||
local distance = self:distanceTo(other)
|
local distance = self:distanceTo(other)
|
||||||
|
@ -590,11 +596,6 @@ function Player:inMyAttackRange(other, fixLimit)
|
||||||
fixLimit = fixLimit or 0
|
fixLimit = fixLimit or 0
|
||||||
|
|
||||||
local status_skills = Fk:currentRoom().status_skills[AttackRangeSkill] or Util.DummyTable
|
local status_skills = Fk:currentRoom().status_skills[AttackRangeSkill] or Util.DummyTable
|
||||||
for _, skill in ipairs(status_skills) do
|
|
||||||
if skill:withoutAttackRange(self, other) then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
for _, skill in ipairs(status_skills) do
|
for _, skill in ipairs(status_skills) do
|
||||||
if skill:withinAttackRange(self, other) then
|
if skill:withinAttackRange(self, other) then
|
||||||
return true
|
return true
|
||||||
|
@ -608,8 +609,7 @@ end
|
||||||
--- 获取下家。
|
--- 获取下家。
|
||||||
---@param ignoreRemoved? boolean @ 忽略被移除
|
---@param ignoreRemoved? boolean @ 忽略被移除
|
||||||
---@param num? integer @ 第几个,默认1
|
---@param num? integer @ 第几个,默认1
|
||||||
---@param ignoreRest? boolean @ 是否忽略休整
|
---@return ServerPlayer
|
||||||
---@return Player
|
|
||||||
function Player:getNextAlive(ignoreRemoved, num, ignoreRest)
|
function Player:getNextAlive(ignoreRemoved, num, ignoreRest)
|
||||||
if #Fk:currentRoom().alive_players == 0 then
|
if #Fk:currentRoom().alive_players == 0 then
|
||||||
return self.rest > 0 and self.next.rest > 0 and self.next or self
|
return self.rest > 0 and self.next.rest > 0 and self.next or self
|
||||||
|
@ -631,9 +631,9 @@ function Player:getNextAlive(ignoreRemoved, num, ignoreRest)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- 获取上家。
|
--- 获取上家。
|
||||||
---@param ignoreRemoved? boolean @ 忽略被移除
|
---@param ignoreRemoved boolean @ 忽略被移除
|
||||||
---@param num? integer @ 第几个,默认1
|
---@param num? integer @ 第几个,默认1
|
||||||
---@return Player
|
---@return ServerPlayer
|
||||||
function Player:getLastAlive(ignoreRemoved, num)
|
function Player:getLastAlive(ignoreRemoved, num)
|
||||||
num = num or 1
|
num = num or 1
|
||||||
local index = (ignoreRemoved and #Fk:currentRoom().alive_players or #table.filter(Fk:currentRoom().alive_players, function(p) return not p:isRemoved() end)) - num
|
local index = (ignoreRemoved and #Fk:currentRoom().alive_players or #table.filter(Fk:currentRoom().alive_players, function(p) return not p:isRemoved() end)) - num
|
||||||
|
@ -1019,11 +1019,9 @@ function Player:prohibitReveal(isDeputy)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
--- 判断能否拼点
|
---@param to Player
|
||||||
---@param to Player @ 拼点对象
|
---@param ignoreFromKong? boolean
|
||||||
---@param ignoreFromKong? boolean @ 忽略发起者没有手牌
|
---@param ignoreToKong? boolean
|
||||||
---@param ignoreToKong? boolean @ 忽略对象没有手牌
|
|
||||||
---@return boolean
|
|
||||||
function Player:canPindian(to, ignoreFromKong, ignoreToKong)
|
function Player:canPindian(to, ignoreFromKong, ignoreToKong)
|
||||||
if self == to then return false end
|
if self == to then return false end
|
||||||
|
|
||||||
|
@ -1075,10 +1073,6 @@ function Player:getSwitchSkillState(skillName, afterUse, inWord)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- 是否能移动特定牌至特定角色
|
|
||||||
---@param to Player @ 移动至的角色
|
|
||||||
---@param id integer @ 移动的牌
|
|
||||||
---@return boolean
|
|
||||||
function Player:canMoveCardInBoardTo(to, id)
|
function Player:canMoveCardInBoardTo(to, id)
|
||||||
if self == to then
|
if self == to then
|
||||||
return false
|
return false
|
||||||
|
@ -1100,11 +1094,6 @@ function Player:canMoveCardInBoardTo(to, id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- 是否能移动特定牌至特定角色
|
|
||||||
--- @param to Player @ 移动至的角色
|
|
||||||
--- @param flag? string @ 移动的区域,`e`为装备区,`j`为判定区,`nil`为装备区和判定区
|
|
||||||
--- @param excludeIds? integer[] @ 排除的牌
|
|
||||||
---@return boolean
|
|
||||||
function Player:canMoveCardsInBoardTo(to, flag, excludeIds)
|
function Player:canMoveCardsInBoardTo(to, flag, excludeIds)
|
||||||
if self == to then
|
if self == to then
|
||||||
return false
|
return false
|
||||||
|
@ -1131,9 +1120,6 @@ function Player:canMoveCardsInBoardTo(to, flag, excludeIds)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
--- 获取使命技状态
|
|
||||||
---@param skillName string
|
|
||||||
---@return string? @ 存在返回`failed` or `succeed`,不存在返回`nil`
|
|
||||||
function Player:getQuestSkillState(skillName)
|
function Player:getQuestSkillState(skillName)
|
||||||
local questSkillState = self:getMark(MarkEnum.QuestSkillPreName .. skillName)
|
local questSkillState = self:getMark(MarkEnum.QuestSkillPreName .. skillName)
|
||||||
return type(questSkillState) == "string" and questSkillState or nil
|
return type(questSkillState) == "string" and questSkillState or nil
|
||||||
|
|
|
@ -90,19 +90,8 @@ function Skill:addRelatedSkill(skill)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- 确认本技能是否为装备技能。
|
--- 确认本技能是否为装备技能。
|
||||||
---@param player Player
|
|
||||||
---@return boolean
|
---@return boolean
|
||||||
function Skill:isEquipmentSkill(player)
|
function Skill:isEquipmentSkill()
|
||||||
if player then
|
|
||||||
local filterSkills = Fk:currentRoom().status_skills[FilterSkill]
|
|
||||||
for _, filter in ipairs(filterSkills) do
|
|
||||||
local result = filter:equipSkillFilter(self, player)
|
|
||||||
if result then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return self.attached_equip and type(self.attached_equip) == 'string' and self.attached_equip ~= ""
|
return self.attached_equip and type(self.attached_equip) == 'string' and self.attached_equip ~= ""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -137,11 +126,4 @@ function Skill:isSwitchSkill()
|
||||||
return self.switchSkillName and type(self.switchSkillName) == 'string' and self.switchSkillName ~= ""
|
return self.switchSkillName and type(self.switchSkillName) == 'string' and self.switchSkillName ~= ""
|
||||||
end
|
end
|
||||||
|
|
||||||
--判断技能是否为角色技能
|
|
||||||
---@param player Player
|
|
||||||
---@return boolean
|
|
||||||
function Skill:isPlayerSkill(player)
|
|
||||||
return not (self:isEquipmentSkill(player) or self.name:endsWith("&"))
|
|
||||||
end
|
|
||||||
|
|
||||||
return Skill
|
return Skill
|
||||||
|
|
|
@ -21,51 +21,47 @@ function ActiveSkill:initialize(name, frequency)
|
||||||
end
|
end
|
||||||
|
|
||||||
---------
|
---------
|
||||||
-- 注:客户端函数,AI也会调用以作主动技判断
|
-- Note: these functions are used both client and ai
|
||||||
------- {
|
------- {
|
||||||
|
|
||||||
-- 判断该技能是否可主动发动
|
--- Determine whether the skill can be used in playing phase
|
||||||
---@param player Player @ 使用者
|
---@param player Player
|
||||||
---@param card Card @ 牌
|
---@param card Card @ helper
|
||||||
---@param extra_data UseExtraData @ 额外数据
|
|
||||||
---@return bool
|
|
||||||
function ActiveSkill:canUse(player, card, extra_data)
|
function ActiveSkill:canUse(player, card, extra_data)
|
||||||
return self:isEffectable(player)
|
return self:isEffectable(player)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 判断一张牌是否可被此技能选中
|
--- Determine whether a card can be selected by this skill
|
||||||
---@param to_select integer @ 待选牌
|
--- only used in skill of players
|
||||||
---@param selected integer[] @ 已选牌
|
---@param to_select integer @ id of a card not selected
|
||||||
---@param selected_targets integer[] @ 已选目标
|
---@param selected integer[] @ ids of selected cards
|
||||||
---@return bool
|
---@param selected_targets integer[] @ ids of selected players
|
||||||
function ActiveSkill:cardFilter(to_select, selected, selected_targets)
|
function ActiveSkill:cardFilter(to_select, selected, selected_targets)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 判断一名角色是否可被此技能选中
|
--- Determine whether a target can be selected by this skill
|
||||||
---@param to_select integer @ 待选目标
|
--- only used in skill of players
|
||||||
---@param selected integer[] @ 已选目标
|
---@param to_select integer @ id of the target
|
||||||
---@param selected_cards integer[] @ 已选牌
|
---@param selected integer[] @ ids of selected targets
|
||||||
---@param card Card @ 牌
|
---@param selected_cards integer[] @ ids of selected cards
|
||||||
---@param extra_data UseExtraData @ 额外数据
|
---@param card Card @ helper
|
||||||
---@return bool
|
---@param extra_data? any @ extra_data
|
||||||
function ActiveSkill:targetFilter(to_select, selected, selected_cards, card, extra_data)
|
function ActiveSkill:targetFilter(to_select, selected, selected_cards, card, extra_data)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 判断一名角色是否可成为此技能的目标
|
--- Determine whether a target can be selected by this skill(in modifying targets)
|
||||||
---@param to_select integer @ 待选目标
|
--- only used in skill of players
|
||||||
---@param selected integer[] @ 已选目标
|
---@param to_select integer @ id of the target
|
||||||
---@param user? integer @ 使用者
|
---@param selected? integer[] @ ids of selected targets
|
||||||
---@param card? Card @ 牌
|
---@param user? integer @ id of the userdata
|
||||||
---@param distance_limited? boolean @ 是否受距离限制
|
---@param card? Card @ helper
|
||||||
---@return bool
|
---@param distance_limited? boolean @ is limited by distance
|
||||||
function ActiveSkill:modTargetFilter(to_select, selected, user, card, distance_limited)
|
function ActiveSkill:modTargetFilter(to_select, selected, user, card, distance_limited)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 获得技能的最小目标数
|
|
||||||
---@return number @ 最小目标数
|
|
||||||
function ActiveSkill:getMinTargetNum()
|
function ActiveSkill:getMinTargetNum()
|
||||||
local ret
|
local ret
|
||||||
if self.target_num then ret = self.target_num
|
if self.target_num then ret = self.target_num
|
||||||
|
@ -82,10 +78,6 @@ function ActiveSkill:getMinTargetNum()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 获得技能的最大目标数
|
|
||||||
---@param player Player @ 使用者
|
|
||||||
---@param card Card @ 牌
|
|
||||||
---@return number @ 最大目标数
|
|
||||||
function ActiveSkill:getMaxTargetNum(player, card)
|
function ActiveSkill:getMaxTargetNum(player, card)
|
||||||
local ret
|
local ret
|
||||||
if self.target_num then ret = self.target_num
|
if self.target_num then ret = self.target_num
|
||||||
|
@ -108,8 +100,6 @@ function ActiveSkill:getMaxTargetNum(player, card)
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 获得技能的最小卡牌数
|
|
||||||
---@return number @ 最小卡牌数
|
|
||||||
function ActiveSkill:getMinCardNum()
|
function ActiveSkill:getMinCardNum()
|
||||||
local ret
|
local ret
|
||||||
if self.card_num then ret = self.card_num
|
if self.card_num then ret = self.card_num
|
||||||
|
@ -126,8 +116,6 @@ function ActiveSkill:getMinCardNum()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 获得技能的最大卡牌数
|
|
||||||
---@return number @ 最大卡牌数
|
|
||||||
function ActiveSkill:getMaxCardNum()
|
function ActiveSkill:getMaxCardNum()
|
||||||
local ret
|
local ret
|
||||||
if self.card_num then ret = self.card_num
|
if self.card_num then ret = self.card_num
|
||||||
|
@ -144,11 +132,6 @@ function ActiveSkill:getMaxCardNum()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 获得技能的距离限制
|
|
||||||
---@param player Player @ 使用者
|
|
||||||
---@param card Card @ 使用卡牌
|
|
||||||
---@param to Player @ 目标
|
|
||||||
---@return number @ 距离限制
|
|
||||||
function ActiveSkill:getDistanceLimit(player, card, to)
|
function ActiveSkill:getDistanceLimit(player, card, to)
|
||||||
local ret = self.distance_limit or 0
|
local ret = self.distance_limit or 0
|
||||||
local status_skills = Fk:currentRoom().status_skills[TargetModSkill] or Util.DummyTable
|
local status_skills = Fk:currentRoom().status_skills[TargetModSkill] or Util.DummyTable
|
||||||
|
@ -160,14 +143,8 @@ function ActiveSkill:getDistanceLimit(player, card, to)
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 判断一个角色是否在技能的距离限制内
|
|
||||||
---@param player Player @ 使用者
|
|
||||||
---@param isattack bool @ 是否使用攻击距离
|
|
||||||
---@param card Card @ 使用卡牌
|
|
||||||
---@param to Player @ 目标
|
|
||||||
---@return bool
|
|
||||||
function ActiveSkill:withinDistanceLimit(player, isattack, card, to)
|
function ActiveSkill:withinDistanceLimit(player, isattack, card, to)
|
||||||
if not to or player:distanceTo(to) < 1 then return false end
|
if to and to.dead then return false end
|
||||||
local status_skills = Fk:currentRoom().status_skills[TargetModSkill] or Util.DummyTable
|
local status_skills = Fk:currentRoom().status_skills[TargetModSkill] or Util.DummyTable
|
||||||
if not card and self.name:endsWith("_skill") then
|
if not card and self.name:endsWith("_skill") then
|
||||||
card = Fk:cloneCard(self.name:sub(1, #self.name - 6))
|
card = Fk:cloneCard(self.name:sub(1, #self.name - 6))
|
||||||
|
@ -197,7 +174,7 @@ function ActiveSkill:withinDistanceLimit(player, isattack, card, to)
|
||||||
end
|
end
|
||||||
|
|
||||||
return (isattack and player:inMyAttackRange(to)) or
|
return (isattack and player:inMyAttackRange(to)) or
|
||||||
(player:distanceTo(to) <= self:getDistanceLimit(player, card, to)) or
|
(player:distanceTo(to) > 0 and player:distanceTo(to) <= self:getDistanceLimit(player, card, to)) or
|
||||||
hasMark(card, MarkEnum.BypassDistancesLimit, card_temp_suf) or
|
hasMark(card, MarkEnum.BypassDistancesLimit, card_temp_suf) or
|
||||||
hasMark(player, MarkEnum.BypassDistancesLimit, temp_suf) or
|
hasMark(player, MarkEnum.BypassDistancesLimit, temp_suf) or
|
||||||
hasMark(to, MarkEnum.BypassDistancesLimitTo, temp_suf)
|
hasMark(to, MarkEnum.BypassDistancesLimitTo, temp_suf)
|
||||||
|
@ -212,32 +189,26 @@ function ActiveSkill:withinDistanceLimit(player, isattack, card, to)
|
||||||
-- end)))
|
-- end)))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 判断一个技能是否可发动(也就是确认键是否可点击)
|
--- Determine if selected cards and targets are valid for this skill
|
||||||
-- 警告:没啥事别改
|
--- If returns true, the OK button should be enabled
|
||||||
---@param selected integer[] @ 已选目标
|
--- only used in skill of players
|
||||||
---@param selected_cards integer[] @ 已选牌
|
|
||||||
---@param player Player @ 使用者
|
-- NOTE: don't reclaim it
|
||||||
---@param card Card @ 牌
|
---@param selected integer[] @ ids of selected players
|
||||||
---@return bool
|
---@param selected_cards integer[] @ ids of selected cards
|
||||||
function ActiveSkill:feasible(selected, selected_cards, player, card)
|
function ActiveSkill:feasible(selected, selected_cards, player, card)
|
||||||
return #selected >= self:getMinTargetNum() and #selected <= self:getMaxTargetNum(player, card)
|
return #selected >= self:getMinTargetNum() and #selected <= self:getMaxTargetNum(player, card)
|
||||||
and #selected_cards >= self:getMinCardNum() and #selected_cards <= self:getMaxCardNum()
|
and #selected_cards >= self:getMinCardNum() and #selected_cards <= self:getMaxCardNum()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 使用技能时默认的烧条提示(一般会在主动使用时出现)
|
|
||||||
---@param selected_cards integer[] @ 已选牌
|
|
||||||
---@param selected_targets integer[] @ 已选目标
|
|
||||||
---@return string?
|
|
||||||
function ActiveSkill:prompt(selected_cards, selected_targets) return "" end
|
|
||||||
|
|
||||||
------- }
|
------- }
|
||||||
|
|
||||||
---@param room Room
|
---@param room Room
|
||||||
---@param cardUseEvent CardUseStruct | SkillEffectEvent
|
---@param cardUseEvent CardUseStruct
|
||||||
function ActiveSkill:onUse(room, cardUseEvent) end
|
function ActiveSkill:onUse(room, cardUseEvent) end
|
||||||
|
|
||||||
---@param room Room
|
---@param room Room
|
||||||
---@param cardUseEvent CardUseStruct | SkillEffectEvent
|
---@param cardUseEvent CardUseStruct
|
||||||
---@param finished? bool
|
---@param finished? bool
|
||||||
function ActiveSkill:onAction(room, cardUseEvent, finished) end
|
function ActiveSkill:onAction(room, cardUseEvent, finished) end
|
||||||
|
|
||||||
|
@ -254,4 +225,8 @@ function ActiveSkill:onEffect(room, cardEffectEvent) end
|
||||||
---@param cardEffectEvent CardEffectEvent | SkillEffectEvent
|
---@param cardEffectEvent CardEffectEvent | SkillEffectEvent
|
||||||
function ActiveSkill:onNullified(room, cardEffectEvent) end
|
function ActiveSkill:onNullified(room, cardEffectEvent) end
|
||||||
|
|
||||||
|
---@param selected_cards integer[] @ ids of selected cards
|
||||||
|
---@param selected_targets integer[] @ ids of selected players
|
||||||
|
function ActiveSkill:prompt(selected_cards, selected_targets) return "" end
|
||||||
|
|
||||||
return ActiveSkill
|
return ActiveSkill
|
||||||
|
|
|
@ -15,18 +15,8 @@ function AttackRangeSkill:getFixed(from)
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param from Player
|
|
||||||
---@param to Player
|
|
||||||
---@return boolean
|
|
||||||
function AttackRangeSkill:withinAttackRange(from, to)
|
function AttackRangeSkill:withinAttackRange(from, to)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param from Player
|
|
||||||
---@param to Player
|
|
||||||
---@return boolean
|
|
||||||
function AttackRangeSkill:withoutAttackRange(from, to)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
return AttackRangeSkill
|
return AttackRangeSkill
|
||||||
|
|
|
@ -17,11 +17,4 @@ function FilterSkill:viewAs(card, player)
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param skill Skill
|
|
||||||
---@param player Player
|
|
||||||
---@return string
|
|
||||||
function FilterSkill:equipSkillFilter(skill, player)
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
return FilterSkill
|
return FilterSkill
|
||||||
|
|
|
@ -14,12 +14,6 @@ function UsableSkill:initialize(name, frequency)
|
||||||
self.max_use_time = {9999, 9999, 9999, 9999}
|
self.max_use_time = {9999, 9999, 9999, 9999}
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 获得技能的最大使用次数
|
|
||||||
---@param player Player @ 使用者
|
|
||||||
---@param scope integer @ 考察时机(默认为回合)
|
|
||||||
---@param card Card @ 卡牌
|
|
||||||
---@param to Player @ 目标
|
|
||||||
---@return number @ 最大使用次数
|
|
||||||
function UsableSkill:getMaxUseTime(player, scope, card, to)
|
function UsableSkill:getMaxUseTime(player, scope, card, to)
|
||||||
scope = scope or Player.HistoryTurn
|
scope = scope or Player.HistoryTurn
|
||||||
local ret = self.max_use_time[scope]
|
local ret = self.max_use_time[scope]
|
||||||
|
@ -32,31 +26,18 @@ function UsableSkill:getMaxUseTime(player, scope, card, to)
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 判断一个角色是否在技能的次数限制内
|
|
||||||
---@param player Player @ 使用者
|
|
||||||
---@param scope integer @ 考察时机(默认为回合)
|
|
||||||
---@param card? Card @ 牌,若没有牌,则尝试制造一张虚拟牌
|
|
||||||
---@param card_name? string @ 牌名
|
|
||||||
---@param to any @ 目标
|
|
||||||
---@return bool
|
|
||||||
function UsableSkill:withinTimesLimit(player, scope, card, card_name, to)
|
function UsableSkill:withinTimesLimit(player, scope, card, card_name, to)
|
||||||
if to and to.dead then return false end
|
if to and to.dead then return false end
|
||||||
scope = scope or Player.HistoryTurn
|
scope = scope or Player.HistoryTurn
|
||||||
local status_skills = Fk:currentRoom().status_skills[TargetModSkill] or Util.DummyTable
|
local status_skills = Fk:currentRoom().status_skills[TargetModSkill] or Util.DummyTable
|
||||||
if not card then
|
if not card and self.name:endsWith("_skill") then
|
||||||
if card_name then
|
card = Fk:cloneCard(self.name:sub(1, #self.name - 6))
|
||||||
card = Fk:cloneCard(card_name)
|
|
||||||
elseif self.name:endsWith("_skill") then
|
|
||||||
card = Fk:cloneCard(self.name:sub(1, #self.name - 6))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if not card_name and card then
|
|
||||||
card_name = card.trueName
|
|
||||||
end
|
end
|
||||||
for _, skill in ipairs(status_skills) do
|
for _, skill in ipairs(status_skills) do
|
||||||
if skill:bypassTimesCheck(player, self, scope, card, to) then return true end
|
if skill:bypassTimesCheck(player, self, scope, card, to) then return true end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
card_name = card_name or card.trueName
|
||||||
local temp_suf = table.simpleClone(MarkEnum.TempMarkSuffix)
|
local temp_suf = table.simpleClone(MarkEnum.TempMarkSuffix)
|
||||||
local card_temp_suf = table.simpleClone(MarkEnum.CardTempMarkSuffix)
|
local card_temp_suf = table.simpleClone(MarkEnum.CardTempMarkSuffix)
|
||||||
|
|
||||||
|
|
|
@ -180,11 +180,11 @@ end
|
||||||
---@field public card_filter? fun(self: ActiveSkill, to_select: integer, selected: integer[], selected_targets: integer[]): 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, extra_data: any): boolean?
|
---@field public target_filter? fun(self: ActiveSkill, to_select: integer, selected: integer[], selected_cards: integer[], card: Card, extra_data: any): boolean?
|
||||||
---@field public feasible? fun(self: ActiveSkill, selected: integer[], selected_cards: integer[]): boolean?
|
---@field public feasible? fun(self: ActiveSkill, selected: integer[], selected_cards: integer[]): boolean?
|
||||||
---@field public on_use? fun(self: ActiveSkill, room: Room, cardUseEvent: CardUseStruct | SkillEffectEvent): boolean?
|
---@field public on_use? fun(self: ActiveSkill, room: Room, cardUseEvent: CardUseStruct): boolean?
|
||||||
---@field public on_action? fun(self: ActiveSkill, room: Room, cardUseEvent: CardUseStruct | SkillEffectEvent, finished: boolean): boolean?
|
---@field public on_action? fun(self: ActiveSkill, room: Room, cardUseEvent: CardUseStruct, finished: boolean): boolean?
|
||||||
---@field public about_to_effect? fun(self: ActiveSkill, room: Room, cardEffectEvent: CardEffectEvent | SkillEffectEvent): 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 | SkillEffectEvent): boolean?
|
---@field public on_effect? fun(self: ActiveSkill, room: Room, cardEffectEvent: CardEffectEvent): boolean?
|
||||||
---@field public on_nullified? fun(self: ActiveSkill, room: Room, cardEffectEvent: CardEffectEvent | SkillEffectEvent): 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 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
|
---@field public prompt? string|fun(self: ActiveSkill, selected_cards: integer[], selected_targets: integer[]): string
|
||||||
---@field public interaction any
|
---@field public interaction any
|
||||||
|
@ -335,14 +335,12 @@ end
|
||||||
---@field public correct_func? fun(self: AttackRangeSkill, from: Player, to: Player): number?
|
---@field public correct_func? fun(self: AttackRangeSkill, from: Player, to: Player): number?
|
||||||
---@field public fixed_func? fun(self: AttackRangeSkill, player: Player): number?
|
---@field public fixed_func? fun(self: AttackRangeSkill, player: Player): number?
|
||||||
---@field public within_func? fun(self: AttackRangeSkill, from: Player, to: Player): boolean?
|
---@field public within_func? fun(self: AttackRangeSkill, from: Player, to: Player): boolean?
|
||||||
---@field public without_func? fun(self: AttackRangeSkill, from: Player, to: Player): boolean?
|
|
||||||
|
|
||||||
---@param spec AttackRangeSpec
|
---@param spec AttackRangeSpec
|
||||||
---@return AttackRangeSkill
|
---@return AttackRangeSkill
|
||||||
function fk.CreateAttackRangeSkill(spec)
|
function fk.CreateAttackRangeSkill(spec)
|
||||||
assert(type(spec.name) == "string")
|
assert(type(spec.name) == "string")
|
||||||
assert(type(spec.correct_func) == "function" or type(spec.fixed_func) == "function" or
|
assert(type(spec.correct_func) == "function" or type(spec.fixed_func) == "function" or type(spec.within_func) == "function")
|
||||||
type(spec.within_func) == "function" or type(spec.without_func) == "function")
|
|
||||||
|
|
||||||
local skill = AttackRangeSkill:new(spec.name)
|
local skill = AttackRangeSkill:new(spec.name)
|
||||||
readStatusSpecToSkill(skill, spec)
|
readStatusSpecToSkill(skill, spec)
|
||||||
|
@ -355,9 +353,6 @@ function fk.CreateAttackRangeSkill(spec)
|
||||||
if spec.within_func then
|
if spec.within_func then
|
||||||
skill.withinAttackRange = spec.within_func
|
skill.withinAttackRange = spec.within_func
|
||||||
end
|
end
|
||||||
if spec.without_func then
|
|
||||||
skill.withoutAttackRange = spec.without_func
|
|
||||||
end
|
|
||||||
|
|
||||||
return skill
|
return skill
|
||||||
end
|
end
|
||||||
|
@ -422,7 +417,6 @@ end
|
||||||
---@class FilterSpec: StatusSkillSpec
|
---@class FilterSpec: StatusSkillSpec
|
||||||
---@field public card_filter? fun(self: FilterSkill, card: Card, player: Player, isJudgeEvent: boolean): boolean?
|
---@field public card_filter? fun(self: FilterSkill, card: Card, player: Player, isJudgeEvent: boolean): boolean?
|
||||||
---@field public view_as? fun(self: FilterSkill, card: Card, player: Player): Card?
|
---@field public view_as? fun(self: FilterSkill, card: Card, player: Player): Card?
|
||||||
---@field public equip_skill_filter? fun(self: FilterSkill, skill: Skill, player: Player): string?
|
|
||||||
|
|
||||||
---@param spec FilterSpec
|
---@param spec FilterSpec
|
||||||
---@return FilterSkill
|
---@return FilterSkill
|
||||||
|
@ -433,7 +427,6 @@ function fk.CreateFilterSkill(spec)
|
||||||
readStatusSpecToSkill(skill, spec)
|
readStatusSpecToSkill(skill, spec)
|
||||||
skill.cardFilter = spec.card_filter
|
skill.cardFilter = spec.card_filter
|
||||||
skill.viewAs = spec.view_as
|
skill.viewAs = spec.view_as
|
||||||
skill.equipSkillFilter = spec.equip_skill_filter
|
|
||||||
|
|
||||||
return skill
|
return skill
|
||||||
end
|
end
|
||||||
|
@ -533,20 +526,7 @@ function fk.CreateDelayedTrickCard(spec)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function readCardSpecToEquip(card, spec)
|
local function readCardSpecToEquip(card, spec)
|
||||||
if spec.equip_skill then
|
card.equip_skill = spec.equip_skill
|
||||||
if spec.equip_skill.class and spec.equip_skill:isInstanceOf(Skill) then
|
|
||||||
card.equip_skill = spec.equip_skill
|
|
||||||
card.equip_skills = { spec.equip_skill }
|
|
||||||
else
|
|
||||||
card.equip_skill = spec.equip_skill[1]
|
|
||||||
card.equip_skills = spec.equip_skill
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if spec.dynamic_equip_skills then
|
|
||||||
assert(type(spec.dynamic_equip_skills) == "function")
|
|
||||||
card.dynamicEquipSkills = spec.dynamic_equip_skills
|
|
||||||
end
|
|
||||||
|
|
||||||
if spec.on_install then card.onInstall = spec.on_install end
|
if spec.on_install then card.onInstall = spec.on_install end
|
||||||
if spec.on_uninstall then card.onUninstall = spec.on_uninstall end
|
if spec.on_uninstall then card.onUninstall = spec.on_uninstall end
|
||||||
|
@ -563,11 +543,6 @@ function fk.CreateWeapon(spec)
|
||||||
local card = Weapon:new(spec.name, spec.suit, spec.number, spec.attack_range)
|
local card = Weapon:new(spec.name, spec.suit, spec.number, spec.attack_range)
|
||||||
readCardSpecToCard(card, spec)
|
readCardSpecToCard(card, spec)
|
||||||
readCardSpecToEquip(card, spec)
|
readCardSpecToEquip(card, spec)
|
||||||
if spec.dynamic_attack_range then
|
|
||||||
assert(type(spec.dynamic_attack_range) == "function")
|
|
||||||
card.dynamicAttackRange = spec.dynamic_attack_range
|
|
||||||
end
|
|
||||||
|
|
||||||
return card
|
return card
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -635,10 +610,6 @@ function fk.CreateGameMode(spec)
|
||||||
assert(type(spec.is_counted) == "function")
|
assert(type(spec.is_counted) == "function")
|
||||||
ret.countInFunc = spec.is_counted
|
ret.countInFunc = spec.is_counted
|
||||||
end
|
end
|
||||||
if spec.get_adjusted then
|
|
||||||
assert(type(spec.get_adjusted) == "function")
|
|
||||||
ret.getAdjustedProperty = spec.get_adjusted
|
|
||||||
end
|
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
-- 向Lua虚拟机中加载库、游戏中的类,以及加载Mod等等。
|
-- 向Lua虚拟机中加载库、游戏中的类,以及加载Mod等等。
|
||||||
|
|
||||||
-- 加载第三方库
|
-- 加载第三方库
|
||||||
package.path = "./?.lua;./?/init.lua;./lua/lib/?.lua;./lua/?.lua"
|
package.path = package.path .. ";./lua/lib/?.lua"
|
||||||
|
.. ";./lua/?.lua"
|
||||||
|
|
||||||
-- middleclass: 轻量级的面向对象库
|
-- middleclass: 轻量级的面向对象库
|
||||||
class = require "middleclass"
|
class = require "middleclass"
|
||||||
|
@ -61,9 +62,7 @@ UI = require "ui-util"
|
||||||
-- 读取配置文件。
|
-- 读取配置文件。
|
||||||
-- 因为io马上就要被禁用了,所以赶紧先在这里读取配置文件。
|
-- 因为io马上就要被禁用了,所以赶紧先在这里读取配置文件。
|
||||||
local function loadConf()
|
local function loadConf()
|
||||||
local new_core = FileIO.pwd():endsWith("packages/freekill-core")
|
local cfg = io.open("freekill.client.config.json")
|
||||||
|
|
||||||
local cfg = io.open((new_core and "../../" or "") .. "freekill.client.config.json")
|
|
||||||
local ret
|
local ret
|
||||||
if cfg == nil then
|
if cfg == nil then
|
||||||
ret = {
|
ret = {
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
-- SPDX-License-Identifier: GPL-3.0-or-later
|
-- SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
---@class GameEvent.Dying : GameEvent
|
GameEvent.functions[GameEvent.Dying] = function(self)
|
||||||
local Dying = GameEvent:subclass("GameEvent.Dying")
|
|
||||||
function Dying:main()
|
|
||||||
local dyingStruct = table.unpack(self.data)
|
local dyingStruct = table.unpack(self.data)
|
||||||
local room = self.room
|
local room = self.room
|
||||||
local logic = room.logic
|
local logic = room.logic
|
||||||
|
@ -29,7 +27,7 @@ function Dying:main()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Dying:exit()
|
GameEvent.exit_funcs[GameEvent.Dying] = function(self)
|
||||||
local room = self.room
|
local room = self.room
|
||||||
local logic = room.logic
|
local logic = room.logic
|
||||||
local dyingStruct = self.data[1]
|
local dyingStruct = self.data[1]
|
||||||
|
@ -43,9 +41,7 @@ function Dying:exit()
|
||||||
logic:trigger(fk.AfterDying, dyingPlayer, dyingStruct, self.interrupted)
|
logic:trigger(fk.AfterDying, dyingPlayer, dyingStruct, self.interrupted)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class GameEvent.Death : GameEvent
|
GameEvent.prepare_funcs[GameEvent.Death] = function(self)
|
||||||
local Death = GameEvent:subclass("GameEvent.Death")
|
|
||||||
function Death:prepare()
|
|
||||||
local deathStruct = table.unpack(self.data)
|
local deathStruct = table.unpack(self.data)
|
||||||
local room = self.room
|
local room = self.room
|
||||||
local victim = room:getPlayerById(deathStruct.who)
|
local victim = room:getPlayerById(deathStruct.who)
|
||||||
|
@ -54,7 +50,7 @@ function Death:prepare()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Death:main()
|
GameEvent.functions[GameEvent.Death] = function(self)
|
||||||
local deathStruct = table.unpack(self.data)
|
local deathStruct = table.unpack(self.data)
|
||||||
local room = self.room
|
local room = self.room
|
||||||
local victim = room:getPlayerById(deathStruct.who)
|
local victim = room:getPlayerById(deathStruct.who)
|
||||||
|
@ -103,9 +99,7 @@ function Death:main()
|
||||||
logic:trigger(fk.Deathed, victim, deathStruct)
|
logic:trigger(fk.Deathed, victim, deathStruct)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class GameEvent.Revive : GameEvent
|
GameEvent.functions[GameEvent.Revive] = function(self)
|
||||||
local Revive = GameEvent:subclass("GameEvent.Revive")
|
|
||||||
function Revive:main()
|
|
||||||
local room = self.room
|
local room = self.room
|
||||||
local player, sendLog, reason = table.unpack(self.data)
|
local player, sendLog, reason = table.unpack(self.data)
|
||||||
|
|
||||||
|
@ -124,5 +118,3 @@ function Revive:main()
|
||||||
reason = reason or ""
|
reason = reason or ""
|
||||||
room.logic:trigger(fk.AfterPlayerRevived, player, { reason = reason })
|
room.logic:trigger(fk.AfterPlayerRevived, player, { reason = reason })
|
||||||
end
|
end
|
||||||
|
|
||||||
return { Dying, Death, Revive }
|
|
||||||
|
|
|
@ -47,9 +47,7 @@ local function discardInit(room, player)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class GameEvent.DrawInitial : GameEvent
|
GameEvent.functions[GameEvent.DrawInitial] = function(self)
|
||||||
local DrawInitial = GameEvent:subclass("GameEvent.DrawInitial")
|
|
||||||
function DrawInitial:main()
|
|
||||||
local room = self.room
|
local room = self.room
|
||||||
|
|
||||||
local luck_data = {
|
local luck_data = {
|
||||||
|
@ -83,7 +81,6 @@ function DrawInitial:main()
|
||||||
room:setTag("LuckCardData", luck_data)
|
room:setTag("LuckCardData", luck_data)
|
||||||
room:notifyMoveFocus(room.alive_players, "AskForLuckCard")
|
room:notifyMoveFocus(room.alive_players, "AskForLuckCard")
|
||||||
room:doBroadcastNotify("AskForLuckCard", room.settings.luckTime or 4)
|
room:doBroadcastNotify("AskForLuckCard", room.settings.luckTime or 4)
|
||||||
room.room:setRequestTimer(room.timeout * 1000 + 1000)
|
|
||||||
|
|
||||||
local remainTime = room.timeout + 1
|
local remainTime = room.timeout + 1
|
||||||
local currentTime = os.time()
|
local currentTime = os.time()
|
||||||
|
@ -126,8 +123,6 @@ function DrawInitial:main()
|
||||||
coroutine.yield("__handleRequest", (remainTime - elapsed) * 1000)
|
coroutine.yield("__handleRequest", (remainTime - elapsed) * 1000)
|
||||||
end
|
end
|
||||||
|
|
||||||
room.room:destroyRequestTimer()
|
|
||||||
|
|
||||||
for _, player in ipairs(room.alive_players) do
|
for _, player in ipairs(room.alive_players) do
|
||||||
local draw_data = luck_data[player.id]
|
local draw_data = luck_data[player.id]
|
||||||
draw_data.luckTime = nil
|
draw_data.luckTime = nil
|
||||||
|
@ -137,23 +132,10 @@ function DrawInitial:main()
|
||||||
room:removeTag("LuckCardData")
|
room:removeTag("LuckCardData")
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class GameEvent.Round : GameEvent
|
GameEvent.functions[GameEvent.Round] = function(self)
|
||||||
local Round = GameEvent:subclass("GameEvent.Round")
|
|
||||||
|
|
||||||
function Round:action()
|
|
||||||
local room = self.room
|
|
||||||
local p
|
|
||||||
repeat
|
|
||||||
p = room.current
|
|
||||||
GameEvent.Turn:create(p):exec()
|
|
||||||
if room.game_finished then break end
|
|
||||||
room.current = room.current:getNextAlive(true, nil, true)
|
|
||||||
until p.seat >= p:getNextAlive(true, nil, true).seat
|
|
||||||
end
|
|
||||||
|
|
||||||
function Round:main()
|
|
||||||
local room = self.room
|
local room = self.room
|
||||||
local logic = room.logic
|
local logic = room.logic
|
||||||
|
local p
|
||||||
|
|
||||||
local isFirstRound = room:getTag("FirstRound")
|
local isFirstRound = room:getTag("FirstRound")
|
||||||
if isFirstRound then
|
if isFirstRound then
|
||||||
|
@ -178,11 +160,18 @@ function Round:main()
|
||||||
end
|
end
|
||||||
|
|
||||||
logic:trigger(fk.RoundStart, room.current)
|
logic:trigger(fk.RoundStart, room.current)
|
||||||
self:action()
|
|
||||||
|
repeat
|
||||||
|
p = room.current
|
||||||
|
GameEvent(GameEvent.Turn, p):exec()
|
||||||
|
if room.game_finished then break end
|
||||||
|
room.current = room.current:getNextAlive(true, nil, true)
|
||||||
|
until p.seat >= p:getNextAlive(true, nil, true).seat
|
||||||
|
|
||||||
logic:trigger(fk.RoundEnd, p)
|
logic:trigger(fk.RoundEnd, p)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Round:clear()
|
GameEvent.cleaners[GameEvent.Round] = function(self)
|
||||||
local room = self.room
|
local room = self.room
|
||||||
|
|
||||||
for _, p in ipairs(room.players) do
|
for _, p in ipairs(room.players) do
|
||||||
|
@ -209,9 +198,7 @@ function Round:clear()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class GameEvent.Turn : GameEvent
|
GameEvent.prepare_funcs[GameEvent.Turn] = function(self)
|
||||||
local Turn = GameEvent:subclass("GameEvent.Turn")
|
|
||||||
function Turn:prepare()
|
|
||||||
local room = self.room
|
local room = self.room
|
||||||
local logic = room.logic
|
local logic = room.logic
|
||||||
local player = room.current
|
local player = room.current
|
||||||
|
@ -237,7 +224,7 @@ function Turn:prepare()
|
||||||
return logic:trigger(fk.BeforeTurnStart, player)
|
return logic:trigger(fk.BeforeTurnStart, player)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Turn:main()
|
GameEvent.functions[GameEvent.Turn] = function(self)
|
||||||
local room = self.room
|
local room = self.room
|
||||||
room.current.phase = Player.PhaseNone
|
room.current.phase = Player.PhaseNone
|
||||||
room.logic:trigger(fk.TurnStart, room.current)
|
room.logic:trigger(fk.TurnStart, room.current)
|
||||||
|
@ -245,7 +232,7 @@ function Turn:main()
|
||||||
room.current:play()
|
room.current:play()
|
||||||
end
|
end
|
||||||
|
|
||||||
function Turn:clear()
|
GameEvent.cleaners[GameEvent.Turn] = function(self)
|
||||||
local room = self.room
|
local room = self.room
|
||||||
|
|
||||||
local current = room.current
|
local current = room.current
|
||||||
|
@ -293,9 +280,7 @@ function Turn:clear()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class GameEvent.Phase : GameEvent
|
GameEvent.functions[GameEvent.Phase] = function(self)
|
||||||
local Phase = GameEvent:subclass("GameEvent.Phase")
|
|
||||||
function Phase:main()
|
|
||||||
local room = self.room
|
local room = self.room
|
||||||
local logic = room.logic
|
local logic = room.logic
|
||||||
|
|
||||||
|
@ -388,7 +373,7 @@ function Phase:main()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Phase:clear()
|
GameEvent.cleaners[GameEvent.Phase] = function(self)
|
||||||
local room = self.room
|
local room = self.room
|
||||||
local player = self.data[1]
|
local player = self.data[1]
|
||||||
local logic = room.logic
|
local logic = room.logic
|
||||||
|
@ -423,5 +408,3 @@ function Phase:clear()
|
||||||
room:broadcastProperty(p, "MaxCards")
|
room:broadcastProperty(p, "MaxCards")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return { DrawInitial, Round, Turn, Phase }
|
|
||||||
|
|
|
@ -31,9 +31,7 @@ local function sendDamageLog(room, damageStruct)
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class GameEvent.ChangeHp : GameEvent
|
GameEvent.functions[GameEvent.ChangeHp] = function(self)
|
||||||
local ChangeHp = GameEvent:subclass("GameEvent.ChangeHp")
|
|
||||||
function ChangeHp:main()
|
|
||||||
local player, num, reason, skillName, damageStruct = table.unpack(self.data)
|
local player, num, reason, skillName, damageStruct = table.unpack(self.data)
|
||||||
local room = self.room
|
local room = self.room
|
||||||
local logic = room.logic
|
local logic = room.logic
|
||||||
|
@ -114,21 +112,17 @@ function ChangeHp:main()
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class GameEvent.Damage : GameEvent
|
GameEvent.functions[GameEvent.Damage] = function(self)
|
||||||
local Damage = GameEvent:subclass("GameEvent.Damage")
|
|
||||||
function Damage:main()
|
|
||||||
local damageStruct = table.unpack(self.data)
|
local damageStruct = table.unpack(self.data)
|
||||||
local room = self.room
|
local room = self.room
|
||||||
local logic = room.logic
|
local logic = room.logic
|
||||||
|
|
||||||
if not damageStruct.chain and logic:damageByCardEffect(false) then
|
if not damageStruct.chain and logic:damageByCardEffect(not not damageStruct.from) then
|
||||||
local cardEffectData = logic:getCurrentEvent():findParent(GameEvent.CardEffect)
|
local cardEffectData = logic:getCurrentEvent():findParent(GameEvent.CardEffect)
|
||||||
if cardEffectData then
|
if cardEffectData then
|
||||||
local cardEffectEvent = cardEffectData.data[1]
|
local cardEffectEvent = cardEffectData.data[1]
|
||||||
damageStruct.damage = damageStruct.damage + (cardEffectEvent.additionalDamage or 0)
|
damageStruct.damage = damageStruct.damage + (cardEffectEvent.additionalDamage or 0)
|
||||||
if damageStruct.from and cardEffectEvent.from == damageStruct.from.id then
|
damageStruct.by_user = true
|
||||||
damageStruct.by_user = true
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -143,14 +137,12 @@ function Damage:main()
|
||||||
|
|
||||||
assert(damageStruct.to:isInstanceOf(ServerPlayer))
|
assert(damageStruct.to:isInstanceOf(ServerPlayer))
|
||||||
|
|
||||||
local stages = {}
|
local stages = {
|
||||||
|
{fk.PreDamage, "from"},
|
||||||
|
}
|
||||||
|
|
||||||
if not damageStruct.isVirtualDMG then
|
if not damageStruct.isVirtualDMG then
|
||||||
stages = {
|
table.insertTable(stages, { { fk.DamageCaused, "from" }, { fk.DamageInflicted, "to" } })
|
||||||
{ fk.PreDamage, "from"},
|
|
||||||
{ fk.DamageCaused, "from" },
|
|
||||||
{ fk.DamageInflicted, "to" },
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, struct in ipairs(stages) do
|
for _, struct in ipairs(stages) do
|
||||||
|
@ -206,7 +198,7 @@ function Damage:main()
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
function Damage:exit()
|
GameEvent.exit_funcs[GameEvent.Damage] = function(self)
|
||||||
local room = self.room
|
local room = self.room
|
||||||
local logic = room.logic
|
local logic = room.logic
|
||||||
local damageStruct = self.data[1]
|
local damageStruct = self.data[1]
|
||||||
|
@ -238,9 +230,7 @@ function Damage:exit()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class GameEvent.LoseHp : GameEvent
|
GameEvent.functions[GameEvent.LoseHp] = function(self)
|
||||||
local LoseHp = GameEvent:subclass("GameEvent.LoseHp")
|
|
||||||
function LoseHp:main()
|
|
||||||
local player, num, skillName = table.unpack(self.data)
|
local player, num, skillName = table.unpack(self.data)
|
||||||
local room = self.room
|
local room = self.room
|
||||||
local logic = room.logic
|
local logic = room.logic
|
||||||
|
@ -268,9 +258,7 @@ function LoseHp:main()
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class GameEvent.Recover : GameEvent
|
GameEvent.functions[GameEvent.Recover] = function(self)
|
||||||
local Recover = GameEvent:subclass("GameEvent.Recover")
|
|
||||||
function Recover:main()
|
|
||||||
local recoverStruct = table.unpack(self.data)
|
local recoverStruct = table.unpack(self.data)
|
||||||
local room = self.room
|
local room = self.room
|
||||||
local logic = room.logic
|
local logic = room.logic
|
||||||
|
@ -301,9 +289,7 @@ function Recover:main()
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class GameEvent.ChangeMaxHp : GameEvent
|
GameEvent.functions[GameEvent.ChangeMaxHp] = function(self)
|
||||||
local ChangeMaxHp = GameEvent:subclass("GameEvent.ChangeMaxHp")
|
|
||||||
function ChangeMaxHp:main()
|
|
||||||
local player, num = table.unpack(self.data)
|
local player, num = table.unpack(self.data)
|
||||||
local room = self.room
|
local room = self.room
|
||||||
|
|
||||||
|
@ -358,5 +344,3 @@ function ChangeMaxHp:main()
|
||||||
room.logic:trigger(fk.MaxHpChanged, player, { num = num })
|
room.logic:trigger(fk.MaxHpChanged, player, { num = num })
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
return { ChangeHp, Damage, LoseHp, Recover, ChangeMaxHp }
|
|
||||||
|
|
|
@ -5,46 +5,51 @@
|
||||||
-- 某类事件对应的结束事件,其id刚好就是那个事件的相反数
|
-- 某类事件对应的结束事件,其id刚好就是那个事件的相反数
|
||||||
-- GameEvent.EventFinish = -1
|
-- GameEvent.EventFinish = -1
|
||||||
|
|
||||||
local tmp
|
GameEvent.Game = 0
|
||||||
tmp = require "server.events.misc"
|
|
||||||
GameEvent.Game = tmp[1]
|
|
||||||
GameEvent.ChangeProperty = tmp[2]
|
|
||||||
GameEvent.ClearEvent = tmp[3]
|
|
||||||
|
|
||||||
tmp = require "server.events.hp"
|
GameEvent.ChangeHp = 1
|
||||||
GameEvent.ChangeHp = tmp[1]
|
GameEvent.Damage = 2
|
||||||
GameEvent.Damage = tmp[2]
|
GameEvent.LoseHp = 3
|
||||||
GameEvent.LoseHp = tmp[3]
|
GameEvent.Recover = 4
|
||||||
GameEvent.Recover = tmp[4]
|
GameEvent.ChangeMaxHp = 5
|
||||||
GameEvent.ChangeMaxHp = tmp[5]
|
dofile "lua/server/events/hp.lua"
|
||||||
|
|
||||||
tmp = require "server.events.death"
|
GameEvent.Dying = 6
|
||||||
GameEvent.Dying = tmp[1]
|
GameEvent.Death = 7
|
||||||
GameEvent.Death = tmp[2]
|
GameEvent.Revive = 22
|
||||||
GameEvent.Revive = tmp[3]
|
dofile "lua/server/events/death.lua"
|
||||||
|
|
||||||
tmp = require "server.events.movecard"
|
GameEvent.MoveCards = 8
|
||||||
GameEvent.MoveCards = tmp
|
dofile "lua/server/events/movecard.lua"
|
||||||
|
|
||||||
tmp = require "server.events.usecard"
|
GameEvent.UseCard = 9
|
||||||
GameEvent.UseCard = tmp[1]
|
GameEvent.RespondCard = 10
|
||||||
GameEvent.RespondCard = tmp[2]
|
GameEvent.CardEffect = 20
|
||||||
GameEvent.CardEffect = tmp[3]
|
dofile "lua/server/events/usecard.lua"
|
||||||
|
|
||||||
tmp = require "server.events.skill"
|
GameEvent.SkillEffect = 11
|
||||||
GameEvent.SkillEffect = tmp
|
-- GameEvent.AddSkill = 12
|
||||||
|
-- GameEvent.LoseSkill = 13
|
||||||
|
dofile "lua/server/events/skill.lua"
|
||||||
|
|
||||||
tmp = require "server.events.judge"
|
GameEvent.Judge = 14
|
||||||
GameEvent.Judge = tmp
|
dofile "lua/server/events/judge.lua"
|
||||||
|
|
||||||
tmp = require "server.events.gameflow"
|
GameEvent.DrawInitial = 15
|
||||||
GameEvent.DrawInitial = tmp[1]
|
GameEvent.Round = 16
|
||||||
GameEvent.Round = tmp[2]
|
GameEvent.Turn = 17
|
||||||
GameEvent.Turn = tmp[3]
|
GameEvent.Phase = 18
|
||||||
GameEvent.Phase = tmp[4]
|
dofile "lua/server/events/gameflow.lua"
|
||||||
|
|
||||||
tmp = require "server.events.pindian"
|
GameEvent.Pindian = 19
|
||||||
GameEvent.Pindian = tmp
|
dofile "lua/server/events/pindian.lua"
|
||||||
|
|
||||||
|
-- 20 = CardEffect
|
||||||
|
GameEvent.ChangeProperty = 21
|
||||||
|
|
||||||
|
-- 新的clear函数专用
|
||||||
|
GameEvent.ClearEvent = 9999
|
||||||
|
dofile "lua/server/events/misc.lua"
|
||||||
|
|
||||||
for _, l in ipairs(Fk._custom_events) do
|
for _, l in ipairs(Fk._custom_events) do
|
||||||
local name, p, m, c, e = l.name, l.p, l.m, l.c, l.e
|
local name, p, m, c, e = l.name, l.p, l.m, l.c, l.e
|
||||||
|
@ -53,3 +58,37 @@ for _, l in ipairs(Fk._custom_events) do
|
||||||
GameEvent.cleaners[name] = c
|
GameEvent.cleaners[name] = c
|
||||||
GameEvent.exit_funcs[name] = e
|
GameEvent.exit_funcs[name] = e
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local eventTranslations = {
|
||||||
|
[GameEvent.Game] = "GameEvent.Game",
|
||||||
|
|
||||||
|
[GameEvent.ChangeHp] = "GameEvent.ChangeHp",
|
||||||
|
[GameEvent.Damage] = "GameEvent.Damage",
|
||||||
|
[GameEvent.LoseHp] = "GameEvent.LoseHp",
|
||||||
|
[GameEvent.Recover] = "GameEvent.Recover",
|
||||||
|
[GameEvent.ChangeMaxHp] = "GameEvent.ChangeMaxHp",
|
||||||
|
[GameEvent.Dying] = "GameEvent.Dying",
|
||||||
|
[GameEvent.Death] = "GameEvent.Death",
|
||||||
|
[GameEvent.Revive] = "GameEvent.Revive",
|
||||||
|
[GameEvent.MoveCards] = "GameEvent.MoveCards",
|
||||||
|
[GameEvent.UseCard] = "GameEvent.UseCard",
|
||||||
|
[GameEvent.RespondCard] = "GameEvent.RespondCard",
|
||||||
|
[GameEvent.CardEffect] = "GameEvent.CardEffect",
|
||||||
|
[GameEvent.SkillEffect] = "GameEvent.SkillEffect",
|
||||||
|
[GameEvent.Judge] = "GameEvent.Judge",
|
||||||
|
[GameEvent.DrawInitial] = "GameEvent.DrawInitial",
|
||||||
|
[GameEvent.Round] = "GameEvent.Round",
|
||||||
|
[GameEvent.Turn] = "GameEvent.Turn",
|
||||||
|
[GameEvent.Phase] = "GameEvent.Phase",
|
||||||
|
[GameEvent.Pindian] = "GameEvent.Pindian",
|
||||||
|
|
||||||
|
[GameEvent.ChangeProperty] = "GameEvent.ChangeProperty",
|
||||||
|
|
||||||
|
[GameEvent.ClearEvent] = "GameEvent.ClearEvent",
|
||||||
|
}
|
||||||
|
|
||||||
|
function GameEvent.static:translate(id)
|
||||||
|
local ret = eventTranslations[id]
|
||||||
|
if not ret then ret = id end
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
-- SPDX-License-Identifier: GPL-3.0-or-later
|
-- SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
---@class GameEvent.Judge : GameEvent
|
GameEvent.functions[GameEvent.Judge] = function(self)
|
||||||
local Judge = GameEvent:subclass("GameEvent.Judge")
|
|
||||||
function Judge:main()
|
|
||||||
local data = table.unpack(self.data)
|
local data = table.unpack(self.data)
|
||||||
local room = self.room
|
local room = self.room
|
||||||
local logic = room.logic
|
local logic = room.logic
|
||||||
|
@ -55,7 +53,7 @@ function Judge:main()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Judge:clear()
|
GameEvent.cleaners[GameEvent.Judge] = function(self)
|
||||||
local data = table.unpack(self.data)
|
local data = table.unpack(self.data)
|
||||||
local room = self.room
|
local room = self.room
|
||||||
if (self.interrupted or not data.skipDrop) and room:getCardArea(data.card.id) == Card.Processing then
|
if (self.interrupted or not data.skipDrop) and room:getCardArea(data.card.id) == Card.Processing then
|
||||||
|
@ -73,5 +71,3 @@ function Judge:clear()
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
return Judge
|
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
-- SPDX-License-Identifier: GPL-3.0-or-later
|
-- SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
---@class GameEvent.Game : GameEvent
|
GameEvent.functions[GameEvent.Game] = function(self)
|
||||||
local Game = GameEvent:subclass("GameEvent.Game")
|
|
||||||
function Game:main()
|
|
||||||
self.room.logic:run()
|
self.room.logic:run()
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class GameEvent.ChangeProperty : GameEvent
|
GameEvent.functions[GameEvent.ChangeProperty] = function(self)
|
||||||
local ChangeProperty = GameEvent:subclass("GameEvent.Game")
|
|
||||||
function ChangeProperty:main()
|
|
||||||
local data = table.unpack(self.data)
|
local data = table.unpack(self.data)
|
||||||
local room = self.room
|
local room = self.room
|
||||||
local player = data.from
|
local player = data.from
|
||||||
|
@ -129,14 +125,12 @@ function ChangeProperty:main()
|
||||||
logic:trigger(fk.AfterPropertyChange, player, data)
|
logic:trigger(fk.AfterPropertyChange, player, data)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class GameEvent.ClearEvent : GameEvent
|
GameEvent.functions[GameEvent.ClearEvent] = function(self)
|
||||||
local ClearEvent = GameEvent:subclass("GameEvent.ClearEvent")
|
|
||||||
function ClearEvent:main()
|
|
||||||
local event = self.data[1]
|
local event = self.data[1]
|
||||||
local logic = self.room.logic
|
local logic = self.room.logic
|
||||||
-- 不可中断
|
-- 不可中断
|
||||||
Pcall(event.clear, event)
|
Pcall(event.clear_func, event)
|
||||||
for _, f in ipairs(event.extra_clear) do
|
for _, f in ipairs(event.extra_clear_funcs) do
|
||||||
if type(f) == "function" then Pcall(f, event) end
|
if type(f) == "function" then Pcall(f, event) end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -153,5 +147,3 @@ function ClearEvent:main()
|
||||||
logic.game_event_stack:pop()
|
logic.game_event_stack:pop()
|
||||||
logic.cleaner_stack:pop()
|
logic.cleaner_stack:pop()
|
||||||
end
|
end
|
||||||
|
|
||||||
return { Game, ChangeProperty, ClearEvent }
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
-- SPDX-License-Identifier: GPL-3.0-or-later
|
-- SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
---@class GameEvent.MoveCards : GameEvent
|
GameEvent.functions[GameEvent.MoveCards] = function(self)
|
||||||
local MoveCards = GameEvent:subclass("GameEvent.MoveCards")
|
|
||||||
function MoveCards:main()
|
|
||||||
local args = self.data
|
local args = self.data
|
||||||
local room = self.room
|
local room = self.room
|
||||||
---@type CardsMoveStruct[]
|
---@type CardsMoveStruct[]
|
||||||
|
@ -59,7 +57,6 @@ function MoveCards:main()
|
||||||
specialVisible = cardsMoveInfo.specialVisible,
|
specialVisible = cardsMoveInfo.specialVisible,
|
||||||
drawPilePosition = cardsMoveInfo.drawPilePosition,
|
drawPilePosition = cardsMoveInfo.drawPilePosition,
|
||||||
moveMark = cardsMoveInfo.moveMark,
|
moveMark = cardsMoveInfo.moveMark,
|
||||||
visiblePlayers = cardsMoveInfo.visiblePlayers,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
table.insert(cardsMoveStructs, cardsMoveStruct)
|
table.insert(cardsMoveStructs, cardsMoveStruct)
|
||||||
|
@ -72,11 +69,10 @@ function MoveCards:main()
|
||||||
from = cardsMoveInfo.from,
|
from = cardsMoveInfo.from,
|
||||||
toArea = Card.DiscardPile,
|
toArea = Card.DiscardPile,
|
||||||
moveReason = fk.ReasonPutIntoDiscardPile,
|
moveReason = fk.ReasonPutIntoDiscardPile,
|
||||||
moveVisible = true,
|
specialName = cardsMoveInfo.specialName,
|
||||||
--specialName = cardsMoveInfo.specialName,
|
specialVisible = cardsMoveInfo.specialVisible,
|
||||||
--specialVisible = cardsMoveInfo.specialVisible,
|
drawPilePosition = cardsMoveInfo.drawPilePosition,
|
||||||
--drawPilePosition = cardsMoveInfo.drawPilePosition,
|
moveMark = cardsMoveInfo.moveMark,
|
||||||
--moveMark = cardsMoveInfo.moveMark,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
table.insert(cardsMoveStructs, cardsMoveStruct)
|
table.insert(cardsMoveStructs, cardsMoveStruct)
|
||||||
|
@ -163,7 +159,7 @@ function MoveCards:main()
|
||||||
realFromArea == Player.Equip and
|
realFromArea == Player.Equip and
|
||||||
beforeCard.type == Card.TypeEquip and
|
beforeCard.type == Card.TypeEquip and
|
||||||
data.from ~= nil and
|
data.from ~= nil and
|
||||||
#beforeCard:getEquipSkills(room:getPlayerById(data.from)) > 0
|
beforeCard.equip_skill
|
||||||
then
|
then
|
||||||
beforeCard:onUninstall(room, room:getPlayerById(data.from))
|
beforeCard:onUninstall(room, room:getPlayerById(data.from))
|
||||||
end
|
end
|
||||||
|
@ -187,20 +183,15 @@ function MoveCards:main()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if data.moveMark then
|
if data.moveMark then
|
||||||
local mark = data.moveMark
|
local mark = table.clone(data.moveMark) or {"", 0}
|
||||||
if type(mark) == "string" then
|
room:setCardMark(currentCard, mark[1], mark[2])
|
||||||
room:setCardMark(currentCard, mark, 1)
|
|
||||||
elseif type(mark) == "table" then
|
|
||||||
mark = table.clone(data.moveMark) or {"", 0}
|
|
||||||
room:setCardMark(currentCard, mark[1], mark[2])
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
if
|
if
|
||||||
data.toArea == Player.Equip and
|
data.toArea == Player.Equip and
|
||||||
currentCard.type == Card.TypeEquip and
|
currentCard.type == Card.TypeEquip and
|
||||||
data.to ~= nil and
|
data.to ~= nil and
|
||||||
room:getPlayerById(data.to):isAlive() and
|
room:getPlayerById(data.to):isAlive() and
|
||||||
#currentCard:getEquipSkills(room:getPlayerById(data.to)) > 0
|
currentCard.equip_skill
|
||||||
then
|
then
|
||||||
currentCard:onInstall(room, room:getPlayerById(data.to))
|
currentCard:onInstall(room, room:getPlayerById(data.to))
|
||||||
end
|
end
|
||||||
|
@ -211,5 +202,3 @@ function MoveCards:main()
|
||||||
room.logic:trigger(fk.AfterCardsMove, nil, cardsMoveStructs)
|
room.logic:trigger(fk.AfterCardsMove, nil, cardsMoveStructs)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
return MoveCards
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
-- SPDX-License-Identifier: GPL-3.0-or-later
|
-- SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
---@class GameEvent.Pindian : GameEvent
|
GameEvent.functions[GameEvent.Pindian] = function(self)
|
||||||
local Pindian = GameEvent:subclass("GameEvent.Pindian")
|
|
||||||
function Pindian:main()
|
|
||||||
local pindianData = table.unpack(self.data)
|
local pindianData = table.unpack(self.data)
|
||||||
local room = self.room
|
local room = self.room
|
||||||
local logic = room.logic
|
local logic = room.logic
|
||||||
|
@ -37,7 +35,6 @@ function Pindian:main()
|
||||||
pindianCard:addSubcard(_pindianCard.id)
|
pindianCard:addSubcard(_pindianCard.id)
|
||||||
|
|
||||||
pindianData.fromCard = pindianCard
|
pindianData.fromCard = pindianCard
|
||||||
pindianData._fromCard = _pindianCard
|
|
||||||
|
|
||||||
table.insert(moveInfos, {
|
table.insert(moveInfos, {
|
||||||
ids = { _pindianCard.id },
|
ids = { _pindianCard.id },
|
||||||
|
@ -56,7 +53,6 @@ function Pindian:main()
|
||||||
pindianCard:addSubcard(_pindianCard.id)
|
pindianCard:addSubcard(_pindianCard.id)
|
||||||
|
|
||||||
pindianData.results[to.id].toCard = pindianCard
|
pindianData.results[to.id].toCard = pindianCard
|
||||||
pindianData.results[to.id]._toCard = _pindianCard
|
|
||||||
|
|
||||||
table.insert(moveInfos, {
|
table.insert(moveInfos, {
|
||||||
ids = { _pindianCard.id },
|
ids = { _pindianCard.id },
|
||||||
|
@ -90,11 +86,9 @@ function Pindian:main()
|
||||||
|
|
||||||
if p == pindianData.from then
|
if p == pindianData.from then
|
||||||
pindianData.fromCard = pindianCard
|
pindianData.fromCard = pindianCard
|
||||||
pindianData._fromCard = _pindianCard
|
|
||||||
else
|
else
|
||||||
pindianData.results[p.id] = pindianData.results[p.id] or {}
|
pindianData.results[p.id] = pindianData.results[p.id] or {}
|
||||||
pindianData.results[p.id].toCard = pindianCard
|
pindianData.results[p.id].toCard = pindianCard
|
||||||
pindianData.results[p.id]._toCard = _pindianCard
|
|
||||||
end
|
end
|
||||||
|
|
||||||
table.insert(moveInfos, {
|
table.insert(moveInfos, {
|
||||||
|
@ -115,21 +109,10 @@ function Pindian:main()
|
||||||
|
|
||||||
room:moveCards(table.unpack(moveInfos))
|
room:moveCards(table.unpack(moveInfos))
|
||||||
|
|
||||||
room:sendFootnote({ pindianData._fromCard.id }, {
|
|
||||||
type = "##PindianCard",
|
|
||||||
from = pindianData.from.id,
|
|
||||||
})
|
|
||||||
for _, to in ipairs(pindianData.tos) do
|
|
||||||
room:sendFootnote({ pindianData.results[to.id]._toCard.id }, {
|
|
||||||
type = "##PindianCard",
|
|
||||||
from = to.id,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
logic:trigger(fk.PindianCardsDisplayed, nil, pindianData)
|
logic:trigger(fk.PindianCardsDisplayed, nil, pindianData)
|
||||||
|
|
||||||
for _, to in ipairs(pindianData.tos) do
|
for toId, result in pairs(pindianData.results) do
|
||||||
local result = pindianData.results[to.id]
|
local to = room:getPlayerById(toId)
|
||||||
if pindianData.fromCard.number > result.toCard.number then
|
if pindianData.fromCard.number > result.toCard.number then
|
||||||
result.winner = pindianData.from
|
result.winner = pindianData.from
|
||||||
elseif pindianData.fromCard.number < result.toCard.number then
|
elseif pindianData.fromCard.number < result.toCard.number then
|
||||||
|
@ -148,13 +131,9 @@ function Pindian:main()
|
||||||
room:sendLog{
|
room:sendLog{
|
||||||
type = "#ShowPindianResult",
|
type = "#ShowPindianResult",
|
||||||
from = pindianData.from.id,
|
from = pindianData.from.id,
|
||||||
to = { to.id },
|
to = { toId },
|
||||||
arg = result.winner == pindianData.from and "pindianwin" or "pindiannotwin"
|
arg = result.winner == pindianData.from and "pindianwin" or "pindiannotwin"
|
||||||
}
|
}
|
||||||
|
|
||||||
-- room:setCardEmotion(pindianData._fromCard.id, result.winner == pindianData.from and "pindianwin" or "pindiannotwin")
|
|
||||||
-- room:setCardEmotion(pindianData.results[to.id]._toCard.id, result.winner == to and "pindianwin" or "pindiannotwin")
|
|
||||||
|
|
||||||
logic:trigger(fk.PindianResultConfirmed, nil, singlePindianData)
|
logic:trigger(fk.PindianResultConfirmed, nil, singlePindianData)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -163,7 +142,7 @@ function Pindian:main()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Pindian:clear()
|
GameEvent.cleaners[GameEvent.Pindian] = function(self)
|
||||||
local pindianData = table.unpack(self.data)
|
local pindianData = table.unpack(self.data)
|
||||||
local room = self.room
|
local room = self.room
|
||||||
|
|
||||||
|
@ -189,5 +168,3 @@ function Pindian:clear()
|
||||||
end
|
end
|
||||||
if not self.interrupted then return end
|
if not self.interrupted then return end
|
||||||
end
|
end
|
||||||
|
|
||||||
return Pindian
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
-- SPDX-License-Identifier: GPL-3.0-or-later
|
-- SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
---@class GameEvent.SkillEffect : GameEvent
|
GameEvent.functions[GameEvent.SkillEffect] = function(self)
|
||||||
local SkillEffect = GameEvent:subclass("GameEvent.SkillEffect")
|
|
||||||
function SkillEffect:main()
|
|
||||||
local effect_cb, player, _skill = table.unpack(self.data)
|
local effect_cb, player, _skill = table.unpack(self.data)
|
||||||
local room = self.room
|
local room = self.room
|
||||||
local logic = room.logic
|
local logic = room.logic
|
||||||
|
@ -21,5 +19,3 @@ function SkillEffect:main()
|
||||||
logic:trigger(fk.AfterSkillEffect, player, skill)
|
logic:trigger(fk.AfterSkillEffect, player, skill)
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
return SkillEffect
|
|
||||||
|
|
|
@ -162,9 +162,7 @@ local sendCardEmotionAndLog = function(room, cardUseEvent)
|
||||||
return _card
|
return _card
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class GameEvent.UseCard : GameEvent
|
GameEvent.functions[GameEvent.UseCard] = function(self)
|
||||||
local UseCard = GameEvent:subclass("GameEvent.UseCard")
|
|
||||||
function UseCard:main()
|
|
||||||
local cardUseEvent = table.unpack(self.data)
|
local cardUseEvent = table.unpack(self.data)
|
||||||
local room = self.room
|
local room = self.room
|
||||||
local logic = room.logic
|
local logic = room.logic
|
||||||
|
@ -187,27 +185,6 @@ function UseCard:main()
|
||||||
cardUseEvent.card.skill:onUse(room, cardUseEvent)
|
cardUseEvent.card.skill:onUse(room, cardUseEvent)
|
||||||
end
|
end
|
||||||
|
|
||||||
if cardUseEvent.card.type == Card.TypeEquip then
|
|
||||||
local targets = TargetGroup:getRealTargets(cardUseEvent.tos)
|
|
||||||
if #targets == 1 then
|
|
||||||
local target = room:getPlayerById(targets[1])
|
|
||||||
local subType = cardUseEvent.card.sub_type
|
|
||||||
local equipsExist = target:getEquipments(subType)
|
|
||||||
|
|
||||||
if #equipsExist > 0 and not target:hasEmptyEquipSlot(subType) then
|
|
||||||
local choices = table.map(
|
|
||||||
equipsExist,
|
|
||||||
function(id, index)
|
|
||||||
return "#EquipmentChoice:" .. index .. "::" .. Fk:translate(Fk:getCardById(id).name) end
|
|
||||||
)
|
|
||||||
if target:hasEmptyEquipSlot(subType) then
|
|
||||||
table.insert(choices, target:getAvailableEquipSlots(subType)[1])
|
|
||||||
end
|
|
||||||
cardUseEvent.toPutSlot = room:askForChoice(target, choices, "replace_equip", "#GameRuleReplaceEquipment")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if logic:trigger(fk.PreCardUse, room:getPlayerById(cardUseEvent.from), cardUseEvent) then
|
if logic:trigger(fk.PreCardUse, room:getPlayerById(cardUseEvent.from), cardUseEvent) then
|
||||||
logic:breakEvent()
|
logic:breakEvent()
|
||||||
end
|
end
|
||||||
|
@ -261,7 +238,7 @@ function UseCard:main()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function UseCard:clear()
|
GameEvent.cleaners[GameEvent.UseCard] = function(self)
|
||||||
local cardUseEvent = table.unpack(self.data)
|
local cardUseEvent = table.unpack(self.data)
|
||||||
local room = self.room
|
local room = self.room
|
||||||
|
|
||||||
|
@ -277,9 +254,7 @@ function UseCard:clear()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class GameEvent.RespondCard : GameEvent
|
GameEvent.functions[GameEvent.RespondCard] = function(self)
|
||||||
local RespondCard = GameEvent:subclass("GameEvent.RespondCard")
|
|
||||||
function RespondCard:main()
|
|
||||||
local cardResponseEvent = table.unpack(self.data)
|
local cardResponseEvent = table.unpack(self.data)
|
||||||
local room = self.room
|
local room = self.room
|
||||||
local logic = room.logic
|
local logic = room.logic
|
||||||
|
@ -331,7 +306,7 @@ function RespondCard:main()
|
||||||
logic:trigger(fk.CardResponding, room:getPlayerById(cardResponseEvent.from), cardResponseEvent)
|
logic:trigger(fk.CardResponding, room:getPlayerById(cardResponseEvent.from), cardResponseEvent)
|
||||||
end
|
end
|
||||||
|
|
||||||
function RespondCard:clear()
|
GameEvent.cleaners[GameEvent.RespondCard] = function(self)
|
||||||
local cardResponseEvent = table.unpack(self.data)
|
local cardResponseEvent = table.unpack(self.data)
|
||||||
local room = self.room
|
local room = self.room
|
||||||
|
|
||||||
|
@ -347,9 +322,7 @@ function RespondCard:clear()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class GameEvent.CardEffect : GameEvent
|
GameEvent.functions[GameEvent.CardEffect] = function(self)
|
||||||
local CardEffect = GameEvent:subclass("GameEvent.CardEffect")
|
|
||||||
function CardEffect:main()
|
|
||||||
local cardEffectEvent = table.unpack(self.data)
|
local cardEffectEvent = table.unpack(self.data)
|
||||||
local room = self.room
|
local room = self.room
|
||||||
local logic = room.logic
|
local logic = room.logic
|
||||||
|
@ -386,16 +359,13 @@ function CardEffect:main()
|
||||||
end
|
end
|
||||||
logic:breakEvent()
|
logic:breakEvent()
|
||||||
end
|
end
|
||||||
elseif logic:trigger(event, room:getPlayerById(cardEffectEvent.to), cardEffectEvent) then
|
elseif cardEffectEvent.to and logic:trigger(event, room:getPlayerById(cardEffectEvent.to), cardEffectEvent) then
|
||||||
if cardEffectEvent.to then
|
cardEffectEvent.nullifiedTargets = cardEffectEvent.nullifiedTargets or {}
|
||||||
cardEffectEvent.nullifiedTargets = cardEffectEvent.nullifiedTargets or {}
|
table.insert(cardEffectEvent.nullifiedTargets, cardEffectEvent.to)
|
||||||
table.insert(cardEffectEvent.nullifiedTargets, cardEffectEvent.to)
|
|
||||||
end
|
|
||||||
logic:breakEvent()
|
logic:breakEvent()
|
||||||
end
|
end
|
||||||
|
|
||||||
room:handleCardEffect(event, cardEffectEvent)
|
room:handleCardEffect(event, cardEffectEvent)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return { UseCard, RespondCard, CardEffect }
|
|
||||||
|
|
|
@ -4,11 +4,15 @@
|
||||||
---@field public id integer @ 事件的id,随着时间推移自动增加并分配给新事件
|
---@field public id integer @ 事件的id,随着时间推移自动增加并分配给新事件
|
||||||
---@field public end_id integer @ 事件的对应结束id,如果整个事件中未插入事件,那么end_id就是自己的id
|
---@field public end_id integer @ 事件的对应结束id,如果整个事件中未插入事件,那么end_id就是自己的id
|
||||||
---@field public room Room @ room实例
|
---@field public room Room @ room实例
|
||||||
---@field public event GameEvent @ 该事件对应的EventType,现已改为对应的class
|
---@field public event integer @ 该事件对应的EventType
|
||||||
---@field public data any @ 事件的附加数据,视类型而定
|
---@field public data any @ 事件的附加数据,视类型而定
|
||||||
---@field public parent GameEvent @ 事件的父事件(栈中的上一层事件)
|
---@field public parent GameEvent @ 事件的父事件(栈中的上一层事件)
|
||||||
---@field public extra_clear fun(self:GameEvent)[] @ 事件结束时执行的自定义函数列表
|
---@field public prepare_func fun(self: GameEvent) @ 事件即将开始时执行的函数
|
||||||
---@field public extra_exit fun(self:GameEvent)[] @ 事件结束后执行的自定义函数
|
---@field public main_func fun(self: GameEvent) @ 事件的主函数
|
||||||
|
---@field public clear_func fun(self: GameEvent) @ 事件结束时执行的函数
|
||||||
|
---@field public extra_clear_funcs fun(self:GameEvent)[] @ 事件结束时执行的自定义函数列表
|
||||||
|
---@field public exit_func fun(self: GameEvent) @ 事件结束后执行的函数
|
||||||
|
---@field public extra_exit_funcs fun(self:GameEvent)[] @ 事件结束后执行的自定义函数
|
||||||
---@field public exec_ret boolean? @ exec函数的返回值,可能不存在
|
---@field public exec_ret boolean? @ exec函数的返回值,可能不存在
|
||||||
---@field public status string @ ready, running, exiting, dead
|
---@field public status string @ ready, running, exiting, dead
|
||||||
---@field public interrupted boolean @ 事件是否是因为被中断而结束的,可能是防止事件或者被杀
|
---@field public interrupted boolean @ 事件是否是因为被中断而结束的,可能是防止事件或者被杀
|
||||||
|
@ -27,96 +31,61 @@ GameEvent.cleaners = {}
|
||||||
---@type (fun(self: GameEvent): bool)[]
|
---@type (fun(self: GameEvent): bool)[]
|
||||||
GameEvent.exit_funcs = {}
|
GameEvent.exit_funcs = {}
|
||||||
|
|
||||||
|
local function wrapCoFunc(f, ...)
|
||||||
|
if not f then return nil end
|
||||||
|
local args = {...}
|
||||||
|
return function() return f(table.unpack(args)) end
|
||||||
|
end
|
||||||
local dummyFunc = Util.DummyFunc
|
local dummyFunc = Util.DummyFunc
|
||||||
|
|
||||||
function GameEvent:initialize(event, ...)
|
function GameEvent:initialize(event, ...)
|
||||||
self.id = -1
|
self.id = -1
|
||||||
self.end_id = -1
|
self.end_id = -1
|
||||||
self.room = RoomInstance
|
self.room = RoomInstance
|
||||||
-- for compat
|
|
||||||
self.event = event
|
self.event = event
|
||||||
---@diagnostic disable-next-line
|
|
||||||
-- self.event = self.class
|
|
||||||
self.data = { ... }
|
self.data = { ... }
|
||||||
|
self.prepare_func = GameEvent.prepare_funcs[event] or dummyFunc
|
||||||
|
self.main_func = wrapCoFunc(GameEvent.functions[event], self) or dummyFunc
|
||||||
|
self.clear_func = GameEvent.cleaners[event] or dummyFunc
|
||||||
|
self.extra_clear_funcs = Util.DummyTable
|
||||||
|
self.exit_func = GameEvent.exit_funcs[event] or dummyFunc
|
||||||
|
self.extra_exit_funcs = Util.DummyTable
|
||||||
self.status = "ready"
|
self.status = "ready"
|
||||||
self.interrupted = false
|
self.interrupted = false
|
||||||
|
|
||||||
self.extra_clear = Util.DummyTable
|
|
||||||
self.extra_exit = Util.DummyTable
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---@generic T
|
-- 静态函数,实际定义在events/init.lua
|
||||||
---@param self T
|
function GameEvent:translate(id)
|
||||||
---@return T
|
error('static')
|
||||||
function GameEvent.create(self, ...)
|
|
||||||
if self.class then error('cannot use "create()" by event instances') end
|
|
||||||
return self:new(self, ...)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- 获取最接近GameEvent的基类
|
|
||||||
---@return GameEvent
|
|
||||||
function GameEvent.getBaseClass(self, ...)
|
|
||||||
if self.class then error('cannot use "getBaseClass()" by event instances') end
|
|
||||||
if self.super == GameEvent or self == GameEvent then
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
return self.super:getBaseClass()
|
|
||||||
end
|
|
||||||
|
|
||||||
function GameEvent.static:subclassed(subclass)
|
|
||||||
local mt = getmetatable(subclass)
|
|
||||||
-- 适配老代码event == GameEvent.Turn之类的奇技淫巧,危险性待评估
|
|
||||||
-- 这样若某个模式启用派生类修改逻辑,那么findParent之类的基于父类也能找
|
|
||||||
mt.__eq = function(a, b)
|
|
||||||
if not a.super or not b.super then return false end
|
|
||||||
return rawequal(a, b) or a:isSubclassOf(b) or b:isSubclassOf(a)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function GameEvent:__tostring()
|
function GameEvent:__tostring()
|
||||||
return string.format("<%s #%d>",
|
return string.format("<%s #%d>", GameEvent:translate(self.event), self.id)
|
||||||
type(self.event == "string") and self.event or self.class.name, self.id)
|
|
||||||
end
|
|
||||||
|
|
||||||
function GameEvent:prepare()
|
|
||||||
return (GameEvent.prepare_funcs[self.event] or dummyFunc)(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function GameEvent:main()
|
|
||||||
return (GameEvent.functions[self.event] or dummyFunc)(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function GameEvent:clear()
|
|
||||||
return (GameEvent.cleaners[self.event] or dummyFunc)(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function GameEvent:exit()
|
|
||||||
return (GameEvent.exit_funcs[self.event] or dummyFunc)(self)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function GameEvent:addCleaner(f)
|
function GameEvent:addCleaner(f)
|
||||||
if self.extra_clear == Util.DummyTable then
|
if self.extra_clear_funcs == Util.DummyTable then
|
||||||
self.extra_clear= {}
|
self.extra_clear_funcs = {}
|
||||||
end
|
end
|
||||||
table.insert(self.extra_clear, f)
|
table.insert(self.extra_clear_funcs, f)
|
||||||
end
|
end
|
||||||
|
|
||||||
function GameEvent:addExitFunc(f)
|
function GameEvent:addExitFunc(f)
|
||||||
if self.extra_exit== Util.DummyTable then
|
if self.extra_exit_funcs == Util.DummyTable then
|
||||||
self.extra_exit= {}
|
self.extra_exit_funcs = {}
|
||||||
end
|
end
|
||||||
table.insert(self.extra_exit, f)
|
table.insert(self.extra_exit_funcs, f)
|
||||||
end
|
end
|
||||||
|
|
||||||
function GameEvent:prependExitFunc(f)
|
function GameEvent:prependExitFunc(f)
|
||||||
if self.extra_exit== Util.DummyTable then
|
if self.extra_exit_funcs == Util.DummyTable then
|
||||||
self.extra_exit= {}
|
self.extra_exit_funcs = {}
|
||||||
end
|
end
|
||||||
table.insert(self.extra_exit, 1, f)
|
table.insert(self.extra_exit_funcs, 1, f)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 找第一个与当前事件有继承关系的特定事件
|
-- 找第一个与当前事件有继承关系的特定事件
|
||||||
---@param eventType GameEvent @ 事件类型
|
---@param eventType integer @ 事件类型
|
||||||
---@param includeSelf bool @ 是否包括本事件
|
---@param includeSelf bool @ 是否包括本事件
|
||||||
---@param depth? integer @ 搜索深度
|
---@param depth? integer @ 搜索深度
|
||||||
---@return GameEvent?
|
---@return GameEvent?
|
||||||
|
@ -218,18 +187,18 @@ function GameEvent:exec()
|
||||||
|
|
||||||
self.parent = logic:getCurrentEvent()
|
self.parent = logic:getCurrentEvent()
|
||||||
|
|
||||||
if self:prepare() then return true end
|
if self:prepare_func() then return true end
|
||||||
|
|
||||||
logic:pushEvent(self)
|
logic:pushEvent(self)
|
||||||
|
|
||||||
local co = coroutine.create(function() return self:main() end)
|
local co = coroutine.create(self.main_func)
|
||||||
self._co = co
|
self._co = co
|
||||||
self.status = "running"
|
self.status = "running"
|
||||||
|
|
||||||
coroutine.yield(self, "__newEvent")
|
coroutine.yield(self, "__newEvent")
|
||||||
|
|
||||||
Pcall(self.exit, self)
|
Pcall(self.exit_func, self)
|
||||||
for _, f in ipairs(self.extra_exit) do
|
for _, f in ipairs(self.extra_exit_funcs) do
|
||||||
if type(f) == "function" then
|
if type(f) == "function" then
|
||||||
Pcall(f, self)
|
Pcall(f, self)
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
---@field public cleaner_stack Stack
|
---@field public cleaner_stack Stack
|
||||||
---@field public role_table string[][]
|
---@field public role_table string[][]
|
||||||
---@field public all_game_events GameEvent[]
|
---@field public all_game_events GameEvent[]
|
||||||
---@field public event_recorder table<GameEvent, GameEvent>
|
---@field public event_recorder table<integer, GameEvent>
|
||||||
---@field public current_event_id integer
|
---@field public current_event_id integer
|
||||||
local GameLogic = class("GameLogic")
|
local GameLogic = class("GameLogic")
|
||||||
|
|
||||||
|
@ -23,21 +23,7 @@ function GameLogic:initialize(room)
|
||||||
self.game_event_stack = Stack:new()
|
self.game_event_stack = Stack:new()
|
||||||
self.cleaner_stack = Stack:new()
|
self.cleaner_stack = Stack:new()
|
||||||
self.all_game_events = {}
|
self.all_game_events = {}
|
||||||
self.event_recorder = setmetatable({}, {
|
self.event_recorder = {}
|
||||||
-- 对派生事件而言 共用一个键 键取决于最接近GameEvent类的基类
|
|
||||||
__newindex = function(t, k, v)
|
|
||||||
if type(k) == "table" and k:isSubclassOf(GameEvent) then
|
|
||||||
k = k:getBaseClass()
|
|
||||||
end
|
|
||||||
rawset(t, k, v)
|
|
||||||
end,
|
|
||||||
__index = function(t, k)
|
|
||||||
if type(k) == "table" and k:isSubclassOf(GameEvent) then
|
|
||||||
k = k:getBaseClass()
|
|
||||||
end
|
|
||||||
return rawget(t, k)
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
self.current_event_id = 0
|
self.current_event_id = 0
|
||||||
self.specific_events_id = {
|
self.specific_events_id = {
|
||||||
[GameEvent.Damage] = 1,
|
[GameEvent.Damage] = 1,
|
||||||
|
@ -79,13 +65,13 @@ function GameLogic:run()
|
||||||
self:action()
|
self:action()
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return boolean
|
local function execGameEvent(type, ...)
|
||||||
local function execGameEvent(tp, ...)
|
local event = GameEvent:new(type, ...)
|
||||||
local event = tp:create(...)
|
|
||||||
local _, ret = event:exec()
|
local _, ret = event:exec()
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function GameLogic:assignRoles()
|
function GameLogic:assignRoles()
|
||||||
local room = self.room
|
local room = self.room
|
||||||
local n = #room.players
|
local n = #room.players
|
||||||
|
@ -127,13 +113,17 @@ function GameLogic:chooseGenerals()
|
||||||
generals = table.filter(generals, function(g) return not table.contains(lord_generals, g) end)
|
generals = table.filter(generals, function(g) return not table.contains(lord_generals, g) end)
|
||||||
room:returnToGeneralPile(generals)
|
room:returnToGeneralPile(generals)
|
||||||
|
|
||||||
room:prepareGeneral(lord, lord_general, deputy, true)
|
room:setPlayerGeneral(lord, lord_general, true)
|
||||||
|
|
||||||
room:askForChooseKingdom({lord})
|
room:askForChooseKingdom({lord})
|
||||||
|
room:broadcastProperty(lord, "general")
|
||||||
|
room:broadcastProperty(lord, "kingdom")
|
||||||
|
room:setDeputyGeneral(lord, deputy)
|
||||||
|
room:broadcastProperty(lord, "deputyGeneral")
|
||||||
end
|
end
|
||||||
|
|
||||||
local nonlord = room:getOtherPlayers(lord, true)
|
local nonlord = room:getOtherPlayers(lord, true)
|
||||||
local generals = table.random(room.general_pile, #nonlord * generalNum)
|
local generals = room:getNGenerals(#nonlord * generalNum)
|
||||||
|
table.shuffle(generals)
|
||||||
for i, p in ipairs(nonlord) do
|
for i, p in ipairs(nonlord) do
|
||||||
local arg = table.slice(generals, (i - 1) * generalNum + 1, i * generalNum + 1)
|
local arg = table.slice(generals, (i - 1) * generalNum + 1, i * generalNum + 1)
|
||||||
p.request_data = json.encode{ arg, n }
|
p.request_data = json.encode{ arg, n }
|
||||||
|
@ -143,22 +133,25 @@ function GameLogic:chooseGenerals()
|
||||||
room:notifyMoveFocus(nonlord, "AskForGeneral")
|
room:notifyMoveFocus(nonlord, "AskForGeneral")
|
||||||
room:doBroadcastRequest("AskForGeneral", nonlord)
|
room:doBroadcastRequest("AskForGeneral", nonlord)
|
||||||
|
|
||||||
|
local selected = {}
|
||||||
for _, p in ipairs(nonlord) do
|
for _, p in ipairs(nonlord) do
|
||||||
local general, deputy
|
|
||||||
if p.general == "" and p.reply_ready then
|
if p.general == "" and p.reply_ready then
|
||||||
local general_ret = json.decode(p.client_reply)
|
local general_ret = json.decode(p.client_reply)
|
||||||
general = general_ret[1]
|
local general = general_ret[1]
|
||||||
deputy = general_ret[2]
|
local deputy = general_ret[2]
|
||||||
|
table.insertTableIfNeed(selected, general_ret)
|
||||||
|
room:setPlayerGeneral(p, general, true, true)
|
||||||
|
room:setDeputyGeneral(p, deputy)
|
||||||
else
|
else
|
||||||
general = p.default_reply[1]
|
room:setPlayerGeneral(p, p.default_reply[1], true, true)
|
||||||
deputy = p.default_reply[2]
|
room:setDeputyGeneral(p, p.default_reply[2])
|
||||||
end
|
end
|
||||||
room:findGeneral(general)
|
|
||||||
room:findGeneral(deputy)
|
|
||||||
room:prepareGeneral(p, general, deputy)
|
|
||||||
p.default_reply = ""
|
p.default_reply = ""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
generals = table.filter(generals, function(g) return not table.contains(selected, g) end)
|
||||||
|
room:returnToGeneralPile(generals)
|
||||||
|
|
||||||
room:askForChooseKingdom(nonlord)
|
room:askForChooseKingdom(nonlord)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -185,25 +178,14 @@ function GameLogic:broadcastGeneral()
|
||||||
p.shield = math.min(general.shield + (deputy and deputy.shield or 0), 5)
|
p.shield = math.min(general.shield + (deputy and deputy.shield or 0), 5)
|
||||||
-- TODO: setup AI here
|
-- TODO: setup AI here
|
||||||
|
|
||||||
local changer = Fk.game_modes[room.settings.gameMode]:getAdjustedProperty(p)
|
if p.role ~= "lord" then
|
||||||
if changer then
|
room:broadcastProperty(p, "general")
|
||||||
for key, value in pairs(changer) do
|
room:broadcastProperty(p, "kingdom")
|
||||||
p[key] = value
|
room:broadcastProperty(p, "deputyGeneral")
|
||||||
end
|
elseif #players >= 5 then
|
||||||
|
p.maxHp = p.maxHp + 1
|
||||||
|
p.hp = p.hp + 1
|
||||||
end
|
end
|
||||||
local fixMaxHp = Fk.generals[p.general].fixMaxHp
|
|
||||||
local deputyFix = Fk.generals[p.deputyGeneral] and Fk.generals[p.deputyGeneral].fixMaxHp
|
|
||||||
if deputyFix then
|
|
||||||
fixMaxHp = fixMaxHp and math.min(fixMaxHp, deputyFix) or deputyFix
|
|
||||||
end
|
|
||||||
if fixMaxHp then
|
|
||||||
p.maxHp = fixMaxHp
|
|
||||||
end
|
|
||||||
p.hp = math.min(p.maxHp, p.hp)
|
|
||||||
|
|
||||||
room:broadcastProperty(p, "general")
|
|
||||||
room:broadcastProperty(p, "deputyGeneral")
|
|
||||||
room:broadcastProperty(p, "kingdom")
|
|
||||||
room:broadcastProperty(p, "maxHp")
|
room:broadcastProperty(p, "maxHp")
|
||||||
room:broadcastProperty(p, "hp")
|
room:broadcastProperty(p, "hp")
|
||||||
room:broadcastProperty(p, "shield")
|
room:broadcastProperty(p, "shield")
|
||||||
|
@ -434,7 +416,7 @@ end
|
||||||
|
|
||||||
-- 此为启动事件管理器并启动第一个事件的初始函数
|
-- 此为启动事件管理器并启动第一个事件的初始函数
|
||||||
function GameLogic:start()
|
function GameLogic:start()
|
||||||
local root_event = GameEvent.Game:create()
|
local root_event = GameEvent:new(GameEvent.Game)
|
||||||
|
|
||||||
self:pushEvent(root_event)
|
self:pushEvent(root_event)
|
||||||
|
|
||||||
|
@ -442,20 +424,25 @@ function GameLogic:start()
|
||||||
-- 事件管理器协程,同时也是Game事件
|
-- 事件管理器协程,同时也是Game事件
|
||||||
-- 当新事件想要exec时,就切回此处,由这里负责调度协程
|
-- 当新事件想要exec时,就切回此处,由这里负责调度协程
|
||||||
-- 一个事件结束后也切回此处,然后resume
|
-- 一个事件结束后也切回此处,然后resume
|
||||||
local co = coroutine.create(function() return root_event:main() end)
|
local co = coroutine.create(root_event.main_func)
|
||||||
root_event._co = co
|
root_event._co = co
|
||||||
|
|
||||||
|
local jump_to -- shutdown函数用
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
-- 对于cleaner和正常事件,处理更后面来的
|
-- 对于cleaner和正常事件,处理更后面来的
|
||||||
local ne = self:getCurrentEvent()
|
local ne = self:getCurrentEvent()
|
||||||
local ce = self:getCurrentCleaner()
|
local ce = self:getCurrentCleaner()
|
||||||
local e = ce and (ce.id >= ne.id and ce or ne) or ne
|
local e = ce and (ce.id >= ne.id and ce or ne) or ne
|
||||||
|
|
||||||
if e == ne and e.killed then
|
-- 如果正在jump的话,判断是否需要继续clean,否则正常继续
|
||||||
|
if e == ne and jump_to ~= nil then
|
||||||
e.interrupted = true
|
e.interrupted = true
|
||||||
|
e.killed = e ~= jump_to
|
||||||
self:clearEvent(e)
|
self:clearEvent(e)
|
||||||
coroutine.close(e._co)
|
coroutine.close(e._co)
|
||||||
e.status = "dead"
|
e.status = "dead"
|
||||||
|
if e == jump_to then jump_to = nil end -- shutdown结束了
|
||||||
e = self:getCurrentCleaner()
|
e = self:getCurrentCleaner()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -480,12 +467,11 @@ function GameLogic:start()
|
||||||
coroutine.close(e._co)
|
coroutine.close(e._co)
|
||||||
e.status = "dead"
|
e.status = "dead"
|
||||||
elseif ret == true then
|
elseif ret == true then
|
||||||
-- 遍历栈,将shutdown图中的事件全标记上killed
|
-- 跳到越早发生的事件越好
|
||||||
-- 被标记killed的事件之后会自动结束并清理
|
if not jump_to then
|
||||||
for i = self.game_event_stack.p, 1, -1 do
|
jump_to = evt
|
||||||
local event = self.game_event_stack.t[i]
|
else
|
||||||
event.killed = true
|
jump_to = jump_to.id < evt.id and jump_to or evt
|
||||||
if event == evt then break end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -565,9 +551,9 @@ function GameLogic:clearEvent(event)
|
||||||
if event.event == GameEvent.ClearEvent then return end
|
if event.event == GameEvent.ClearEvent then return end
|
||||||
if event.status == "exiting" then return end
|
if event.status == "exiting" then return end
|
||||||
event.status = "exiting"
|
event.status = "exiting"
|
||||||
local ce = GameEvent.ClearEvent:create(event)
|
local ce = GameEvent(GameEvent.ClearEvent, event)
|
||||||
ce.id = self.current_event_id
|
ce.id = self.current_event_id
|
||||||
local co = coroutine.create(function() return ce:main() end)
|
local co = coroutine.create(ce.main_func)
|
||||||
ce._co = co
|
ce._co = co
|
||||||
self.cleaner_stack:push(ce)
|
self.cleaner_stack:push(ce)
|
||||||
end
|
end
|
||||||
|
@ -577,7 +563,7 @@ function GameLogic:getCurrentEvent()
|
||||||
return self.game_event_stack.t[self.game_event_stack.p]
|
return self.game_event_stack.t[self.game_event_stack.p]
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param eventType GameEvent
|
---@param eventType integer
|
||||||
function GameLogic:getMostRecentEvent(eventType)
|
function GameLogic:getMostRecentEvent(eventType)
|
||||||
return self:getCurrentEvent():findParent(eventType, true)
|
return self:getCurrentEvent():findParent(eventType, true)
|
||||||
end
|
end
|
||||||
|
@ -595,7 +581,7 @@ function GameLogic:getCurrentSkillName()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 在指定历史范围中找至多n个符合条件的事件
|
-- 在指定历史范围中找至多n个符合条件的事件
|
||||||
---@param eventType GameEvent @ 要查找的事件类型
|
---@param eventType integer @ 要查找的事件类型
|
||||||
---@param n integer @ 最多找多少个
|
---@param n integer @ 最多找多少个
|
||||||
---@param func fun(e: GameEvent): boolean @ 过滤用的函数
|
---@param func fun(e: GameEvent): boolean @ 过滤用的函数
|
||||||
---@param scope integer @ 查询历史范围,只能是当前阶段/回合/轮次
|
---@param scope integer @ 查询历史范围,只能是当前阶段/回合/轮次
|
||||||
|
|
|
@ -2,13 +2,9 @@
|
||||||
|
|
||||||
local function tellRoomToObserver(self, player)
|
local function tellRoomToObserver(self, player)
|
||||||
local observee = self.players[1]
|
local observee = self.players[1]
|
||||||
local start_time = os.getms()
|
|
||||||
local summary = self:getSummary(observee, true)
|
local summary = self:getSummary(observee, true)
|
||||||
player:doNotify("Observe", json.encode(summary))
|
player:doNotify("Observe", json.encode(summary))
|
||||||
|
|
||||||
fk.qInfo(string.format("[Observe] %d, %s, in %.3fms",
|
|
||||||
self.id, player:getScreenName(), (os.getms() - start_time) / 1000))
|
|
||||||
|
|
||||||
table.insert(self.observers, {observee.id, player, player:getId()})
|
table.insert(self.observers, {observee.id, player, player:getId()})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -80,7 +76,6 @@ request_handlers["luckcard"] = function(room, id, reqlist)
|
||||||
p:doNotify("AskForLuckCard", pdata.luckTime)
|
p:doNotify("AskForLuckCard", pdata.luckTime)
|
||||||
else
|
else
|
||||||
p.serverplayer:setThinking(false)
|
p.serverplayer:setThinking(false)
|
||||||
ResumeRoom(room.id)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
room:setTag("LuckCardData", luck_data)
|
room:setTag("LuckCardData", luck_data)
|
||||||
|
@ -116,7 +111,6 @@ request_handlers["surrender"] = function(room, id, reqlist)
|
||||||
room.hasSurrendered = true
|
room.hasSurrendered = true
|
||||||
player.surrendered = true
|
player.surrendered = true
|
||||||
room:doBroadcastNotify("CancelRequest", "")
|
room:doBroadcastNotify("CancelRequest", "")
|
||||||
ResumeRoom(room.id)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
request_handlers["updatemini"] = function(room, pid, reqlist)
|
request_handlers["updatemini"] = function(room, pid, reqlist)
|
||||||
|
@ -133,7 +127,6 @@ end
|
||||||
|
|
||||||
request_handlers["newroom"] = function(s, id)
|
request_handlers["newroom"] = function(s, id)
|
||||||
s:registerRoom(id)
|
s:registerRoom(id)
|
||||||
ResumeRoom(id)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
request_handlers["reloadpackage"] = function(_, _, reqlist)
|
request_handlers["reloadpackage"] = function(_, _, reqlist)
|
||||||
|
@ -142,16 +135,31 @@ request_handlers["reloadpackage"] = function(_, _, reqlist)
|
||||||
Fk:reloadPackage(path)
|
Fk:reloadPackage(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
return function(self, request)
|
-- 处理异步请求的协程,本身也是个死循环就是了。
|
||||||
local reqlist = request:split(",")
|
-- 为了适应调度器,目前又暂且将请求分为“耗时请求”和不耗时请求。
|
||||||
local roomId = tonumber(table.remove(reqlist, 1))
|
-- 耗时请求处理后会立刻挂起。不耗时的请求则会不断处理直到请求队列空后再挂起。
|
||||||
local room = self:getRoom(roomId)
|
local function requestLoop(self)
|
||||||
|
while true do
|
||||||
|
local ret = false
|
||||||
|
local request = self.thread:fetchRequest()
|
||||||
|
if request ~= "" then
|
||||||
|
ret = true
|
||||||
|
local reqlist = request:split(",")
|
||||||
|
local roomId = tonumber(table.remove(reqlist, 1))
|
||||||
|
local room = self:getRoom(roomId)
|
||||||
|
|
||||||
if room then
|
if room then
|
||||||
RoomInstance = room
|
RoomInstance = room
|
||||||
local id = tonumber(reqlist[1])
|
local id = tonumber(reqlist[1])
|
||||||
local command = reqlist[2]
|
local command = reqlist[2]
|
||||||
Pcall(request_handlers[command], room, id, reqlist)
|
Pcall(request_handlers[command], room, id, reqlist)
|
||||||
RoomInstance = nil
|
RoomInstance = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not ret then
|
||||||
|
coroutine.yield()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return requestLoop
|
||||||
|
|
|
@ -84,11 +84,6 @@ function Room:initialize(_room)
|
||||||
self.request_queue = {}
|
self.request_queue = {}
|
||||||
self.request_self = {}
|
self.request_self = {}
|
||||||
|
|
||||||
-- doNotify过载保护,每次获得控制权时置为0
|
|
||||||
-- 若在yield之前执行了max次doNotify则强制让出
|
|
||||||
self.notify_count = 0
|
|
||||||
self.notify_max = 500
|
|
||||||
|
|
||||||
self.settings = json.decode(self.room:settings())
|
self.settings = json.decode(self.room:settings())
|
||||||
self.disabled_packs = self.settings.disabledPack
|
self.disabled_packs = self.settings.disabledPack
|
||||||
if not Fk.game_modes[self.settings.gameMode] then
|
if not Fk.game_modes[self.settings.gameMode] then
|
||||||
|
@ -113,11 +108,10 @@ function Room:resume()
|
||||||
local main_co = self.main_co
|
local main_co = self.main_co
|
||||||
|
|
||||||
if self:checkNoHuman() then
|
if self:checkNoHuman() then
|
||||||
goto GAME_OVER
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
if not self.game_finished then
|
if not self.game_finished then
|
||||||
self.notify_count = 0
|
|
||||||
ret, err_msg, rest_time = coroutine.resume(main_co, err_msg)
|
ret, err_msg, rest_time = coroutine.resume(main_co, err_msg)
|
||||||
|
|
||||||
-- handle error
|
-- handle error
|
||||||
|
@ -168,6 +162,17 @@ function Room:isReady()
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- 因为delay函数而延时:判断延时是否已经结束。
|
||||||
|
-- 注意整个delay函数的实现都搬到这来了,delay本身只负责挂起协程了。
|
||||||
|
if self.in_delay then
|
||||||
|
local rest = self.delay_duration - (os.getms() - self.delay_start) / 1000
|
||||||
|
if rest <= 0 then
|
||||||
|
self.in_delay = false
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false, rest
|
||||||
|
end
|
||||||
|
|
||||||
-- 剩下的就是因为等待应答而未就绪了
|
-- 剩下的就是因为等待应答而未就绪了
|
||||||
-- 检查所有正在等回答的玩家,如果已经过了烧条时间
|
-- 检查所有正在等回答的玩家,如果已经过了烧条时间
|
||||||
-- 那么就不认为他还需要时间就绪了
|
-- 那么就不认为他还需要时间就绪了
|
||||||
|
@ -177,14 +182,13 @@ function Room:isReady()
|
||||||
for _, p in ipairs(self.players) do
|
for _, p in ipairs(self.players) do
|
||||||
-- 这里判断的话需要用_splayer了,不然一控多的情况下会导致重复判断
|
-- 这里判断的话需要用_splayer了,不然一控多的情况下会导致重复判断
|
||||||
if p._splayer:thinking() then
|
if p._splayer:thinking() then
|
||||||
|
ret = false
|
||||||
-- 烧条烧光了的话就把thinking设为false
|
-- 烧条烧光了的话就把thinking设为false
|
||||||
rest = p.request_timeout * 1000 - (os.getms() -
|
rest = p.request_timeout * 1000 - (os.getms() -
|
||||||
p.request_start) / 1000
|
p.request_start) / 1000
|
||||||
|
|
||||||
if rest <= 0 or p.serverplayer:getState() ~= fk.Player_Online then
|
if rest <= 0 or p.serverplayer:getState() ~= fk.Player_Online then
|
||||||
p._splayer:setThinking(false)
|
p._splayer:setThinking(false)
|
||||||
else
|
|
||||||
ret = false
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -240,6 +244,7 @@ function Room:run()
|
||||||
local logic = (mode.logic and mode.logic() or GameLogic):new(self)
|
local logic = (mode.logic and mode.logic() or GameLogic):new(self)
|
||||||
self.logic = logic
|
self.logic = logic
|
||||||
if mode.rule then logic:addTriggerSkill(mode.rule) end
|
if mode.rule then logic:addTriggerSkill(mode.rule) end
|
||||||
|
-- GameEvent(GameEvent.Game):exec()
|
||||||
logic:start()
|
logic:start()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -332,7 +337,7 @@ end
|
||||||
--- 获得当前房间中的所有玩家。
|
--- 获得当前房间中的所有玩家。
|
||||||
---
|
---
|
||||||
--- 返回的数组的第一个元素是当前回合玩家,并且按行动顺序进行排序。
|
--- 返回的数组的第一个元素是当前回合玩家,并且按行动顺序进行排序。
|
||||||
---@param sortBySeat? boolean @ 是否按座位排序,默认是
|
---@param sortBySeat? boolean @ 是否无视按座位排序直接返回
|
||||||
---@return ServerPlayer[] @ 房间中玩家的数组
|
---@return ServerPlayer[] @ 房间中玩家的数组
|
||||||
function Room:getAllPlayers(sortBySeat)
|
function Room:getAllPlayers(sortBySeat)
|
||||||
if not self.game_started then
|
if not self.game_started then
|
||||||
|
@ -354,7 +359,7 @@ function Room:getAllPlayers(sortBySeat)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- 获得所有存活玩家,参看getAllPlayers
|
--- 获得所有存活玩家,参看getAllPlayers
|
||||||
---@param sortBySeat? boolean @ 是否按座位排序,默认是
|
---@param sortBySeat? boolean
|
||||||
---@return ServerPlayer[]
|
---@return ServerPlayer[]
|
||||||
function Room:getAlivePlayers(sortBySeat)
|
function Room:getAlivePlayers(sortBySeat)
|
||||||
if sortBySeat == nil or sortBySeat then
|
if sortBySeat == nil or sortBySeat then
|
||||||
|
@ -365,7 +370,7 @@ function Room:getAlivePlayers(sortBySeat)
|
||||||
if temp == nil then
|
if temp == nil then
|
||||||
return { table.unpack(self.players) }
|
return { table.unpack(self.players) }
|
||||||
end
|
end
|
||||||
local ret = current.dead and {} or {current}
|
local ret = {current}
|
||||||
while temp ~= current do
|
while temp ~= current do
|
||||||
if not temp.dead then
|
if not temp.dead then
|
||||||
table.insert(ret, temp)
|
table.insert(ret, temp)
|
||||||
|
@ -381,7 +386,7 @@ end
|
||||||
|
|
||||||
--- 获得除一名玩家外的其他玩家。
|
--- 获得除一名玩家外的其他玩家。
|
||||||
---@param player ServerPlayer @ 要排除的玩家
|
---@param player ServerPlayer @ 要排除的玩家
|
||||||
---@param sortBySeat? boolean @ 是否按座位排序,默认是
|
---@param sortBySeat? boolean @ 是否要按座位排序?
|
||||||
---@param include_dead? boolean @ 是否要把死人也算进去?
|
---@param include_dead? boolean @ 是否要把死人也算进去?
|
||||||
---@return ServerPlayer[] @ 其他玩家列表
|
---@return ServerPlayer[] @ 其他玩家列表
|
||||||
function Room:getOtherPlayers(player, sortBySeat, include_dead)
|
function Room:getOtherPlayers(player, sortBySeat, include_dead)
|
||||||
|
@ -560,8 +565,8 @@ function Room:setBanner(name, value)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return boolean
|
---@return boolean
|
||||||
local function execGameEvent(tp, ...)
|
local function execGameEvent(type, ...)
|
||||||
local event = tp:create(...)
|
local event = GameEvent:new(type, ...)
|
||||||
local _, ret = event:exec()
|
local _, ret = event:exec()
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
@ -595,41 +600,6 @@ function Room:setDeputyGeneral(player, general)
|
||||||
self:notifyProperty(player, player, "deputyGeneral")
|
self:notifyProperty(player, player, "deputyGeneral")
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param player ServerPlayer
|
|
||||||
---@param general string
|
|
||||||
---@param deputy string
|
|
||||||
---@param broadcast boolean|nil
|
|
||||||
function Room:prepareGeneral(player, general, deputy, broadcast)
|
|
||||||
self:findGeneral(general)
|
|
||||||
self:findGeneral(deputy)
|
|
||||||
local skills = Fk.generals[general]:getSkillNameList()
|
|
||||||
if Fk.generals[deputy] then
|
|
||||||
table.insertTable(skills, Fk.generals[deputy]:getSkillNameList())
|
|
||||||
end
|
|
||||||
if table.find(skills, function (s) return Fk.skills[s].isHiddenSkill end) then
|
|
||||||
self:setPlayerMark(player, "__hidden_general", general)
|
|
||||||
if Fk.generals[deputy] then
|
|
||||||
self:setPlayerMark(player, "__hidden_deputy", deputy)
|
|
||||||
deputy = ""
|
|
||||||
end
|
|
||||||
general = "hiddenone"
|
|
||||||
end
|
|
||||||
player.general = general
|
|
||||||
player.gender = Fk.generals[general].gender
|
|
||||||
self:broadcastProperty(player, "gender")
|
|
||||||
if Fk.generals[deputy] then
|
|
||||||
player.deputyGeneral = deputy
|
|
||||||
end
|
|
||||||
player.kingdom = Fk.generals[general].kingdom
|
|
||||||
for _, property in ipairs({"general","deputyGeneral","kingdom"}) do
|
|
||||||
if broadcast then
|
|
||||||
self:broadcastProperty(player, property)
|
|
||||||
else
|
|
||||||
self:notifyProperty(player, player, property)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param player ServerPlayer @ 要换将的玩家
|
---@param player ServerPlayer @ 要换将的玩家
|
||||||
---@param new_general string @ 要变更的武将,若不存在则变身为孙策,孙策不存在变身为士兵
|
---@param new_general string @ 要变更的武将,若不存在则变身为孙策,孙策不存在变身为士兵
|
||||||
---@param full? boolean @ 是否血量满状态变身
|
---@param full? boolean @ 是否血量满状态变身
|
||||||
|
@ -787,10 +757,6 @@ local function surrenderCheck(room)
|
||||||
room.hasSurrendered = false
|
room.hasSurrendered = false
|
||||||
end
|
end
|
||||||
|
|
||||||
local function setRequestTimer(room)
|
|
||||||
room.room:setRequestTimer(room.timeout * 1000 + 500)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- 向某个玩家发起一次Request。
|
--- 向某个玩家发起一次Request。
|
||||||
---@param player ServerPlayer @ 发出这个请求的目标玩家
|
---@param player ServerPlayer @ 发出这个请求的目标玩家
|
||||||
---@param command string @ 请求的类型
|
---@param command string @ 请求的类型
|
||||||
|
@ -804,11 +770,9 @@ function Room:doRequest(player, command, jsonData, wait)
|
||||||
player:doRequest(command, jsonData, self.timeout)
|
player:doRequest(command, jsonData, self.timeout)
|
||||||
|
|
||||||
if wait then
|
if wait then
|
||||||
setRequestTimer(self)
|
|
||||||
local ret = player:waitForReply(self.timeout)
|
local ret = player:waitForReply(self.timeout)
|
||||||
player.serverplayer:setBusy(false)
|
player.serverplayer:setBusy(false)
|
||||||
player.serverplayer:setThinking(false)
|
player.serverplayer:setThinking(false)
|
||||||
self.room:destroyRequestTimer()
|
|
||||||
surrenderCheck(self)
|
surrenderCheck(self)
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
@ -822,7 +786,6 @@ function Room:doBroadcastRequest(command, players, jsonData)
|
||||||
players = players or self.players
|
players = players or self.players
|
||||||
self.request_queue = {}
|
self.request_queue = {}
|
||||||
self.race_request_list = nil
|
self.race_request_list = nil
|
||||||
setRequestTimer(self)
|
|
||||||
for _, p in ipairs(players) do
|
for _, p in ipairs(players) do
|
||||||
p:doRequest(command, jsonData or p.request_data)
|
p:doRequest(command, jsonData or p.request_data)
|
||||||
end
|
end
|
||||||
|
@ -840,7 +803,6 @@ function Room:doBroadcastRequest(command, players, jsonData)
|
||||||
p.serverplayer:setThinking(false)
|
p.serverplayer:setThinking(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
self.room:destroyRequestTimer()
|
|
||||||
surrenderCheck(self)
|
surrenderCheck(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -857,7 +819,6 @@ function Room:doRaceRequest(command, players, jsonData)
|
||||||
players = players or self.players
|
players = players or self.players
|
||||||
players = table.simpleClone(players)
|
players = table.simpleClone(players)
|
||||||
local player_len = #players
|
local player_len = #players
|
||||||
setRequestTimer(self)
|
|
||||||
-- self:notifyMoveFocus(players, command)
|
-- self:notifyMoveFocus(players, command)
|
||||||
self.request_queue = {}
|
self.request_queue = {}
|
||||||
self.race_request_list = players
|
self.race_request_list = players
|
||||||
|
@ -876,8 +837,7 @@ function Room:doRaceRequest(command, players, jsonData)
|
||||||
if remainTime - elapsed <= 0 then
|
if remainTime - elapsed <= 0 then
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
for i = #players, 1, -1 do
|
for _, p in ipairs(players) do
|
||||||
local p = players[i]
|
|
||||||
p:waitForReply(0)
|
p:waitForReply(0)
|
||||||
if p.reply_ready == true then
|
if p.reply_ready == true then
|
||||||
winner = p
|
winner = p
|
||||||
|
@ -885,7 +845,7 @@ function Room:doRaceRequest(command, players, jsonData)
|
||||||
end
|
end
|
||||||
|
|
||||||
if p.reply_cancel then
|
if p.reply_cancel then
|
||||||
table.remove(players, i)
|
table.removeOne(players, p)
|
||||||
table.insertIfNeed(canceled_players, p)
|
table.insertIfNeed(canceled_players, p)
|
||||||
elseif p.id > 0 then
|
elseif p.id > 0 then
|
||||||
-- 骗过调度器让他以为自己尚未就绪
|
-- 骗过调度器让他以为自己尚未就绪
|
||||||
|
@ -911,16 +871,20 @@ function Room:doRaceRequest(command, players, jsonData)
|
||||||
p.serverplayer:setThinking(false)
|
p.serverplayer:setThinking(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
self.room:destroyRequestTimer()
|
|
||||||
surrenderCheck(self)
|
surrenderCheck(self)
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
--- 延迟一段时间。
|
--- 延迟一段时间。
|
||||||
|
---
|
||||||
|
--- 这个函数不应该在请求处理协程中使用。
|
||||||
---@param ms integer @ 要延迟的毫秒数
|
---@param ms integer @ 要延迟的毫秒数
|
||||||
function Room:delay(ms)
|
function Room:delay(ms)
|
||||||
self.room:delay(ms)
|
local start = os.getms()
|
||||||
|
self.delay_start = start
|
||||||
|
self.delay_duration = ms
|
||||||
|
self.in_delay = true
|
||||||
coroutine.yield("__handleRequest", ms)
|
coroutine.yield("__handleRequest", ms)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -949,6 +913,24 @@ function Room:notifyMoveCards(players, card_moves, forceVisible)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function containArea(area, relevant) --处理区的处理?
|
||||||
|
local areas = relevant
|
||||||
|
and {Card.PlayerEquip, Card.PlayerJudge, Card.DiscardPile, Card.Processing, Card.PlayerHand, Card.PlayerSpecial}
|
||||||
|
or {Card.PlayerEquip, Card.PlayerJudge, Card.DiscardPile, Card.Processing}
|
||||||
|
return table.contains(areas, area)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- forceVisible make the move visible
|
||||||
|
-- if move is relevant to player's hands or equips, it should be open
|
||||||
|
-- cards move from/to equip/judge/discard/processing should be open
|
||||||
|
|
||||||
|
if not (move.moveVisible or forceVisible or containArea(move.toArea, move.to and p.isBuddy and p:isBuddy(move.to))) then
|
||||||
|
for _, info in ipairs(move.moveInfo) do
|
||||||
|
if not containArea(info.fromArea, move.from and p.isBuddy and p:isBuddy(move.from)) then
|
||||||
|
info.cardId = -1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
p:doNotify("MoveCards", json.encode(arg))
|
p:doNotify("MoveCards", json.encode(arg))
|
||||||
end
|
end
|
||||||
|
@ -1076,7 +1058,7 @@ end
|
||||||
--- 与此同时,在战报里面发一条“xxx发动了xxx”
|
--- 与此同时,在战报里面发一条“xxx发动了xxx”
|
||||||
---@param player ServerPlayer @ 发动技能的那个玩家
|
---@param player ServerPlayer @ 发动技能的那个玩家
|
||||||
---@param skill_name string @ 技能名
|
---@param skill_name string @ 技能名
|
||||||
---@param skill_type? string | AnimationType @ 技能的动画效果,默认是那个技能的anim_type
|
---@param skill_type? string @ 技能的动画效果,默认是那个技能的anim_type
|
||||||
function Room:notifySkillInvoked(player, skill_name, skill_type)
|
function Room:notifySkillInvoked(player, skill_name, skill_type)
|
||||||
local bigAnim = false
|
local bigAnim = false
|
||||||
if not skill_type then
|
if not skill_type then
|
||||||
|
@ -1860,13 +1842,7 @@ function Room:askForChoice(player, choices, skill_name, prompt, detailed, all_ch
|
||||||
local result = self:doRequest(player, command, json.encode{
|
local result = self:doRequest(player, command, json.encode{
|
||||||
choices, all_choices, skill_name, prompt, detailed
|
choices, all_choices, skill_name, prompt, detailed
|
||||||
})
|
})
|
||||||
if result == "" then
|
if result == "" then result = choices[1] end
|
||||||
if table.contains(choices, "Cancel") then
|
|
||||||
result = "Cancel"
|
|
||||||
else
|
|
||||||
result = choices[1]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1994,93 +1970,6 @@ function Room:askForAddTarget(player, targets, num, can_minus, distance_limited,
|
||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
|
|
||||||
--- 询问玩家在自定义大小的框中排列卡牌(观星、交换、拖拽选牌)
|
|
||||||
---@param player ServerPlayer @ 要询问的玩家
|
|
||||||
---@param skillname string @ 烧条技能名
|
|
||||||
---@param cardMap any @ { "牌堆1卡表", "牌堆2卡表", …… }
|
|
||||||
---@param prompt? string @ 操作提示
|
|
||||||
---@param box_size? integer @ 数值对应卡牌平铺张数的最大值,为0则有单个卡位,每张卡占100单位长度,默认为7
|
|
||||||
---@param max_limit? integer[] @ 每一行牌上限 { 第一行, 第二行,…… },不填写则不限
|
|
||||||
---@param min_limit? integer[] @ 每一行牌下限 { 第一行, 第二行,…… },不填写则不限
|
|
||||||
---@param free_arrange? boolean @ 是否允许自由排列第一行卡的位置,默认不能
|
|
||||||
---@param pattern? string @ 控制第一行卡牌是否可以操作,不填写默认均可操作
|
|
||||||
---@param poxi_type? string @ 控制每张卡牌是否可以操作、确定键是否可以点击,不填写默认均可操作
|
|
||||||
---@param default_choice? table[] @ 超时的默认响应值,在带poxi_type时需要填写
|
|
||||||
---@return table[]
|
|
||||||
function Room:askForArrangeCards(player, skillname, cardMap, prompt, free_arrange, box_size, max_limit, min_limit, pattern, poxi_type, default_choice)
|
|
||||||
prompt = prompt or ""
|
|
||||||
local areaNames = {}
|
|
||||||
if type(cardMap[1]) == "number" then
|
|
||||||
cardMap = {cardMap}
|
|
||||||
else
|
|
||||||
for i = #cardMap, 1, -1 do
|
|
||||||
if type(cardMap[i]) == "string" then
|
|
||||||
table.insert(areaNames, 1, cardMap[i])
|
|
||||||
table.remove(cardMap, i)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if #areaNames == 0 then
|
|
||||||
areaNames = {skillname, "toObtain"}
|
|
||||||
end
|
|
||||||
box_size = box_size or 7
|
|
||||||
max_limit = max_limit or {#cardMap[1], #cardMap > 1 and #cardMap[2] or #cardMap[1]}
|
|
||||||
min_limit = min_limit or {0, 0}
|
|
||||||
for _ = #cardMap + 1, #min_limit, 1 do
|
|
||||||
table.insert(cardMap, {})
|
|
||||||
end
|
|
||||||
pattern = pattern or "."
|
|
||||||
poxi_type = poxi_type or ""
|
|
||||||
local command = "AskForArrangeCards"
|
|
||||||
local data = {
|
|
||||||
cards = cardMap,
|
|
||||||
names = areaNames,
|
|
||||||
prompt = prompt,
|
|
||||||
size = box_size,
|
|
||||||
capacities = max_limit,
|
|
||||||
limits = min_limit,
|
|
||||||
is_free = free_arrange or false,
|
|
||||||
pattern = pattern or ".",
|
|
||||||
poxi_type = poxi_type or "",
|
|
||||||
cancelable = ((pattern ~= "." or poxi_type ~= "") and (default_choice == nil))
|
|
||||||
}
|
|
||||||
local result = self:doRequest(player, command, json.encode(data))
|
|
||||||
-- local result = player.room:askForCustomDialog(player, skillname,
|
|
||||||
-- "RoomElement/ArrangeCardsBox.qml", {
|
|
||||||
-- cardMap, prompt, box_size, max_limit, min_limit, free_arrange or false, areaNames,
|
|
||||||
-- pattern or ".", poxi_type or "", ((pattern ~= "." or poxi_type ~= "") and (default_choice == nil))
|
|
||||||
-- })
|
|
||||||
if result == "" then
|
|
||||||
if default_choice then return default_choice end
|
|
||||||
for j = 1, #min_limit, 1 do
|
|
||||||
if #cardMap[j] < min_limit[j] then
|
|
||||||
local cards = {table.connect(table.unpack(cardMap))}
|
|
||||||
if #min_limit > 1 then
|
|
||||||
for i = 2, #min_limit, 1 do
|
|
||||||
table.insert(cards, {})
|
|
||||||
if #cards[i] < min_limit[i] then
|
|
||||||
for _ = 1, min_limit[i] - #cards[i], 1 do
|
|
||||||
table.insert(cards[i], table.remove(cards[1], #cards[1] + #cards[i] - min_limit[i] + 1))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if #cards[1] > max_limit[1] then
|
|
||||||
for i = 2, #max_limit, 1 do
|
|
||||||
while #cards[i] < max_limit[i] do
|
|
||||||
table.insert(cards[i], table.remove(cards[1], max_limit[1] + 1))
|
|
||||||
if #cards[1] == max_limit[1] then return cards end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return cards
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return cardMap
|
|
||||||
end
|
|
||||||
return json.decode(result)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- TODO: guanxing type
|
-- TODO: guanxing type
|
||||||
--- 询问玩家对若干牌进行观星。
|
--- 询问玩家对若干牌进行观星。
|
||||||
---
|
---
|
||||||
|
@ -2114,15 +2003,9 @@ function Room:askForGuanxing(player, cards, top_limit, bottom_limit, customNotif
|
||||||
end
|
end
|
||||||
local command = "AskForGuanxing"
|
local command = "AskForGuanxing"
|
||||||
self:notifyMoveFocus(player, customNotify or command)
|
self:notifyMoveFocus(player, customNotify or command)
|
||||||
local max_top = top_limit and top_limit[2] or #cards
|
|
||||||
local card_map = {table.slice(cards, 1, max_top + 1)}
|
|
||||||
if max_top < #cards then
|
|
||||||
table.insert(card_map, table.slice(cards, max_top))
|
|
||||||
end
|
|
||||||
local data = {
|
local data = {
|
||||||
prompt = "",
|
prompt = "",
|
||||||
is_free = true,
|
cards = cards,
|
||||||
cards = card_map,
|
|
||||||
min_top_cards = top_limit and top_limit[1] or 0,
|
min_top_cards = top_limit and top_limit[1] or 0,
|
||||||
max_top_cards = top_limit and top_limit[2] or #cards,
|
max_top_cards = top_limit and top_limit[2] or #cards,
|
||||||
min_bottom_cards = bottom_limit and bottom_limit[1] or 0,
|
min_bottom_cards = bottom_limit and bottom_limit[1] or 0,
|
||||||
|
@ -2151,7 +2034,7 @@ function Room:askForGuanxing(player, cards, top_limit, bottom_limit, customNotif
|
||||||
for i = #top, 1, -1 do
|
for i = #top, 1, -1 do
|
||||||
table.insert(self.draw_pile, 1, top[i])
|
table.insert(self.draw_pile, 1, top[i])
|
||||||
end
|
end
|
||||||
for i = 1, #bottom, 1 do
|
for i = 1, #bottom, -1 do
|
||||||
table.insert(self.draw_pile, bottom[i])
|
table.insert(self.draw_pile, bottom[i])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -2179,7 +2062,7 @@ function Room:askForExchange(player, piles, piles_name, customNotify)
|
||||||
if #piles_name ~= #piles then
|
if #piles_name ~= #piles then
|
||||||
piles_name = {}
|
piles_name = {}
|
||||||
for i, _ in ipairs(piles) do
|
for i, _ in ipairs(piles) do
|
||||||
table.insert(piles_name, Fk:translate("Pile") .. i)
|
table.insert(piles_name, "Pile" .. i)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
self:notifyMoveFocus(player, customNotify or command)
|
self:notifyMoveFocus(player, customNotify or command)
|
||||||
|
@ -2537,7 +2420,6 @@ end
|
||||||
-- Show a qml dialog and return qml's ClientInstance.replyToServer
|
-- Show a qml dialog and return qml's ClientInstance.replyToServer
|
||||||
-- Do anything you like through this function
|
-- Do anything you like through this function
|
||||||
|
|
||||||
-- 调用一个自定义对话框,须自备loadData方法
|
|
||||||
---@param player ServerPlayer
|
---@param player ServerPlayer
|
||||||
---@param focustxt string
|
---@param focustxt string
|
||||||
---@param qmlPath string
|
---@param qmlPath string
|
||||||
|
@ -2552,7 +2434,6 @@ function Room:askForCustomDialog(player, focustxt, qmlPath, extra_data)
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
--- 询问移动场上的一张牌
|
|
||||||
---@param player ServerPlayer @ 移动的操作
|
---@param player ServerPlayer @ 移动的操作
|
||||||
---@param targetOne ServerPlayer @ 移动的目标1玩家
|
---@param targetOne ServerPlayer @ 移动的目标1玩家
|
||||||
---@param targetTwo ServerPlayer @ 移动的目标2玩家
|
---@param targetTwo ServerPlayer @ 移动的目标2玩家
|
||||||
|
@ -2850,16 +2731,15 @@ function Room:doCardUseEffect(cardUseEvent)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local target = TargetGroup:getRealTargets(cardUseEvent.tos)[1]
|
if self:getPlayerById(TargetGroup:getRealTargets(cardUseEvent.tos)[1]).dead then
|
||||||
if not (self:getPlayerById(target).dead or table.contains((cardUseEvent.nullifiedTargets or Util.DummyTable), target)) then
|
self:moveCards({
|
||||||
local existingEquipId
|
ids = realCardIds,
|
||||||
if cardUseEvent.toPutSlot and cardUseEvent.toPutSlot:startsWith("#EquipmentChoice") then
|
toArea = Card.DiscardPile,
|
||||||
local index = cardUseEvent.toPutSlot:split(":")[2]
|
moveReason = fk.ReasonPutIntoDiscardPile,
|
||||||
existingEquipId = self:getPlayerById(target):getEquipments(cardUseEvent.card.sub_type)[tonumber(index)]
|
})
|
||||||
elseif not self:getPlayerById(target):hasEmptyEquipSlot(cardUseEvent.card.sub_type) then
|
else
|
||||||
existingEquipId = self:getPlayerById(target):getEquipment(cardUseEvent.card.sub_type)
|
local target = TargetGroup:getRealTargets(cardUseEvent.tos)[1]
|
||||||
end
|
local existingEquipId = self:getPlayerById(target):getEquipment(cardUseEvent.card.sub_type)
|
||||||
|
|
||||||
if existingEquipId then
|
if existingEquipId then
|
||||||
self:moveCards(
|
self:moveCards(
|
||||||
{
|
{
|
||||||
|
@ -2892,7 +2772,7 @@ function Room:doCardUseEffect(cardUseEvent)
|
||||||
end
|
end
|
||||||
|
|
||||||
local target = TargetGroup:getRealTargets(cardUseEvent.tos)[1]
|
local target = TargetGroup:getRealTargets(cardUseEvent.tos)[1]
|
||||||
if not (self:getPlayerById(target).dead or table.contains((cardUseEvent.nullifiedTargets or Util.DummyTable), target)) then
|
if not self:getPlayerById(target).dead then
|
||||||
local findSameCard = false
|
local findSameCard = false
|
||||||
for _, cardId in ipairs(self:getPlayerById(target):getCardIds(Player.Judge)) do
|
for _, cardId in ipairs(self:getPlayerById(target):getCardIds(Player.Judge)) do
|
||||||
if Fk:getCardById(cardId).trueName == cardUseEvent.card.trueName then
|
if Fk:getCardById(cardId).trueName == cardUseEvent.card.trueName then
|
||||||
|
@ -2903,13 +2783,6 @@ function Room:doCardUseEffect(cardUseEvent)
|
||||||
if not findSameCard then
|
if not findSameCard then
|
||||||
if cardUseEvent.card:isVirtual() then
|
if cardUseEvent.card:isVirtual() then
|
||||||
self:getPlayerById(target):addVirtualEquip(cardUseEvent.card)
|
self:getPlayerById(target):addVirtualEquip(cardUseEvent.card)
|
||||||
elseif cardUseEvent.card.name ~= Fk:getCardById(cardUseEvent.card.id, true).name then
|
|
||||||
local card = Fk:cloneCard(cardUseEvent.card.name)
|
|
||||||
card.skillNames = cardUseEvent.card.skillNames
|
|
||||||
card:addSubcard(cardUseEvent.card.id)
|
|
||||||
self:getPlayerById(target):addVirtualEquip(card)
|
|
||||||
else
|
|
||||||
self:getPlayerById(target):removeVirtualEquip(cardUseEvent.card.id)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
self:moveCards({
|
self:moveCards({
|
||||||
|
@ -2923,6 +2796,12 @@ function Room:doCardUseEffect(cardUseEvent)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
self:moveCards({
|
||||||
|
ids = realCardIds,
|
||||||
|
toArea = Card.DiscardPile,
|
||||||
|
moveReason = fk.ReasonPutIntoDiscardPile,
|
||||||
|
})
|
||||||
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -3212,16 +3091,34 @@ end
|
||||||
|
|
||||||
--- 让一名玩家获得一张牌
|
--- 让一名玩家获得一张牌
|
||||||
---@param player integer|ServerPlayer @ 要拿牌的玩家
|
---@param player integer|ServerPlayer @ 要拿牌的玩家
|
||||||
---@param card integer|integer[]|Card|Card[] @ 要拿到的卡牌
|
---@param cid integer|Card|integer[] @ 要拿到的卡牌
|
||||||
---@param unhide? boolean @ 是否明着拿
|
---@param unhide? boolean @ 是否明着拿
|
||||||
---@param reason? CardMoveReason @ 卡牌移动的原因
|
---@param reason? CardMoveReason @ 卡牌移动的原因
|
||||||
---@param proposer? integer @ 移动操作者的id
|
---@param proposer? integer @ 移动操作者的id
|
||||||
---@param skill_name? string @ 技能名
|
function Room:obtainCard(player, cid, unhide, reason, proposer)
|
||||||
---@param moveMark? table|string @ 移动后自动赋予标记,格式:{标记名(支持-inarea后缀,移出值代表区域后清除), 值}
|
if type(cid) ~= "number" then
|
||||||
---@param visiblePlayers? integer|integer[] @ 控制移动对特定角色可见(在moveVisible为false时生效)
|
assert(cid and type(cid) == "table")
|
||||||
function Room:obtainCard(player, card, unhide, reason, proposer, skill_name, moveMark, visiblePlayers)
|
if cid[1] == nil then
|
||||||
local pid = type(player) == "number" and player or player.id
|
cid = cid:isVirtual() and cid.subcards or {cid.id}
|
||||||
self:moveCardTo(card, Card.PlayerHand, player, reason, skill_name, nil, unhide, proposer or pid, moveMark, visiblePlayers)
|
end
|
||||||
|
else
|
||||||
|
cid = {cid}
|
||||||
|
end
|
||||||
|
if #cid == 0 then return end
|
||||||
|
|
||||||
|
if type(player) == "table" then
|
||||||
|
player = player.id
|
||||||
|
end
|
||||||
|
|
||||||
|
self:moveCards({
|
||||||
|
ids = cid,
|
||||||
|
from = self.owner_map[cid[1]],
|
||||||
|
to = player,
|
||||||
|
toArea = Card.PlayerHand,
|
||||||
|
moveReason = reason or fk.ReasonJustMove,
|
||||||
|
proposer = proposer or player,
|
||||||
|
moveVisible = unhide or false,
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
--- 让玩家摸牌
|
--- 让玩家摸牌
|
||||||
|
@ -3229,9 +3126,8 @@ end
|
||||||
---@param num integer @ 摸牌数
|
---@param num integer @ 摸牌数
|
||||||
---@param skillName? string @ 技能名
|
---@param skillName? string @ 技能名
|
||||||
---@param fromPlace? string @ 摸牌的位置,"top" 或者 "bottom"
|
---@param fromPlace? string @ 摸牌的位置,"top" 或者 "bottom"
|
||||||
---@param moveMark? table|string @ 移动后自动赋予标记,格式:{标记名(支持-inarea后缀,移出值代表区域后清除), 值}
|
|
||||||
---@return integer[] @ 摸到的牌
|
---@return integer[] @ 摸到的牌
|
||||||
function Room:drawCards(player, num, skillName, fromPlace, moveMark)
|
function Room:drawCards(player, num, skillName, fromPlace)
|
||||||
local drawData = {
|
local drawData = {
|
||||||
who = player,
|
who = player,
|
||||||
num = num,
|
num = num,
|
||||||
|
@ -3254,7 +3150,6 @@ function Room:drawCards(player, num, skillName, fromPlace, moveMark)
|
||||||
moveReason = fk.ReasonDraw,
|
moveReason = fk.ReasonDraw,
|
||||||
proposer = player.id,
|
proposer = player.id,
|
||||||
skillName = skillName,
|
skillName = skillName,
|
||||||
moveMark = moveMark,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return { table.unpack(topCards) }
|
return { table.unpack(topCards) }
|
||||||
|
@ -3263,15 +3158,13 @@ end
|
||||||
--- 将一张或多张牌移动到某处
|
--- 将一张或多张牌移动到某处
|
||||||
---@param card integer | integer[] | Card | Card[] @ 要移动的牌
|
---@param card integer | integer[] | Card | Card[] @ 要移动的牌
|
||||||
---@param to_place integer @ 移动的目标位置
|
---@param to_place integer @ 移动的目标位置
|
||||||
---@param target? ServerPlayer|integer @ 移动的目标角色
|
---@param target? ServerPlayer @ 移动的目标角色
|
||||||
---@param reason? integer @ 移动时使用的移牌原因
|
---@param reason? integer @ 移动时使用的移牌原因
|
||||||
---@param skill_name? string @ 技能名
|
---@param skill_name? string @ 技能名
|
||||||
---@param special_name? string @ 私人牌堆名
|
---@param special_name? string @ 私人牌堆名
|
||||||
---@param visible? boolean @ 是否明置
|
---@param visible? boolean @ 是否明置
|
||||||
---@param proposer? integer @ 移动操作者的id
|
---@param proposer? integer @ 移动操作者的id
|
||||||
---@param moveMark? table|string @ 移动后自动赋予标记,格式:{标记名(支持-inarea后缀,移出值代表区域后清除), 值}
|
function Room:moveCardTo(card, to_place, target, reason, skill_name, special_name, visible, proposer)
|
||||||
---@param visiblePlayers? integer|integer[] @ 控制移动对特定角色可见(在moveVisible为false时生效)
|
|
||||||
function Room:moveCardTo(card, to_place, target, reason, skill_name, special_name, visible, proposer, moveMark, visiblePlayers)
|
|
||||||
reason = reason or fk.ReasonJustMove
|
reason = reason or fk.ReasonJustMove
|
||||||
skill_name = skill_name or ""
|
skill_name = skill_name or ""
|
||||||
special_name = special_name or ""
|
special_name = special_name or ""
|
||||||
|
@ -3281,12 +3174,7 @@ function Room:moveCardTo(card, to_place, target, reason, skill_name, special_nam
|
||||||
if table.contains(
|
if table.contains(
|
||||||
{Card.PlayerEquip, Card.PlayerHand,
|
{Card.PlayerEquip, Card.PlayerHand,
|
||||||
Card.PlayerJudge, Card.PlayerSpecial}, to_place) then
|
Card.PlayerJudge, Card.PlayerSpecial}, to_place) then
|
||||||
assert(target)
|
to = target.id
|
||||||
if type(target) == "number" then
|
|
||||||
to = target
|
|
||||||
else
|
|
||||||
to = target.id
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local movesSplitedByOwner = {}
|
local movesSplitedByOwner = {}
|
||||||
|
@ -3308,8 +3196,6 @@ function Room:moveCardTo(card, to_place, target, reason, skill_name, special_nam
|
||||||
specialName = special_name,
|
specialName = special_name,
|
||||||
moveVisible = visible,
|
moveVisible = visible,
|
||||||
proposer = proposer,
|
proposer = proposer,
|
||||||
moveMark = moveMark,
|
|
||||||
visiblePlayers = visiblePlayers,
|
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -3673,10 +3559,6 @@ function Room:recastCard(card_ids, who, skillName)
|
||||||
moveReason = fk.ReasonRecast,
|
moveReason = fk.ReasonRecast,
|
||||||
proposer = who.id
|
proposer = who.id
|
||||||
})
|
})
|
||||||
self:sendFootnote(card_ids, {
|
|
||||||
type = "##RecastCard",
|
|
||||||
from = who.id,
|
|
||||||
})
|
|
||||||
self:broadcastPlaySound("./audio/system/recast")
|
self:broadcastPlaySound("./audio/system/recast")
|
||||||
self:sendLog{
|
self:sendLog{
|
||||||
type = skillName == "recast" and "#Recast" or "#RecastBySkill",
|
type = skillName == "recast" and "#Recast" or "#RecastBySkill",
|
||||||
|
@ -3830,7 +3712,6 @@ end
|
||||||
---@param winner string @ 获胜的身份,空字符串表示平局
|
---@param winner string @ 获胜的身份,空字符串表示平局
|
||||||
function Room:gameOver(winner)
|
function Room:gameOver(winner)
|
||||||
if not self.game_started then return end
|
if not self.game_started then return end
|
||||||
self.room:destroyRequestTimer()
|
|
||||||
|
|
||||||
if table.contains(
|
if table.contains(
|
||||||
{ "running", "normal" },
|
{ "running", "normal" },
|
||||||
|
@ -3846,7 +3727,6 @@ function Room:gameOver(winner)
|
||||||
self:broadcastProperty(p, "role")
|
self:broadcastProperty(p, "role")
|
||||||
end
|
end
|
||||||
self:doBroadcastNotify("GameOver", winner)
|
self:doBroadcastNotify("GameOver", winner)
|
||||||
fk.qInfo(string.format("[GameOver] %d, %s, %s, in %ds", self.id, self.settings.gameMode, winner, os.time() - self.start_time))
|
|
||||||
|
|
||||||
if shouldUpdateWinRate(self) then
|
if shouldUpdateWinRate(self) then
|
||||||
for _, p in ipairs(self.players) do
|
for _, p in ipairs(self.players) do
|
||||||
|
@ -4110,52 +3990,6 @@ function Room:resumePlayerArea(player, playerSlots)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param player ServerPlayer
|
|
||||||
---@param playerSlots string | string[]
|
|
||||||
function Room:addPlayerEquipSlots(player, playerSlots)
|
|
||||||
assert(type(playerSlots) == "string" or type(playerSlots) == "table")
|
|
||||||
|
|
||||||
if type(playerSlots) == "string" then
|
|
||||||
playerSlots = { playerSlots }
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, slot in ipairs(playerSlots) do
|
|
||||||
local slotIndex = table.indexOf(player.equipSlots, slot)
|
|
||||||
if slotIndex > -1 then
|
|
||||||
table.insert(player.equipSlots, slotIndex, slot)
|
|
||||||
else
|
|
||||||
table.insert(player.equipSlots, slot)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
self:broadcastProperty(player, "equipSlots")
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param player ServerPlayer
|
|
||||||
---@param playerSlots string | string[]
|
|
||||||
function Room:removePlayerEquipSlots(player, playerSlots)
|
|
||||||
assert(type(playerSlots) == "string" or type(playerSlots) == "table")
|
|
||||||
|
|
||||||
if type(playerSlots) == "string" then
|
|
||||||
playerSlots = { playerSlots }
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, slot in ipairs(playerSlots) do
|
|
||||||
table.removeOne(player.equipSlots, slot)
|
|
||||||
end
|
|
||||||
|
|
||||||
self:broadcastProperty(player, "equipSlots")
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param player ServerPlayer
|
|
||||||
---@param playerSlots string[]
|
|
||||||
function Room:setPlayerEquipSlots(player, playerSlots)
|
|
||||||
assert(type(playerSlots) == "table")
|
|
||||||
player.equipSlots = playerSlots
|
|
||||||
|
|
||||||
self:broadcastProperty(player, "equipSlots")
|
|
||||||
end
|
|
||||||
|
|
||||||
--- 设置休整
|
--- 设置休整
|
||||||
---@param player ServerPlayer
|
---@param player ServerPlayer
|
||||||
---@param roundNum integer
|
---@param roundNum integer
|
||||||
|
|
|
@ -2,19 +2,49 @@
|
||||||
|
|
||||||
local Room = require "server.room"
|
local Room = require "server.room"
|
||||||
|
|
||||||
|
--[[
|
||||||
|
local verbose = function(...)
|
||||||
|
printf(...)
|
||||||
|
end
|
||||||
|
--]]
|
||||||
|
|
||||||
-- 所有当前正在运行的房间(即游戏尚未结束的房间)
|
-- 所有当前正在运行的房间(即游戏尚未结束的房间)
|
||||||
---@type table<integer, Room>
|
---@type table<integer, Room>
|
||||||
local runningRooms = {}
|
local runningRooms = {}
|
||||||
|
|
||||||
|
-- 所有处于就绪态的房间,以及request协程(如果就绪的话)
|
||||||
|
---@type Room[]
|
||||||
|
local readyRooms = {}
|
||||||
|
|
||||||
|
local requestCo = coroutine.create(function(room)
|
||||||
|
require "server.request"(room)
|
||||||
|
end)
|
||||||
|
|
||||||
-- 仿照Room接口编写的request协程处理器
|
-- 仿照Room接口编写的request协程处理器
|
||||||
local requestRoom = setmetatable({
|
local requestRoom = setmetatable({
|
||||||
id = -1,
|
id = -1,
|
||||||
runningRooms = runningRooms,
|
runningRooms = runningRooms,
|
||||||
|
|
||||||
|
-- minDelayTime 是当没有任何就绪房间时,可以睡眠的时间。
|
||||||
|
-- 因为这个时间是所有房间预期就绪用时的最小值,故称为minDelayTime。
|
||||||
|
minDelayTime = -1,
|
||||||
|
|
||||||
getRoom = function(_, roomId)
|
getRoom = function(_, roomId)
|
||||||
return runningRooms[roomId]
|
return runningRooms[roomId]
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
resume = function(self)
|
||||||
|
local err, msg = coroutine.resume(requestCo, self)
|
||||||
|
if err == false then
|
||||||
|
fk.qCritical(msg .. "\n" .. debug.traceback(requestCo))
|
||||||
|
end
|
||||||
|
return nil, 0
|
||||||
|
end,
|
||||||
|
|
||||||
|
isReady = function(self)
|
||||||
|
return self.thread:hasRequest()
|
||||||
|
end,
|
||||||
|
|
||||||
registerRoom = function(self, id)
|
registerRoom = function(self, id)
|
||||||
local cRoom = self.thread:getRoom(id)
|
local cRoom = self.thread:getRoom(id)
|
||||||
local room = Room:new(cRoom)
|
local room = Room:new(cRoom)
|
||||||
|
@ -29,44 +59,116 @@ local requestRoom = setmetatable({
|
||||||
|
|
||||||
runningRooms[-1] = requestRoom
|
runningRooms[-1] = requestRoom
|
||||||
|
|
||||||
|
-- 从所有运行中房间中挑出就绪的房间。
|
||||||
|
-- 方法暂时就是最简单的遍历。
|
||||||
|
local function refreshReadyRooms()
|
||||||
|
-- verbose '[+] Refreshing ready queue...'
|
||||||
|
for k, v in pairs(runningRooms) do
|
||||||
|
local ready, rest = v:isReady()
|
||||||
|
if ready then
|
||||||
|
table.insertIfNeed(readyRooms, v)
|
||||||
|
elseif rest and rest >= 0 then
|
||||||
|
local time = requestRoom.minDelayTime
|
||||||
|
time = math.min((time <= 0 and 9999999 or time), rest)
|
||||||
|
requestRoom.minDelayTime = math.ceil(time)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- verbose('[+] now have %d ready rooms...', #readyRooms)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 主循环。只要线程没有被杀掉,就一直循环下去。
|
||||||
|
-- 函数每轮循环会从队列中取一个元素并交给控制权,
|
||||||
|
-- 如果没有,则尝试刷新队列,无法刷新则开始睡眠。
|
||||||
|
local function mainLoop()
|
||||||
|
-- request协程的专用特判变量。因为处理request不应当重置睡眠时长
|
||||||
|
local rest_sleep_time
|
||||||
|
|
||||||
|
while not requestRoom.thread:isTerminated() do
|
||||||
|
local room = table.remove(readyRooms, 1)
|
||||||
|
if room then
|
||||||
|
-- verbose '============= LOOP =============='
|
||||||
|
-- verbose('[*] Switching to %s...', tostring(room))
|
||||||
|
|
||||||
|
RoomInstance = (room ~= requestRoom and room or nil)
|
||||||
|
local over, rest = room:resume()
|
||||||
|
RoomInstance = nil
|
||||||
|
|
||||||
|
if over then
|
||||||
|
-- verbose('[#] %s is finished, removing ...', tostring(room))
|
||||||
|
for _, e in ipairs(room.logic.game_event_stack.t) do
|
||||||
|
coroutine.close(e._co)
|
||||||
|
end
|
||||||
|
for _, e in ipairs(room.logic.cleaner_stack.t) do
|
||||||
|
coroutine.close(e._co)
|
||||||
|
end
|
||||||
|
room.logic = nil
|
||||||
|
runningRooms[room.id] = nil
|
||||||
|
else
|
||||||
|
local time = requestRoom.minDelayTime
|
||||||
|
if room == requestRoom then
|
||||||
|
rest = rest_sleep_time
|
||||||
|
end
|
||||||
|
|
||||||
|
if rest and rest >= 0 then
|
||||||
|
time = math.min((time <= 0 and 9999999 or time), rest)
|
||||||
|
else
|
||||||
|
time = -1
|
||||||
|
end
|
||||||
|
requestRoom.minDelayTime = math.ceil(time)
|
||||||
|
-- verbose("[+] minDelay is %d ms...", requestRoom.minDelayTime)
|
||||||
|
-- verbose('[-] %s successfully yielded, %d ready rooms left...',
|
||||||
|
-- tostring(room), #readyRooms)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
refreshReadyRooms()
|
||||||
|
if #readyRooms == 0 then
|
||||||
|
refreshReadyRooms()
|
||||||
|
if #readyRooms == 0 then
|
||||||
|
local time = requestRoom.minDelayTime
|
||||||
|
-- verbose('[.] Sleeping for %d ms...', time)
|
||||||
|
local cur = os.getms()
|
||||||
|
|
||||||
|
time = math.min((time <= 0 and 9999999 or time), 200)
|
||||||
|
|
||||||
|
-- 调用RoomThread的trySleep函数开始真正的睡眠。会被wakeUp(c++)唤醒。
|
||||||
|
requestRoom.thread:trySleep(time)
|
||||||
|
local runningRoomsCount = -1 -- 必有requestRoom,从-1开始算
|
||||||
|
for _ in pairs(runningRooms) do
|
||||||
|
runningRoomsCount = runningRoomsCount + 1
|
||||||
|
if runningRoomsCount > 0 then break end
|
||||||
|
end
|
||||||
|
if runningRoomsCount == 0 and requestRoom.thread:isOutdated() then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
-- verbose('[!] Waked up after %f ms...', (os.getms() - cur) / 1000)
|
||||||
|
|
||||||
|
if time > 0 then
|
||||||
|
rest_sleep_time = math.floor(time - (os.getms() - cur) / 1000)
|
||||||
|
else
|
||||||
|
rest_sleep_time = -1
|
||||||
|
end
|
||||||
|
|
||||||
|
requestRoom.minDelayTime = -1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- verbose '=========== LOOP END ============'
|
||||||
|
-- verbose '[:)] Goodbye!'
|
||||||
|
end
|
||||||
|
|
||||||
-- 当Cpp侧的RoomThread运行时,以下这个函数就是这个线程的主函数。
|
-- 当Cpp侧的RoomThread运行时,以下这个函数就是这个线程的主函数。
|
||||||
-- 而这个函数里面又调用了上面的mainLoop。
|
-- 而这个函数里面又调用了上面的mainLoop。
|
||||||
function InitScheduler(_thread)
|
function InitScheduler(_thread)
|
||||||
requestRoom.thread = _thread
|
requestRoom.thread = _thread
|
||||||
-- Pcall(mainLoop)
|
Pcall(mainLoop)
|
||||||
end
|
end
|
||||||
|
|
||||||
function IsConsoleStart()
|
function IsConsoleStart()
|
||||||
return requestRoom.thread:isConsoleStart()
|
return requestRoom.thread:isConsoleStart()
|
||||||
end
|
end
|
||||||
|
|
||||||
local Req = require "server.request"
|
|
||||||
function HandleRequest(req)
|
|
||||||
Req(requestRoom, req)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function ResumeRoom(roomId)
|
|
||||||
local room = requestRoom:getRoom(roomId)
|
|
||||||
if not room then return false end
|
|
||||||
if not room:isReady() then return false end
|
|
||||||
RoomInstance = (room ~= requestRoom and room or nil)
|
|
||||||
local over = room:resume()
|
|
||||||
RoomInstance = nil
|
|
||||||
|
|
||||||
if over then
|
|
||||||
for _, e in ipairs(room.logic.game_event_stack.t) do
|
|
||||||
coroutine.close(e._co)
|
|
||||||
end
|
|
||||||
for _, e in ipairs(room.logic.cleaner_stack.t) do
|
|
||||||
coroutine.close(e._co)
|
|
||||||
end
|
|
||||||
room.logic = nil
|
|
||||||
runningRooms[room.id] = nil
|
|
||||||
end
|
|
||||||
return over
|
|
||||||
end
|
|
||||||
|
|
||||||
if FileIO.pwd():endsWith("packages/freekill-core") then
|
if FileIO.pwd():endsWith("packages/freekill-core") then
|
||||||
FileIO.cd("../..")
|
FileIO.cd("../..")
|
||||||
end
|
end
|
||||||
|
|
|
@ -53,25 +53,17 @@ end
|
||||||
---@param command string
|
---@param command string
|
||||||
---@param jsonData string
|
---@param jsonData string
|
||||||
function ServerPlayer:doNotify(command, jsonData)
|
function ServerPlayer:doNotify(command, jsonData)
|
||||||
local room = self.room
|
|
||||||
for _, p in ipairs(self._observers) do
|
for _, p in ipairs(self._observers) do
|
||||||
if p:getState() ~= fk.Player_Robot then
|
|
||||||
room.notify_count = room.notify_count + 1
|
|
||||||
end
|
|
||||||
p:doNotify(command, jsonData)
|
p:doNotify(command, jsonData)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local room = self.room
|
||||||
for _, t in ipairs(room.observers) do
|
for _, t in ipairs(room.observers) do
|
||||||
local id, p = table.unpack(t)
|
local id, p = table.unpack(t)
|
||||||
if id == self.id and room.room:hasObserver(p) then
|
if id == self.id and room.room:hasObserver(p) then
|
||||||
p:doNotify(command, jsonData)
|
p:doNotify(command, jsonData)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if room.notify_count >= room.notify_max and
|
|
||||||
coroutine.status(room.main_co) == "normal" then
|
|
||||||
room:delay(100)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Send a request to client, and allow client to reply within *timeout* seconds.
|
--- Send a request to client, and allow client to reply within *timeout* seconds.
|
||||||
|
@ -163,16 +155,6 @@ local function _waitForReply(player, timeout)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- 发送一句聊天
|
|
||||||
---@param msg string
|
|
||||||
function ServerPlayer:chat(msg)
|
|
||||||
self.room:doBroadcastNotify("Chat", json.encode {
|
|
||||||
type = 2,
|
|
||||||
sender = self.id,
|
|
||||||
msg = msg,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Wait for at most *timeout* seconds for reply from client.
|
--- Wait for at most *timeout* seconds for reply from client.
|
||||||
---
|
---
|
||||||
--- If *timeout* is negative or **nil**, the function will wait forever until get reply.
|
--- If *timeout* is negative or **nil**, the function will wait forever until get reply.
|
||||||
|
@ -387,7 +369,7 @@ function ServerPlayer:changePhase(from_phase, to_phase)
|
||||||
table.remove(self.phases, 1)
|
table.remove(self.phases, 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
GameEvent.Phase:create(self, self.phase):exec()
|
GameEvent(GameEvent.Phase, self, self.phase):exec()
|
||||||
|
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
@ -430,7 +412,7 @@ function ServerPlayer:gainAnExtraPhase(phase, delay)
|
||||||
arg = phase_name_table[phase],
|
arg = phase_name_table[phase],
|
||||||
}
|
}
|
||||||
|
|
||||||
GameEvent.Phase:create(self, self.phase):exec()
|
GameEvent(GameEvent.Phase, self, self.phase):exec()
|
||||||
|
|
||||||
phase_change = {
|
phase_change = {
|
||||||
from = phase,
|
from = phase,
|
||||||
|
@ -509,7 +491,7 @@ function ServerPlayer:play(phase_table)
|
||||||
end
|
end
|
||||||
|
|
||||||
if (not skip) or (cancel_skip) then
|
if (not skip) or (cancel_skip) then
|
||||||
GameEvent.Phase:create(self, self.phase):exec()
|
GameEvent(GameEvent.Phase, self, self.phase):exec()
|
||||||
else
|
else
|
||||||
room:sendLog{
|
room:sendLog{
|
||||||
type = "#PhaseSkipped",
|
type = "#PhaseSkipped",
|
||||||
|
@ -572,7 +554,7 @@ function ServerPlayer:gainAnExtraTurn(delay, skillName)
|
||||||
local ex_tag = self.tag["_extra_turn_count"]
|
local ex_tag = self.tag["_extra_turn_count"]
|
||||||
table.insert(ex_tag, skillName)
|
table.insert(ex_tag, skillName)
|
||||||
|
|
||||||
GameEvent.Turn:create(self):exec()
|
GameEvent(GameEvent.Turn, self):exec()
|
||||||
|
|
||||||
table.remove(ex_tag)
|
table.remove(ex_tag)
|
||||||
|
|
||||||
|
@ -599,21 +581,18 @@ end
|
||||||
---@param num integer @ 摸牌数
|
---@param num integer @ 摸牌数
|
||||||
---@param skillName? string @ 技能名
|
---@param skillName? string @ 技能名
|
||||||
---@param fromPlace? string @ 摸牌的位置,"top" 或者 "bottom"
|
---@param fromPlace? string @ 摸牌的位置,"top" 或者 "bottom"
|
||||||
---@param moveMark? table|string @ 移动后自动赋予标记,格式:{标记名(支持-inarea后缀,移出值代表区域后清除), 值}
|
|
||||||
---@return integer[] @ 摸到的牌
|
---@return integer[] @ 摸到的牌
|
||||||
function ServerPlayer:drawCards(num, skillName, fromPlace, moveMark)
|
function ServerPlayer:drawCards(num, skillName, fromPlace)
|
||||||
return self.room:drawCards(self, num, skillName, fromPlace, moveMark)
|
return self.room:drawCards(self, num, skillName, fromPlace)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param pile_name string
|
---@param pile_name string
|
||||||
---@param card integer | integer[] | Card | Card[]
|
---@param card integer|Card
|
||||||
---@param visible? boolean
|
---@param visible? boolean
|
||||||
---@param skillName? string
|
---@param skillName? string
|
||||||
---@param proposer? integer
|
function ServerPlayer:addToPile(pile_name, card, visible, skillName)
|
||||||
---@param visiblePlayers? integer | integer[] @ 为nil时默认对自己可见
|
local room = self.room
|
||||||
function ServerPlayer:addToPile(pile_name, card, visible, skillName, proposer, visiblePlayers)
|
room:moveCardTo(card, Card.PlayerSpecial, self, fk.ReasonJustMove, skillName, pile_name, visible)
|
||||||
self.room:moveCardTo(card, Card.PlayerSpecial, self, fk.ReasonJustMove, skillName, pile_name, visible,
|
|
||||||
proposer or self.id, nil, visiblePlayers)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function ServerPlayer:bury()
|
function ServerPlayer:bury()
|
||||||
|
@ -658,7 +637,6 @@ function ServerPlayer:clearPiles()
|
||||||
end
|
end
|
||||||
|
|
||||||
function ServerPlayer:addVirtualEquip(card)
|
function ServerPlayer:addVirtualEquip(card)
|
||||||
self:removeVirtualEquip(card:getEffectiveId())
|
|
||||||
Player.addVirtualEquip(self, card)
|
Player.addVirtualEquip(self, card)
|
||||||
self.room:doBroadcastNotify("AddVirtualEquip", json.encode{
|
self.room:doBroadcastNotify("AddVirtualEquip", json.encode{
|
||||||
player = self.id,
|
player = self.id,
|
||||||
|
@ -669,12 +647,10 @@ end
|
||||||
|
|
||||||
function ServerPlayer:removeVirtualEquip(cid)
|
function ServerPlayer:removeVirtualEquip(cid)
|
||||||
local ret = Player.removeVirtualEquip(self, cid)
|
local ret = Player.removeVirtualEquip(self, cid)
|
||||||
if ret then
|
self.room:doBroadcastNotify("RemoveVirtualEquip", json.encode{
|
||||||
self.room:doBroadcastNotify("RemoveVirtualEquip", json.encode{
|
player = self.id,
|
||||||
player = self.id,
|
id = cid,
|
||||||
id = cid,
|
})
|
||||||
})
|
|
||||||
end
|
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,7 @@
|
||||||
---@field public specialName? string @ 若终点区域为PlayerSpecial,则存至对应私人牌堆内
|
---@field public specialName? string @ 若终点区域为PlayerSpecial,则存至对应私人牌堆内
|
||||||
---@field public specialVisible? boolean @ 控制上述创建私人牌堆后是否令其可见
|
---@field public specialVisible? boolean @ 控制上述创建私人牌堆后是否令其可见
|
||||||
---@field public drawPilePosition? integer @ 移至牌堆的索引位置,值为-1代表置入牌堆底,或者牌堆牌数+1也为牌堆底
|
---@field public drawPilePosition? integer @ 移至牌堆的索引位置,值为-1代表置入牌堆底,或者牌堆牌数+1也为牌堆底
|
||||||
---@field public moveMark? table|string @ 移动后自动赋予标记,格式:{标记名(支持-inarea后缀,移出值代表区域后清除), 值}
|
---@field public moveMark? table @ 移动后自动赋予标记,格式:{标记名(支持-inarea后缀,移出值代表区域后清除), 值}
|
||||||
---@field public visiblePlayers? integer|integer[] @ 控制移动对特定角色可见(在moveVisible为false时生效)
|
|
||||||
|
|
||||||
--- MoveInfo 一张牌的来源信息
|
--- MoveInfo 一张牌的来源信息
|
||||||
---@class MoveInfo
|
---@class MoveInfo
|
||||||
|
@ -37,8 +36,7 @@
|
||||||
---@field public specialName? string @ 若终点区域为PlayerSpecial,则存至对应私人牌堆内
|
---@field public specialName? string @ 若终点区域为PlayerSpecial,则存至对应私人牌堆内
|
||||||
---@field public specialVisible? boolean @ 控制上述创建私人牌堆后是否令其可见
|
---@field public specialVisible? boolean @ 控制上述创建私人牌堆后是否令其可见
|
||||||
---@field public drawPilePosition? integer @ 移至牌堆的索引位置,值为-1代表置入牌堆底,或者牌堆牌数+1也为牌堆底
|
---@field public drawPilePosition? integer @ 移至牌堆的索引位置,值为-1代表置入牌堆底,或者牌堆牌数+1也为牌堆底
|
||||||
---@field public moveMark? table|string @ 移动后自动赋予标记,格式:{标记名(支持-inarea后缀,移出值代表区域后清除), 值}
|
---@field public moveMark? table @ 移动后自动赋予标记,格式:{标记名(支持-inarea后缀,移出值代表区域后清除), 值}
|
||||||
---@field public visiblePlayers? integer|integer[] @ 控制移动对特定角色可见(在moveVisible为false时生效)
|
|
||||||
|
|
||||||
--- PindianResult 拼点结果
|
--- PindianResult 拼点结果
|
||||||
---@class PindianResult
|
---@class PindianResult
|
||||||
|
|
|
@ -2,21 +2,21 @@ return {
|
||||||
["maneuvering"] = "Maneuvering",
|
["maneuvering"] = "Maneuvering",
|
||||||
|
|
||||||
["thunder__slash"] = "Thunder Slash",
|
["thunder__slash"] = "Thunder Slash",
|
||||||
[":thunder__slash"] = "Thunder Slash (basic card)<br /><b>Phase</b>: Action phase<br /><b>Target</b>: Another player within your ATK range<br /><b>Effect</b>: Deal 1 Thunder DMG to the targets.<br/><b>Note</b>: You can only use 1 Slash per action phase.",
|
[":thunder__slash"] = "Thunder Slash (basic card)<br /><b>Phase</b>: Action phase<br /><b>Target</b>: Another player within your ATK range<br /><b>Effect</b>: Deal 1 Thunder DMG to the targets.<br/><b>Note</b>: You can only use 1 Slash per action phase.",
|
||||||
["#thunder__slash_skill"] = "Choose 1 player within your ATK range, deal 1 Thunder DMG to him",
|
["#thunder__slash_skill"] = "Choose 1 player within your ATK range, deal 1 Thunder DMG to him",
|
||||||
["#thunder__slash_skill_multi"] = "Choose up to %arg players within your ATK range. Deal 1 Thunder DMG to them",
|
["#thunder__slash_skill_multi"] = "Choose up to %arg players within your ATK range. Deal 1 Thunder DMG to them",
|
||||||
|
|
||||||
["fire__slash"] = "Fire Slash",
|
["fire__slash"] = "Fire Slash",
|
||||||
[":fire__slash"] = "Fire Slash (basic card)<br /><b>Phase</b>: Action phase<br /><b>Target</b>: Another player within your ATK range<br /><b>Effect</b>: Deal 1 Fire DMG to the targets.<br/><b>Note</b>: You can only use 1 Slash per action phase.",
|
[":fire__slash"] = "Fire Slash (basic card)<br /><b>Phase</b>: Action phase<br /><b>Target</b>: Another player within your ATK range<br /><b>Effect</b>: Deal 1 Fire DMG to the targets.<br/><b>Note</b>: You can only use 1 Slash per action phase.",
|
||||||
["#fire__slash_skill"] = "Choose 1 player within your ATK range, deal 1 Fire DMG to him",
|
["#fire__slash_skill"] = "Choose 1 player within your ATK range, deal 1 Fire DMG to him",
|
||||||
["#fire__slash_skill_multi"] = "Choose up to %arg players within your ATK range. Deal 1 Fire DMG to them",
|
["#fire__slash_skill_multi"] = "Choose up to %arg players within your ATK range. Deal 1 Fire DMG to them",
|
||||||
|
|
||||||
["analeptic"] = "Alcohol",
|
["analeptic"] = "Alcohol",
|
||||||
[":analeptic"] = "Alcohol (basic card)<br /><b>Phase</b>: 1. Action phase 2. When you are dying<br /><b>Target</b>: Yourself<br /><b>Effect</b>: 1. the DMG of the next Slash you use this turn is increased by +1. (This effect can only be used once per turn) 2. You heal 1 HP.",
|
[":analeptic"] = "Alcohol (basic card)<br /><b>Phase</b>: 1. Action phase 2. When you are dying<br /><b>Target</b>: Yourself<br /><b>Effect</b>: 1. the DMG of the next Slash you use this turn is increased by +1. (This effect can only be used once per turn) 2. You heal 1 HP.",
|
||||||
["#analeptic_skill"] = "the DMG of the next Slash you use this turn is increased by +1",
|
["#analeptic_skill"] = "the DMG of the next Slash you use this turn is increased by +1",
|
||||||
|
|
||||||
["iron_chain"] = "Iron Chain",
|
["iron_chain"] = "Iron Chain",
|
||||||
[":iron_chain"] = "Iron Chain (trick card)<br /><b>Phase</b>: Action phase<br /><b>Target</b>: 1~2 players<br /><b>Effect</b>: Change chain state of the targets.",
|
[":iron_chain"] = "Iron Chain (trick card)<br /><b>Phase</b>: Action phase<br /><b>Target</b>: 1~2 players<br /><b>Effect</b>: Change chain state of the targets.",
|
||||||
["#iron_chain_skill"] = "Choose 1~2 players. Change their chain states",
|
["#iron_chain_skill"] = "Choose 1~2 players. Change their chain states",
|
||||||
["_normal_use"] = "Normally use",
|
["_normal_use"] = "Normally use",
|
||||||
["recast"] = "Recast",
|
["recast"] = "Recast",
|
||||||
|
@ -25,29 +25,29 @@ return {
|
||||||
|
|
||||||
["fire_attack"] = "Fire Attack",
|
["fire_attack"] = "Fire Attack",
|
||||||
["fire_attack_skill"] = "Fire Attack",
|
["fire_attack_skill"] = "Fire Attack",
|
||||||
[":fire_attack"] = "Fire Attack (trick card)<br /><b>Phase</b>: Action phase<br /><b>Target</b>: A player with hand cards<br /><b>Effect</b>: The target player shows 1 hand card; then, if you discard 1 card with the same suit, you deal 1 Fire DMG to him.",
|
[":fire_attack"] = "Fire Attack (trick card)<br /><b>Phase</b>: Action phase<br /><b>Target</b>: A player with hand cards<br /><b>Effect</b>: The target player shows 1 hand card; then, if you discard 1 card with the same suit, you deal 1 Fire DMG to him.",
|
||||||
["#fire_attack-show"] = "%src used Fire Attack to you, please show 1 hand card",
|
["#fire_attack-show"] = "%src used Fire Attack to you, please show 1 hand card",
|
||||||
["#fire_attack-discard"] = "You can discard 1 %arg hand card, then deal 1 Fire DMG to %src",
|
["#fire_attack-discard"] = "You can discard 1 %arg hand card, then deal 1 Fire DMG to %src",
|
||||||
["#fire_attack_skill"] = "Choose a player with hand cards. He shows 1 hand card;<br />then, if you discard 1 card with the same suit, you deal 1 Fire DMG to him",
|
["#fire_attack_skill"] = "Choose a player with hand cards. He shows 1 hand card;<br />then, if you discard 1 card with the same suit, you deal 1 Fire DMG to him",
|
||||||
|
|
||||||
["supply_shortage"] = "Supply Shortage",
|
["supply_shortage"] = "Supply Shortage",
|
||||||
[":supply_shortage"] = "Supply Shortage (delayed trick card)<br /><b>Phase</b>: Action phase<br /><b>Target</b>: Another player at distance 1<br /><b>Effect</b>: Place this card in target's judgement area. He performs a judgement in his judge phase: if result is not ♣, he skips his draw phase.",
|
[":supply_shortage"] = "Supply Shortage (delayed trick card)<br /><b>Phase</b>: Action phase<br /><b>Target</b>: Another player at distance 1<br /><b>Effect</b>: Place this card in target's judgement area. He performs a judgement in his judge phase: if result is not ♣, he skips his draw phase.",
|
||||||
["#supply_shortage_skill"] = "Place this card in another player's judgement area. He performs a judgement in his judge phase:<br />If result is not ♣, he skips his draw phase",
|
["#supply_shortage_skill"] = "Place this card in another player's judgement area. He performs a judgement in his judge phase:<br />If result is not ♣, he skips his draw phase",
|
||||||
|
|
||||||
["guding_blade"] = "Ancient Scimitar",
|
["guding_blade"] = "Ancient Scimitar",
|
||||||
[":guding_blade"] = "Ancient Scimitar (equip card, weapon)<br /><b>ATK range</b>: 2<br /><b>Weapon skill</b>: When your used Slash is about to cause DMG, if the target player has no hand cards: the DMG is increased by +1.",
|
[":guding_blade"] = "Ancient Scimitar (equip card, weapon)<br /><b>ATK range</b>: 2<br /><b>Weapon skill</b>: When your used Slash is about to cause DMG, if the target player has no hand cards: the DMG is increased by +1.",
|
||||||
["#guding_blade_skill"] = "Ancient Scimitar",
|
["#guding_blade_skill"] = "Ancient Scimitar",
|
||||||
|
|
||||||
["fan"] = "Fan",
|
["fan"] = "Fan",
|
||||||
[":fan"] = "Fan (equip card, weapon)<br /><b>ATK range</b>: 4<br /><b>Weapon skill</b>: You can use any basic Slash as Fire Slash.",
|
[":fan"] = "Fan (equip card, weapon)<br /><b>ATK range</b>: 4<br /><b>Weapon skill</b>: You can use any basic Slash as Fire Slash.",
|
||||||
["#fan_skill"] = "Fan",
|
["#fan_skill"] = "Fan",
|
||||||
|
|
||||||
["vine"] = "Vine",
|
["vine"] = "Vine",
|
||||||
[":vine"] = "Vine (equip card, armor)<br /><b>Armor skill</b>: Savage Assault, Archery Attack and basic Slash have no effect on you. When you are about to suffer Fire DMG, the DMG is increased by +1.",
|
[":vine"] = "Vine (equip card, armor)<br /><b>Armor skill</b>: Savage Assault, Archery Attack and basic Slash have no effect on you. When you are about to suffer Fire DMG, the DMG is increased by +1.",
|
||||||
["#vine_skill"] = "Vine",
|
["#vine_skill"] = "Vine",
|
||||||
|
|
||||||
["silver_lion"] = "Sliver Lion",
|
["silver_lion"] = "Sliver Lion",
|
||||||
[":silver_lion"] = "Sliver Lion (equip card, armor)<br /><b>Armor skill</b>: When you are about to suffer DMG: that DMG is reduced to 1. When you lose this card in your equipment area: you heal 1 HP.",
|
[":silver_lion"] = "Sliver Lion (equip card, armor)<br /><b>Armor skill</b>: When you are about to suffer DMG: that DMG is reduced to 1. When you lose this card in your equipment area: you heal 1 HP.",
|
||||||
["#silver_lion_skill"] = "Sliver Lion",
|
["#silver_lion_skill"] = "Sliver Lion",
|
||||||
|
|
||||||
["hualiu"] = "Hua Liu",
|
["hualiu"] = "Hua Liu",
|
||||||
|
|
|
@ -136,7 +136,7 @@ local analepticSkill = fk.CreateActiveSkill{
|
||||||
card = effect.card,
|
card = effect.card,
|
||||||
})
|
})
|
||||||
else
|
else
|
||||||
to.drank = to.drank + 1 + ((effect.extra_data or {}).additionalDrank or 0)
|
to.drank = to.drank + 1
|
||||||
room:broadcastProperty(to, "drank")
|
room:broadcastProperty(to, "drank")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -497,21 +497,21 @@ Fk:loadTranslationTable{
|
||||||
["maneuvering"] = "军争",
|
["maneuvering"] = "军争",
|
||||||
|
|
||||||
["thunder__slash"] = "雷杀",
|
["thunder__slash"] = "雷杀",
|
||||||
[":thunder__slash"] = "基本牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:攻击范围内的一名角色<br /><b>效果</b>:对目标角色造成1点雷电伤害。",
|
[":thunder__slash"] = "基本牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:攻击范围内的一名角色<br /><b>效果</b>:对目标角色造成1点雷电伤害。",
|
||||||
["#thunder__slash_skill"] = "选择攻击范围内的一名角色,对其造成1点雷电伤害",
|
["#thunder__slash_skill"] = "选择攻击范围内的一名角色,对其造成1点雷电伤害",
|
||||||
["#thunder__slash_skill_multi"] = "选择攻击范围内的至多%arg名角色,对这些角色各造成1点雷电伤害",
|
["#thunder__slash_skill_multi"] = "选择攻击范围内的至多%arg名角色,对这些角色各造成1点雷电伤害",
|
||||||
|
|
||||||
["fire__slash"] = "火杀",
|
["fire__slash"] = "火杀",
|
||||||
[":fire__slash"] = "基本牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:攻击范围内的一名角色<br /><b>效果</b>:对目标角色造成1点火焰伤害。",
|
[":fire__slash"] = "基本牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:攻击范围内的一名角色<br /><b>效果</b>:对目标角色造成1点火焰伤害。",
|
||||||
["#fire__slash_skill"] = "选择攻击范围内的一名角色,对其造成1点火焰伤害",
|
["#fire__slash_skill"] = "选择攻击范围内的一名角色,对其造成1点火焰伤害",
|
||||||
["#fire__slash_skill_multi"] = "选择攻击范围内的至多%arg名角色,对这些角色各造成1点火焰伤害",
|
["#fire__slash_skill_multi"] = "选择攻击范围内的至多%arg名角色,对这些角色各造成1点火焰伤害",
|
||||||
|
|
||||||
["analeptic"] = "酒",
|
["analeptic"] = "酒",
|
||||||
[":analeptic"] = "基本牌<br /><b>时机</b>:出牌阶段/你处于濒死状态时<br /><b>目标</b>:你<br /><b>效果</b>:目标角色本回合使用的下一张【杀】将要造成的伤害+1/目标角色回复1点体力。",
|
[":analeptic"] = "基本牌<br /><b>时机</b>:出牌阶段/你处于濒死状态时<br /><b>目标</b>:你<br /><b>效果</b>:目标角色本回合使用的下一张【杀】将要造成的伤害+1/目标角色回复1点体力。",
|
||||||
["#analeptic_skill"] = "你于此回合内使用的下一张【杀】的伤害值基数+1",
|
["#analeptic_skill"] = "你于此回合内使用的下一张【杀】的伤害值基数+1",
|
||||||
|
|
||||||
["iron_chain"] = "铁锁连环",
|
["iron_chain"] = "铁锁连环",
|
||||||
[":iron_chain"] = "锦囊牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:一至两名角色<br /><b>效果</b>:横置或重置目标角色的武将牌。",
|
[":iron_chain"] = "锦囊牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:一至两名角色<br /><b>效果</b>:横置或重置目标角色的武将牌。",
|
||||||
["#iron_chain_skill"] = "选择一至两名角色,这些角色横置或重置",
|
["#iron_chain_skill"] = "选择一至两名角色,这些角色横置或重置",
|
||||||
["_normal_use"] = "正常使用",
|
["_normal_use"] = "正常使用",
|
||||||
["recast"] = "重铸",
|
["recast"] = "重铸",
|
||||||
|
@ -520,29 +520,29 @@ Fk:loadTranslationTable{
|
||||||
|
|
||||||
["fire_attack"] = "火攻",
|
["fire_attack"] = "火攻",
|
||||||
["fire_attack_skill"] = "火攻",
|
["fire_attack_skill"] = "火攻",
|
||||||
[":fire_attack"] = "锦囊牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:一名有手牌的角色<br /><b>效果</b>:目标角色展示一张手牌,然后你可以弃置一张与此牌花色相同的手牌对其造成1点火焰伤害。",
|
[":fire_attack"] = "锦囊牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:一名有手牌的角色<br /><b>效果</b>:目标角色展示一张手牌,然后你可以弃置一张与此牌花色相同的手牌对其造成1点火焰伤害。",
|
||||||
["#fire_attack-show"] = "%src 对你使用了火攻,请展示一张手牌",
|
["#fire_attack-show"] = "%src 对你使用了火攻,请展示一张手牌",
|
||||||
["#fire_attack-discard"] = "你可弃置一张 %arg 手牌,对 %src 造成1点火属性伤害",
|
["#fire_attack-discard"] = "你可弃置一张 %arg 手牌,对 %src 造成1点火属性伤害",
|
||||||
["#fire_attack_skill"] = "选择一名有手牌的角色,令其展示一张手牌,<br />然后你可以弃置一张与此牌花色相同的手牌对其造成1点火焰伤害",
|
["#fire_attack_skill"] = "选择一名有手牌的角色,令其展示一张手牌,<br />然后你可以弃置一张与此牌花色相同的手牌对其造成1点火焰伤害",
|
||||||
|
|
||||||
["supply_shortage"] = "兵粮寸断",
|
["supply_shortage"] = "兵粮寸断",
|
||||||
[":supply_shortage"] = "延时锦囊牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:距离1的一名其他角色<br /><b>效果</b>:将此牌置于目标角色判定区内。其判定阶段进行判定:若结果不为♣,其跳过摸牌阶段。然后将【兵粮寸断】置入弃牌堆。",
|
[":supply_shortage"] = "延时锦囊牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:距离1的一名其他角色<br /><b>效果</b>:将此牌置于目标角色判定区内。其判定阶段进行判定:若结果不为♣,其跳过摸牌阶段。然后将【兵粮寸断】置入弃牌堆。",
|
||||||
["#supply_shortage_skill"] = "选择距离1的一名角色,将此牌置于其判定区内。其判定阶段判定:<br />若结果不为♣,其跳过摸牌阶段",
|
["#supply_shortage_skill"] = "选择距离1的一名角色,将此牌置于其判定区内。其判定阶段判定:<br />若结果不为♣,其跳过摸牌阶段",
|
||||||
|
|
||||||
["guding_blade"] = "古锭刀",
|
["guding_blade"] = "古锭刀",
|
||||||
[":guding_blade"] = "装备牌·武器<br /><b>攻击范围</b>:2<br /><b>武器技能</b>:锁定技。每当你使用【杀】对目标角色造成伤害时,若该角色没有手牌,此伤害+1。",
|
[":guding_blade"] = "装备牌·武器<br /><b>攻击范围</b>:2<br /><b>武器技能</b>:锁定技。每当你使用【杀】对目标角色造成伤害时,若该角色没有手牌,此伤害+1。",
|
||||||
["#guding_blade_skill"] = "古锭刀",
|
["#guding_blade_skill"] = "古锭刀",
|
||||||
|
|
||||||
["fan"] = "朱雀羽扇",
|
["fan"] = "朱雀羽扇",
|
||||||
[":fan"] = "装备牌·武器<br /><b>攻击范围</b>:4<br /><b>武器技能</b>:当你声明使用普【杀】后,你可以将此【杀】改为火【杀】。",
|
[":fan"] = "装备牌·武器<br /><b>攻击范围</b>:4<br /><b>武器技能</b>:当你声明使用普【杀】后,你可以将此【杀】改为火【杀】。",
|
||||||
["#fan_skill"] = "朱雀羽扇",
|
["#fan_skill"] = "朱雀羽扇",
|
||||||
|
|
||||||
["vine"] = "藤甲",
|
["vine"] = "藤甲",
|
||||||
[":vine"] = "装备牌·防具<br /><b>防具技能</b>:锁定技。【南蛮入侵】、【万箭齐发】和普通【杀】对你无效。每当你受到火焰伤害时,此伤害+1。",
|
[":vine"] = "装备牌·防具<br /><b>防具技能</b>:锁定技。【南蛮入侵】、【万箭齐发】和普通【杀】对你无效。每当你受到火焰伤害时,此伤害+1。",
|
||||||
["#vine_skill"] = "藤甲",
|
["#vine_skill"] = "藤甲",
|
||||||
|
|
||||||
["silver_lion"] = "白银狮子",
|
["silver_lion"] = "白银狮子",
|
||||||
[":silver_lion"] = "装备牌·防具<br /><b>防具技能</b>:锁定技。每当你受到伤害时,若此伤害大于1点,防止多余的伤害。每当你失去装备区里的【白银狮子】后,你回复1点体力。",
|
[":silver_lion"] = "装备牌·防具<br /><b>防具技能</b>:锁定技。每当你受到伤害时,若此伤害大于1点,防止多余的伤害。每当你失去装备区里的【白银狮子】后,你回复1点体力。",
|
||||||
["#silver_lion_skill"] = "白银狮子",
|
["#silver_lion_skill"] = "白银狮子",
|
||||||
|
|
||||||
["hualiu"] = "骅骝",
|
["hualiu"] = "骅骝",
|
||||||
|
|
|
@ -213,7 +213,7 @@ local uncompulsoryInvalidity = fk.CreateInvaliditySkill {
|
||||||
end
|
end
|
||||||
return
|
return
|
||||||
(skill.frequency ~= Skill.Compulsory and skill.frequency ~= Skill.Wake) and
|
(skill.frequency ~= Skill.Compulsory and skill.frequency ~= Skill.Wake) and
|
||||||
skill:isPlayerSkill(from) and
|
not (skill:isEquipmentSkill() or skill.name:endsWith("&")) and
|
||||||
hasMark(from, MarkEnum.UncompulsoryInvalidity, MarkEnum.TempMarkSuffix)
|
hasMark(from, MarkEnum.UncompulsoryInvalidity, MarkEnum.TempMarkSuffix)
|
||||||
-- (
|
-- (
|
||||||
-- from:getMark(MarkEnum.UncompulsoryInvalidity) ~= 0 or
|
-- from:getMark(MarkEnum.UncompulsoryInvalidity) ~= 0 or
|
||||||
|
|
|
@ -53,14 +53,7 @@ GameRule = fk.CreateTriggerSkill{
|
||||||
end)
|
end)
|
||||||
if #cardNames == 0 then return end
|
if #cardNames == 0 then return end
|
||||||
|
|
||||||
local peach_use = room:askForUseCard(
|
local peach_use = room:askForUseCard(player, "peach", table.concat(cardNames, ",") , prompt, true, {analepticRecover = true})
|
||||||
player,
|
|
||||||
"peach",
|
|
||||||
table.concat(cardNames, ","),
|
|
||||||
prompt,
|
|
||||||
true,
|
|
||||||
{analepticRecover = true, must_targets = { dyingPlayer.id }}
|
|
||||||
)
|
|
||||||
if not peach_use then break end
|
if not peach_use then break end
|
||||||
peach_use.tos = { {dyingPlayer.id} }
|
peach_use.tos = { {dyingPlayer.id} }
|
||||||
if peach_use.card.trueName == "analeptic" then
|
if peach_use.card.trueName == "analeptic" then
|
||||||
|
|
|
@ -52,7 +52,6 @@ Fk:loadTranslationTable({
|
||||||
["liubei"] = "Liu Bei",
|
["liubei"] = "Liu Bei",
|
||||||
["rende"] = "Benevolence",
|
["rende"] = "Benevolence",
|
||||||
[":rende"] = "In your Action Phase: you can give any # of hand cards to other players; then, if you have given a total of 2 or more cards, you heal 1 HP (only once).",
|
[":rende"] = "In your Action Phase: you can give any # of hand cards to other players; then, if you have given a total of 2 or more cards, you heal 1 HP (only once).",
|
||||||
["#rende-active"] = "Use Benevolence, give any # of hand cards to other players;<br >then, if you have given a total of 2 or more cards, you heal 1 HP (only once)",
|
|
||||||
["jijiang"] = "Rouse",
|
["jijiang"] = "Rouse",
|
||||||
[":jijiang"] = "(lord) When you need to use/play Slash: you can ask other Shu characters to play Slash, which is regard as you use/play that.",
|
[":jijiang"] = "(lord) When you need to use/play Slash: you can ask other Shu characters to play Slash, which is regard as you use/play that.",
|
||||||
["#jijiang-ask"] = "Rouse: you can play a Slash, which is regarded as %src uses/plays",
|
["#jijiang-ask"] = "Rouse: you can play a Slash, which is regarded as %src uses/plays",
|
||||||
|
@ -90,7 +89,6 @@ Fk:loadTranslationTable({
|
||||||
["sunquan"] = "Sun Quan",
|
["sunquan"] = "Sun Quan",
|
||||||
["zhiheng"] = "Balance of Power",
|
["zhiheng"] = "Balance of Power",
|
||||||
[":zhiheng"] = "Once per Action Phase: you can discard any # of cards; then, draw the same # of cards.",
|
[":zhiheng"] = "Once per Action Phase: you can discard any # of cards; then, draw the same # of cards.",
|
||||||
["#zhiheng-active"] = "Use Balance of Power, discard any # of cards; then, draw the same # of cards",
|
|
||||||
["jiuyuan"] = "Rescued",
|
["jiuyuan"] = "Rescued",
|
||||||
[":jiuyuan"] = "(lord, forced) When another Wu character uses Peach to you, you heal +1 HP.",
|
[":jiuyuan"] = "(lord, forced) When another Wu character uses Peach to you, you heal +1 HP.",
|
||||||
|
|
||||||
|
@ -105,20 +103,18 @@ Fk:loadTranslationTable({
|
||||||
["huanggai"] = "Huang Gai",
|
["huanggai"] = "Huang Gai",
|
||||||
["kurou"] = "Trojan Flesh",
|
["kurou"] = "Trojan Flesh",
|
||||||
[":kurou"] = "In your Action Phase: you can lose 1 HP; then, draw 2 cards.",
|
[":kurou"] = "In your Action Phase: you can lose 1 HP; then, draw 2 cards.",
|
||||||
["#kurou-active"] = "Use Trojan Flesh, lose 1 HP; then, draw 2 cards",
|
|
||||||
|
|
||||||
["zhouyu"] = "Zhou Yu",
|
["zhouyu"] = "Zhou Yu",
|
||||||
["yingzi"] = "Handsome",
|
["yingzi"] = "Handsome",
|
||||||
[":yingzi"] = "In your Draw Phase: you can draw +1 additional card.",
|
[":yingzi"] = "In your Draw Phase: you can draw +1 additional card.",
|
||||||
["fanjian"] = "Sow Dissension",
|
["fanjian"] = "Sow Dissension",
|
||||||
[":fanjian"] = "Once per Action Phase: you can make another player choose 1 suit; then, he takes 1 hand card from you and displays it. If the guess was wrong, you cause him 1 DMG.",
|
[":fanjian"] = "Once per Action Phase: you can make another player choose 1 suit; then, that player takes 1 hand card from you and displays it. If the guess was wrong, you cause him 1 DMG.",
|
||||||
["#fanjian-active"] = "Use Sow Dissension, select another player; he chooses 1 suit;<br />then he takes 1 hand card from you and displays it. If the guess was wrong, you cause him 1 DMG",
|
|
||||||
|
|
||||||
["daqiao"] = "Da Qiao",
|
["daqiao"] = "Da Qiao",
|
||||||
["guose"] = "National Beauty",
|
["guose"] = "National Beauty",
|
||||||
[":guose"] = "You can use any diamond card as Indulgence.",
|
[":guose"] = "You can use any diamond card as Indulgence.",
|
||||||
["liuli"] = "Shirk",
|
["liuli"] = "Shirk",
|
||||||
[":liuli"] = "When you become the target of Slash: you can discard 1 card and select another player (except the attacker) within your attack range; then, he becomes the target of the Slash instead.",
|
[":liuli"] = "When you become the target of Slash: you can discard 1 card and select another player (except the attacker) within your attack range; then, that player becomes the target of the Slash instead.",
|
||||||
["#liuli-target"] = "Shirk: you can discard 1 card and transfer the Slash",
|
["#liuli-target"] = "Shirk: you can discard 1 card and transfer the Slash",
|
||||||
|
|
||||||
["luxun"] = "Lu Xun",
|
["luxun"] = "Lu Xun",
|
||||||
|
@ -132,12 +128,10 @@ Fk:loadTranslationTable({
|
||||||
[":xiaoji"] = "After you lose 1 card in your equipment area: you can draw 2 cards.",
|
[":xiaoji"] = "After you lose 1 card in your equipment area: you can draw 2 cards.",
|
||||||
["jieyin"] = "Marriage",
|
["jieyin"] = "Marriage",
|
||||||
[":jieyin"] = "Once per Action Phase: you can discard 2 hand cards and select a hurt male character; then, both of you heal 1 HP.",
|
[":jieyin"] = "Once per Action Phase: you can discard 2 hand cards and select a hurt male character; then, both of you heal 1 HP.",
|
||||||
["#jieyin-active"] = "Use Marriage, discard 2 hand cards and select a hurt male character; then, both of you heal 1 HP",
|
|
||||||
|
|
||||||
["huatuo"] = "Hua Tuo",
|
["huatuo"] = "Hua Tuo",
|
||||||
["qingnang"] = "Green Salve",
|
["qingnang"] = "Green Salve",
|
||||||
[":qingnang"] = "Once per Action Phase: you can discard 1 hand card and select a wounded player; then, he heals 1 HP.",
|
[":qingnang"] = "Once per Action Phase: you can discard 1 hand card and select a wounded player; then, he heals 1 HP.",
|
||||||
["#qingnang-active"] = "Use Green Salve, discard 1 hand card and select a wounded player; then, he heals 1 HP",
|
|
||||||
["jijiu"] = "First Aid",
|
["jijiu"] = "First Aid",
|
||||||
[":jijiu"] = "Outside of your turn: you can use any red card as Peach.",
|
[":jijiu"] = "Outside of your turn: you can use any red card as Peach.",
|
||||||
|
|
||||||
|
@ -148,7 +142,6 @@ Fk:loadTranslationTable({
|
||||||
["diaochan"] = "Diao Chan",
|
["diaochan"] = "Diao Chan",
|
||||||
["lijian"] = "Seed of Animosity",
|
["lijian"] = "Seed of Animosity",
|
||||||
[":lijian"] = "Once per Action Phase: you may discard 1 card and select 2 male characters; then, this is regarded as one of them having used Duel to target the other. This Duel can't be countered by Nullification.",
|
[":lijian"] = "Once per Action Phase: you may discard 1 card and select 2 male characters; then, this is regarded as one of them having used Duel to target the other. This Duel can't be countered by Nullification.",
|
||||||
["#lijian-active"] = "Use Seed of Animosity, discard 1 card and select 2 male characters;<br />then, this is regarded as one of them having used Duel to target the other.<br />This Duel can't be countered by Nullification",
|
|
||||||
["biyue"] = "Envious by Moon",
|
["biyue"] = "Envious by Moon",
|
||||||
[":biyue"] = "In your Finish Phase, you can draw 1 card.",
|
[":biyue"] = "In your Finish Phase, you can draw 1 card.",
|
||||||
|
|
||||||
|
|
|
@ -100,7 +100,6 @@ Fk:loadTranslationTable{
|
||||||
["$rende2"] = "唯贤唯德,能服于人。",
|
["$rende2"] = "唯贤唯德,能服于人。",
|
||||||
["rende"] = "仁德",
|
["rende"] = "仁德",
|
||||||
[":rende"] = "出牌阶段,你可以将至少一张手牌任意分配给其他角色。你于本阶段内以此法给出的手牌首次达到两张或更多后,你回复1点体力。",
|
[":rende"] = "出牌阶段,你可以将至少一张手牌任意分配给其他角色。你于本阶段内以此法给出的手牌首次达到两张或更多后,你回复1点体力。",
|
||||||
["#rende-active"] = "发动 仁德,将至少一张手牌交给其他角色",
|
|
||||||
["$jijiang1"] = "蜀将何在?",
|
["$jijiang1"] = "蜀将何在?",
|
||||||
["$jijiang2"] = "尔等敢应战否?",
|
["$jijiang2"] = "尔等敢应战否?",
|
||||||
["jijiang"] = "激将",
|
["jijiang"] = "激将",
|
||||||
|
@ -176,8 +175,7 @@ Fk:loadTranslationTable{
|
||||||
["$zhiheng1"] = "容我三思。",
|
["$zhiheng1"] = "容我三思。",
|
||||||
["$zhiheng2"] = "且慢。",
|
["$zhiheng2"] = "且慢。",
|
||||||
["zhiheng"] = "制衡",
|
["zhiheng"] = "制衡",
|
||||||
[":zhiheng"] = "出牌阶段限一次,你可以弃置任意张牌,然后摸等量的牌。",
|
[":zhiheng"] = "出牌阶段限一次,你可以弃置至少一张牌然后摸等量的牌。",
|
||||||
["#zhiheng-active"] = "发动 制衡,弃置任意张牌,然后摸等量的牌",
|
|
||||||
["$jiuyuan1"] = "有汝辅佐,甚好!",
|
["$jiuyuan1"] = "有汝辅佐,甚好!",
|
||||||
["$jiuyuan2"] = "好舒服啊。",
|
["$jiuyuan2"] = "好舒服啊。",
|
||||||
["jiuyuan"] = "救援",
|
["jiuyuan"] = "救援",
|
||||||
|
@ -208,8 +206,7 @@ Fk:loadTranslationTable{
|
||||||
["$kurou1"] = "请鞭笞我吧,公瑾!",
|
["$kurou1"] = "请鞭笞我吧,公瑾!",
|
||||||
["$kurou2"] = "赴汤蹈火,在所不辞!",
|
["$kurou2"] = "赴汤蹈火,在所不辞!",
|
||||||
["kurou"] = "苦肉",
|
["kurou"] = "苦肉",
|
||||||
[":kurou"] = "出牌阶段,你可以失去1点体力,然后摸两张牌。",
|
[":kurou"] = "出牌阶段,你可以失去1点体力然后摸两张牌。",
|
||||||
["#kurou-active"] = "发动 苦肉,失去1点体力,然后摸两张牌",
|
|
||||||
|
|
||||||
["zhouyu"] = "周瑜",
|
["zhouyu"] = "周瑜",
|
||||||
["#zhouyu"] = "大都督",
|
["#zhouyu"] = "大都督",
|
||||||
|
@ -222,8 +219,7 @@ Fk:loadTranslationTable{
|
||||||
["$fanjian1"] = "挣扎吧,在血和暗的深渊里!",
|
["$fanjian1"] = "挣扎吧,在血和暗的深渊里!",
|
||||||
["$fanjian2"] = "痛苦吧,在仇与恨的地狱中!",
|
["$fanjian2"] = "痛苦吧,在仇与恨的地狱中!",
|
||||||
["fanjian"] = "反间",
|
["fanjian"] = "反间",
|
||||||
[":fanjian"] = "出牌阶段限一次,你可以令一名其他角色选择一种花色,然后正面朝上获得你的一张手牌。若此牌花色与其所选花色不同,你对其造成1点伤害。",
|
[":fanjian"] = "阶段技。你可以令一名其他角色选择一种花色,然后正面朝上获得你的一张手牌。若此牌花色与该角色所选花色不同,你对其造成1点伤害。",
|
||||||
["#fanjian-active"] = "发动 反间,选择一名其他角色,令其选择一种花色,然后正面朝上获得你的一张手牌<br />若此牌花色与其所选花色不同,你对其造成1点伤害",
|
|
||||||
|
|
||||||
["daqiao"] = "大乔",
|
["daqiao"] = "大乔",
|
||||||
["#daqiao"] = "矜持之花",
|
["#daqiao"] = "矜持之花",
|
||||||
|
@ -250,7 +246,7 @@ Fk:loadTranslationTable{
|
||||||
["$lianying1"] = "牌不是万能的,但是没牌是万万不能的。",
|
["$lianying1"] = "牌不是万能的,但是没牌是万万不能的。",
|
||||||
["$lianying2"] = "旧的不去,新的不来。",
|
["$lianying2"] = "旧的不去,新的不来。",
|
||||||
["lianying"] = "连营",
|
["lianying"] = "连营",
|
||||||
[":lianying"] = "当你失去手牌后,若你没有手牌,你可以摸一张牌。",
|
[":lianying"] = "当你失去最后的手牌后,你可以摸一张牌。",
|
||||||
|
|
||||||
["sunshangxiang"] = "孙尚香",
|
["sunshangxiang"] = "孙尚香",
|
||||||
["#sunshangxiang"] = "弓腰姬",
|
["#sunshangxiang"] = "弓腰姬",
|
||||||
|
@ -263,8 +259,7 @@ Fk:loadTranslationTable{
|
||||||
["$jieyin1"] = "夫君,身体要紧。",
|
["$jieyin1"] = "夫君,身体要紧。",
|
||||||
["$jieyin2"] = "他好,我也好。",
|
["$jieyin2"] = "他好,我也好。",
|
||||||
["jieyin"] = "结姻",
|
["jieyin"] = "结姻",
|
||||||
[":jieyin"] = "出牌阶段限一次,你可以弃置两张手牌并选择一名已受伤的男性角色,然后你与其各回复1点体力。",
|
[":jieyin"] = "出牌阶段限一次,你可以弃置两张手牌并选择一名已受伤的男性角色:若如此做,你和该角色各回复1点体力。",
|
||||||
["#jieyin-active"] = "发动 结姻,弃置两张手牌并选择一名已受伤的男性角色,你与其各回复1点体力",
|
|
||||||
|
|
||||||
["huatuo"] = "华佗",
|
["huatuo"] = "华佗",
|
||||||
["#huatuo"] = "神医",
|
["#huatuo"] = "神医",
|
||||||
|
@ -273,8 +268,7 @@ Fk:loadTranslationTable{
|
||||||
["$qingnang1"] = "早睡早起,方能养生。",
|
["$qingnang1"] = "早睡早起,方能养生。",
|
||||||
["$qingnang2"] = "越老越要补啊。",
|
["$qingnang2"] = "越老越要补啊。",
|
||||||
["qingnang"] = "青囊",
|
["qingnang"] = "青囊",
|
||||||
[":qingnang"] = "出牌阶段限一次,你可以弃置一张手牌并选择一名已受伤的角色,然后其回复1点体力。",
|
[":qingnang"] = "出牌阶段限一次,你可以弃置一张手牌并选择一名已受伤的角色:若如此做,该角色回复1点体力。",
|
||||||
["#qingnang-active"] = "发动 青囊,弃置一张手牌并选择一名已受伤的角色,其回复1点体力",
|
|
||||||
["$jijiu1"] = "别紧张,有老夫呢。",
|
["$jijiu1"] = "别紧张,有老夫呢。",
|
||||||
["$jijiu2"] = "救人一命,胜造七级浮屠。",
|
["$jijiu2"] = "救人一命,胜造七级浮屠。",
|
||||||
["jijiu"] = "急救",
|
["jijiu"] = "急救",
|
||||||
|
@ -297,7 +291,6 @@ Fk:loadTranslationTable{
|
||||||
["$lijian2"] = "夫君,你要替妾身作主啊……",
|
["$lijian2"] = "夫君,你要替妾身作主啊……",
|
||||||
["lijian"] = "离间",
|
["lijian"] = "离间",
|
||||||
[":lijian"] = "出牌阶段限一次,你可以弃置一张牌并选择两名其他男性角色,后选择的角色视为对先选择的角色使用了一张不能被【无懈可击】的【决斗】。",
|
[":lijian"] = "出牌阶段限一次,你可以弃置一张牌并选择两名其他男性角色,后选择的角色视为对先选择的角色使用了一张不能被【无懈可击】的【决斗】。",
|
||||||
["#lijian-active"] = "发动 离间,弃置一张手牌并选择两名其他男性角色,后选择的角色视为对先选择的角色使用了一张不能被【无懈可击】的【决斗】",
|
|
||||||
["$biyue1"] = "失礼了~",
|
["$biyue1"] = "失礼了~",
|
||||||
["$biyue2"] = "羡慕吧~",
|
["$biyue2"] = "羡慕吧~",
|
||||||
["biyue"] = "闭月",
|
["biyue"] = "闭月",
|
||||||
|
@ -536,7 +529,4 @@ Fk:loadTranslationTable{
|
||||||
["revealDeputy"] = "明置副将 %arg",
|
["revealDeputy"] = "明置副将 %arg",
|
||||||
|
|
||||||
["game_rule"] = "弃牌阶段",
|
["game_rule"] = "弃牌阶段",
|
||||||
["replace_equip"] = "替换装备",
|
|
||||||
["#EquipmentChoice"] = "%arg",
|
|
||||||
["#GameRuleReplaceEquipment"] = "请选择要置入的区域",
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,11 @@ local jianxiong = fk.CreateTriggerSkill{
|
||||||
anim_type = "masochism",
|
anim_type = "masochism",
|
||||||
events = {fk.Damaged},
|
events = {fk.Damaged},
|
||||||
can_trigger = function(self, event, target, player, data)
|
can_trigger = function(self, event, target, player, data)
|
||||||
return target == player and player:hasSkill(self) and data.card and player.room:getCardArea(data.card) == Card.Processing
|
if target == player and player:hasSkill(self) and data.card then
|
||||||
|
local room = player.room
|
||||||
|
local subcards = data.card:isVirtual() and data.card.subcards or {data.card.id}
|
||||||
|
return #subcards>0 and table.every(subcards, function(id) return room:getCardArea(id) == Card.Processing end)
|
||||||
|
end
|
||||||
end,
|
end,
|
||||||
on_use = function(self, event, target, player, data)
|
on_use = function(self, event, target, player, data)
|
||||||
player.room:obtainCard(player.id, data.card, true, fk.ReasonJustMove)
|
player.room:obtainCard(player.id, data.card, true, fk.ReasonJustMove)
|
||||||
|
@ -153,11 +157,11 @@ local tuxi = fk.CreateTriggerSkill{
|
||||||
events = {fk.EventPhaseStart},
|
events = {fk.EventPhaseStart},
|
||||||
can_trigger = function(self, event, target, player, data)
|
can_trigger = function(self, event, target, player, data)
|
||||||
return target == player and player:hasSkill(self) and player.phase == Player.Draw and
|
return target == player and player:hasSkill(self) and player.phase == Player.Draw and
|
||||||
table.find(player.room:getOtherPlayers(player, false), function(p) return not p:isKongcheng() end)
|
table.find(player.room:getOtherPlayers(player), function(p) return not p:isKongcheng() end)
|
||||||
end,
|
end,
|
||||||
on_cost = function(self, event, target, player, data)
|
on_cost = function(self, event, target, player, data)
|
||||||
local room = player.room
|
local room = player.room
|
||||||
local targets = table.map(table.filter(room:getOtherPlayers(player, false), function(p)
|
local targets = table.map(table.filter(room:getOtherPlayers(player), function(p)
|
||||||
return not p:isKongcheng() end), Util.IdMapper)
|
return not p:isKongcheng() end), Util.IdMapper)
|
||||||
|
|
||||||
local result = room:askForChoosePlayers(player, targets, 1, 2, "#tuxi-ask", self.name)
|
local result = room:askForChoosePlayers(player, targets, 1, 2, "#tuxi-ask", self.name)
|
||||||
|
@ -269,8 +273,7 @@ local yiji = fk.CreateTriggerSkill{
|
||||||
for _, id in ipairs(ret.cards) do
|
for _, id in ipairs(ret.cards) do
|
||||||
table.removeOne(ids, id)
|
table.removeOne(ids, id)
|
||||||
end
|
end
|
||||||
room:moveCardTo(ret.cards, Card.PlayerHand, room:getPlayerById(ret.targets[1]), fk.ReasonGive,
|
room:moveCardTo(ret.cards, Card.PlayerHand, room:getPlayerById(ret.targets[1]), fk.ReasonGive, self.name, nil, false, player.id)
|
||||||
self.name, nil, false, player.id, nil, player.id)
|
|
||||||
if #ids == 0 then break end
|
if #ids == 0 then break end
|
||||||
if player.dead then
|
if player.dead then
|
||||||
room:moveCards({
|
room:moveCards({
|
||||||
|
@ -354,7 +357,6 @@ zhenji:addSkill(qingguo)
|
||||||
|
|
||||||
local rende = fk.CreateActiveSkill{
|
local rende = fk.CreateActiveSkill{
|
||||||
name = "rende",
|
name = "rende",
|
||||||
prompt = "#rende-active",
|
|
||||||
anim_type = "support",
|
anim_type = "support",
|
||||||
card_filter = function(self, to_select, selected)
|
card_filter = function(self, to_select, selected)
|
||||||
return Fk:currentRoom():getCardArea(to_select) == Card.PlayerHand
|
return Fk:currentRoom():getCardArea(to_select) == Card.PlayerHand
|
||||||
|
@ -631,7 +633,6 @@ huangyueying:addSkill(qicai)
|
||||||
|
|
||||||
local zhiheng = fk.CreateActiveSkill{
|
local zhiheng = fk.CreateActiveSkill{
|
||||||
name = "zhiheng",
|
name = "zhiheng",
|
||||||
prompt = "#zhiheng-active",
|
|
||||||
anim_type = "drawcard",
|
anim_type = "drawcard",
|
||||||
can_use = function(self, player)
|
can_use = function(self, player)
|
||||||
return player:usedSkillTimes(self.name, Player.HistoryPhase) == 0
|
return player:usedSkillTimes(self.name, Player.HistoryPhase) == 0
|
||||||
|
@ -736,9 +737,10 @@ lvmeng:addSkill(keji)
|
||||||
|
|
||||||
local kurou = fk.CreateActiveSkill{
|
local kurou = fk.CreateActiveSkill{
|
||||||
name = "kurou",
|
name = "kurou",
|
||||||
prompt = "#kurou-active",
|
|
||||||
anim_type = "drawcard",
|
anim_type = "drawcard",
|
||||||
card_filter = Util.FalseFunc,
|
card_filter = function(self, to_select, selected, selected_targets)
|
||||||
|
return false
|
||||||
|
end,
|
||||||
on_use = function(self, room, effect)
|
on_use = function(self, room, effect)
|
||||||
local from = room:getPlayerById(effect.from)
|
local from = room:getPlayerById(effect.from)
|
||||||
room:loseHp(from, 1, self.name)
|
room:loseHp(from, 1, self.name)
|
||||||
|
@ -760,7 +762,6 @@ local yingzi = fk.CreateTriggerSkill{
|
||||||
}
|
}
|
||||||
local fanjian = fk.CreateActiveSkill{
|
local fanjian = fk.CreateActiveSkill{
|
||||||
name = "fanjian",
|
name = "fanjian",
|
||||||
prompt = "#fanjian-active",
|
|
||||||
can_use = function(self, player)
|
can_use = function(self, player)
|
||||||
return player:usedSkillTimes(self.name, Player.HistoryPhase) == 0
|
return player:usedSkillTimes(self.name, Player.HistoryPhase) == 0
|
||||||
end,
|
end,
|
||||||
|
@ -936,7 +937,6 @@ local xiaoji = fk.CreateTriggerSkill{
|
||||||
}
|
}
|
||||||
local jieyin = fk.CreateActiveSkill{
|
local jieyin = fk.CreateActiveSkill{
|
||||||
name = "jieyin",
|
name = "jieyin",
|
||||||
prompt = "#jieyin-active",
|
|
||||||
anim_type = "support",
|
anim_type = "support",
|
||||||
can_use = function(self, player)
|
can_use = function(self, player)
|
||||||
return player:usedSkillTimes(self.name, Player.HistoryPhase) == 0
|
return player:usedSkillTimes(self.name, Player.HistoryPhase) == 0
|
||||||
|
@ -978,7 +978,6 @@ sunshangxiang:addSkill(jieyin)
|
||||||
|
|
||||||
local qingnang = fk.CreateActiveSkill{
|
local qingnang = fk.CreateActiveSkill{
|
||||||
name = "qingnang",
|
name = "qingnang",
|
||||||
prompt = "#qingnang-active",
|
|
||||||
anim_type = "support",
|
anim_type = "support",
|
||||||
can_use = function(self, player)
|
can_use = function(self, player)
|
||||||
return player:usedSkillTimes(self.name, Player.HistoryPhase) == 0
|
return player:usedSkillTimes(self.name, Player.HistoryPhase) == 0
|
||||||
|
@ -994,8 +993,8 @@ local qingnang = fk.CreateActiveSkill{
|
||||||
card_num = 1,
|
card_num = 1,
|
||||||
on_use = function(self, room, effect)
|
on_use = function(self, room, effect)
|
||||||
local from = room:getPlayerById(effect.from)
|
local from = room:getPlayerById(effect.from)
|
||||||
local to = room:getPlayerById(effect.tos[1])
|
|
||||||
room:throwCard(effect.cards, self.name, from, from)
|
room:throwCard(effect.cards, self.name, from, from)
|
||||||
|
local to = room:getPlayerById(effect.tos[1])
|
||||||
if to:isAlive() and to:isWounded() then
|
if to:isAlive() and to:isWounded() then
|
||||||
room:recover({
|
room:recover({
|
||||||
who = to,
|
who = to,
|
||||||
|
@ -1064,7 +1063,6 @@ lvbu:addSkill(wushuang)
|
||||||
|
|
||||||
local lijian = fk.CreateActiveSkill{
|
local lijian = fk.CreateActiveSkill{
|
||||||
name = "lijian",
|
name = "lijian",
|
||||||
prompt = "#lijian-active",
|
|
||||||
anim_type = "offensive",
|
anim_type = "offensive",
|
||||||
can_use = function(self, player)
|
can_use = function(self, player)
|
||||||
return player:usedSkillTimes(self.name, Player.HistoryPhase) == 0
|
return player:usedSkillTimes(self.name, Player.HistoryPhase) == 0
|
||||||
|
@ -1074,9 +1072,7 @@ local lijian = fk.CreateActiveSkill{
|
||||||
end,
|
end,
|
||||||
target_filter = function(self, to_select, selected)
|
target_filter = function(self, to_select, selected)
|
||||||
if #selected < 2 and to_select ~= Self.id then
|
if #selected < 2 and to_select ~= Self.id then
|
||||||
local target = Fk:currentRoom():getPlayerById(to_select)
|
return Fk:currentRoom():getPlayerById(to_select):isMale()
|
||||||
return target:isMale() and (#selected == 0 or
|
|
||||||
target:canUseTo(Fk:cloneCard("duel"), Fk:currentRoom():getPlayerById(selected[1])))
|
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
target_num = 2,
|
target_num = 2,
|
||||||
|
@ -1155,10 +1151,12 @@ local role_getlogic = function()
|
||||||
end)
|
end)
|
||||||
room:returnToGeneralPile(generals)
|
room:returnToGeneralPile(generals)
|
||||||
|
|
||||||
room:prepareGeneral(lord, lord_general, deputy, true)
|
room:setPlayerGeneral(lord, lord_general, true)
|
||||||
|
|
||||||
room:askForChooseKingdom({lord})
|
room:askForChooseKingdom({lord})
|
||||||
|
room:broadcastProperty(lord, "general")
|
||||||
room:broadcastProperty(lord, "kingdom")
|
room:broadcastProperty(lord, "kingdom")
|
||||||
|
room:setDeputyGeneral(lord, deputy)
|
||||||
|
room:broadcastProperty(lord, "deputyGeneral")
|
||||||
|
|
||||||
-- 显示技能
|
-- 显示技能
|
||||||
local canAttachSkill = function(player, skillName)
|
local canAttachSkill = function(player, skillName)
|
||||||
|
@ -1212,7 +1210,8 @@ local role_getlogic = function()
|
||||||
end
|
end
|
||||||
|
|
||||||
local nonlord = room:getOtherPlayers(lord, true)
|
local nonlord = room:getOtherPlayers(lord, true)
|
||||||
local generals = table.random(room.general_pile, #nonlord * generalNum)
|
local generals = room:getNGenerals(#nonlord * generalNum)
|
||||||
|
table.shuffle(generals)
|
||||||
for i, p in ipairs(nonlord) do
|
for i, p in ipairs(nonlord) do
|
||||||
local arg = table.slice(generals, (i - 1) * generalNum + 1, i * generalNum + 1)
|
local arg = table.slice(generals, (i - 1) * generalNum + 1, i * generalNum + 1)
|
||||||
p.request_data = json.encode{ arg, n }
|
p.request_data = json.encode{ arg, n }
|
||||||
|
@ -1222,22 +1221,30 @@ local role_getlogic = function()
|
||||||
room:notifyMoveFocus(nonlord, "AskForGeneral")
|
room:notifyMoveFocus(nonlord, "AskForGeneral")
|
||||||
room:doBroadcastRequest("AskForGeneral", nonlord)
|
room:doBroadcastRequest("AskForGeneral", nonlord)
|
||||||
|
|
||||||
|
local selected = {}
|
||||||
for _, p in ipairs(nonlord) do
|
for _, p in ipairs(nonlord) do
|
||||||
local general, deputy
|
|
||||||
if p.general == "" and p.reply_ready then
|
if p.general == "" and p.reply_ready then
|
||||||
local general_ret = json.decode(p.client_reply)
|
local general_ret = json.decode(p.client_reply)
|
||||||
general = general_ret[1]
|
local general = general_ret[1]
|
||||||
deputy = general_ret[2]
|
local deputy = general_ret[2]
|
||||||
|
table.insertTableIfNeed(selected, general_ret)
|
||||||
|
room:setPlayerGeneral(p, general, true, true)
|
||||||
|
room:setDeputyGeneral(p, deputy)
|
||||||
else
|
else
|
||||||
general = p.default_reply[1]
|
table.insertTableIfNeed(selected, p.default_reply)
|
||||||
deputy = p.default_reply[2]
|
room:setPlayerGeneral(p, p.default_reply[1], true, true)
|
||||||
|
room:setDeputyGeneral(p, p.default_reply[2])
|
||||||
end
|
end
|
||||||
room:findGeneral(general)
|
|
||||||
room:findGeneral(deputy)
|
|
||||||
room:prepareGeneral(p, general, deputy)
|
|
||||||
p.default_reply = ""
|
p.default_reply = ""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
generals = table.filter(generals, function(g)
|
||||||
|
return not table.find(selected, function(lg)
|
||||||
|
return Fk.generals[lg].trueName == Fk.generals[g].trueName
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
room:returnToGeneralPile(generals)
|
||||||
|
|
||||||
room:askForChooseKingdom(nonlord)
|
room:askForChooseKingdom(nonlord)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1368,6 +1375,6 @@ Fk:loadTranslationTable{
|
||||||
}
|
}
|
||||||
|
|
||||||
-- load translations of this package
|
-- load translations of this package
|
||||||
dofile "packages/freekill-core/standard/i18n/init.lua"
|
dofile "packages/standard/i18n/init.lua"
|
||||||
|
|
||||||
return extension
|
return extension
|
||||||
|
|
|
@ -52,8 +52,6 @@ Fk:loadTranslationTable({
|
||||||
["method_draw"] = "draw",
|
["method_draw"] = "draw",
|
||||||
["method_discard"] = "discard",
|
["method_discard"] = "discard",
|
||||||
|
|
||||||
["prohibit"] = " prohibit ",
|
|
||||||
|
|
||||||
["slash"] = "Slash",
|
["slash"] = "Slash",
|
||||||
[":slash"] = "Slash (basic card)<br /><b>Phase</b>: Action phase<br /><b>Target</b>: Another player within your ATK range<br /><b>Effect</b>: Deal 1 DMG to the targets.<br/><b>Note</b>: You can only use 1 Slash per action phase.",
|
[":slash"] = "Slash (basic card)<br /><b>Phase</b>: Action phase<br /><b>Target</b>: Another player within your ATK range<br /><b>Effect</b>: Deal 1 DMG to the targets.<br/><b>Note</b>: You can only use 1 Slash per action phase.",
|
||||||
["#slash-jink"] = "%src used Slash to you, please use a Dodge",
|
["#slash-jink"] = "%src used Slash to you, please use a Dodge",
|
||||||
|
|
|
@ -52,12 +52,10 @@ Fk:loadTranslationTable{
|
||||||
["method_draw"] = "摸",
|
["method_draw"] = "摸",
|
||||||
["method_discard"] = "弃置",
|
["method_discard"] = "弃置",
|
||||||
|
|
||||||
["prohibit"] = "禁",
|
|
||||||
|
|
||||||
["slash"] = "杀",
|
["slash"] = "杀",
|
||||||
[":slash"] = "基本牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:攻击范围内的一名角色<br /><b>效果</b>:对目标角色造成1点伤害。",
|
[":slash"] = "基本牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:攻击范围内的一名角色<br /><b>效果</b>:对目标角色造成1点伤害。",
|
||||||
["#slash-jink"] = "%src 对你使用了【杀】,请使用一张【闪】",
|
["#slash-jink"] = "%src 对你使用了杀,请使用一张闪",
|
||||||
["#slash-jink-multi"] = "%src 对你使用了【杀】,请使用一张【闪】(此为第 %arg 张,共需 %arg2 张)",
|
["#slash-jink-multi"] = "%src 对你使用了杀,请使用一张闪(此为第 %arg 张,共需 %arg2 张)",
|
||||||
["#slash_skill"] = "选择攻击范围内的一名角色,对其造成1点伤害",
|
["#slash_skill"] = "选择攻击范围内的一名角色,对其造成1点伤害",
|
||||||
["#slash_skill_multi"] = "选择攻击范围内的至多%arg名角色,对这些角色各造成1点伤害",
|
["#slash_skill_multi"] = "选择攻击范围内的至多%arg名角色,对这些角色各造成1点伤害",
|
||||||
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 22 KiB |
|
@ -622,7 +622,6 @@ local amazingGraceSkill = fk.CreateActiveSkill{
|
||||||
ids = toDisplay,
|
ids = toDisplay,
|
||||||
toArea = Card.Processing,
|
toArea = Card.Processing,
|
||||||
moveReason = fk.ReasonPut,
|
moveReason = fk.ReasonPut,
|
||||||
proposer = use.from,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
table.forEach(room.players, function(p)
|
table.forEach(room.players, function(p)
|
||||||
|
@ -723,17 +722,7 @@ local lightningSkill = fk.CreateActiveSkill{
|
||||||
local nextp = to
|
local nextp = to
|
||||||
repeat
|
repeat
|
||||||
nextp = nextp:getNextAlive(true)
|
nextp = nextp:getNextAlive(true)
|
||||||
if nextp == to then
|
if nextp == to then break end
|
||||||
if nextp:isProhibited(nextp, effect.card) then
|
|
||||||
room:moveCards{
|
|
||||||
ids = room:getSubcardsByRule(effect.card, { Card.Processing }),
|
|
||||||
toArea = Card.DiscardPile,
|
|
||||||
moveReason = fk.ReasonPut
|
|
||||||
}
|
|
||||||
return
|
|
||||||
end
|
|
||||||
break
|
|
||||||
end
|
|
||||||
until not nextp:hasDelayedTrick("lightning") and not nextp:isProhibited(nextp, effect.card)
|
until not nextp:hasDelayedTrick("lightning") and not nextp:isProhibited(nextp, effect.card)
|
||||||
|
|
||||||
|
|
||||||
|
@ -827,16 +816,10 @@ local crossbowAudio = fk.CreateTriggerSkill{
|
||||||
local crossbowSkill = fk.CreateTargetModSkill{
|
local crossbowSkill = fk.CreateTargetModSkill{
|
||||||
name = "#crossbow_skill",
|
name = "#crossbow_skill",
|
||||||
attached_equip = "crossbow",
|
attached_equip = "crossbow",
|
||||||
bypass_times = function(self, player, skill, scope, card)
|
bypass_times = function(self, player, skill, scope)
|
||||||
if player:hasSkill(self) and skill.trueName == "slash_skill" and scope == Player.HistoryPhase then
|
if player:hasSkill(self) and skill.trueName == "slash_skill"
|
||||||
--FIXME: 无法检测到非转化的cost选牌的情况,如活墨等
|
and scope == Player.HistoryPhase then
|
||||||
local cardIds = Card:getIdList(card)
|
return true
|
||||||
local crossbows = table.filter(player:getEquipments(Card.SubtypeWeapon), function(id)
|
|
||||||
return Fk:getCardById(id).equip_skill == self
|
|
||||||
end)
|
|
||||||
return #crossbows == 0 or not table.every(crossbows, function(id)
|
|
||||||
return table.contains(cardIds, id)
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
@ -1173,13 +1156,9 @@ local eightDiagramSkill = fk.CreateTriggerSkill{
|
||||||
attached_equip = "eight_diagram",
|
attached_equip = "eight_diagram",
|
||||||
events = {fk.AskForCardUse, fk.AskForCardResponse},
|
events = {fk.AskForCardUse, fk.AskForCardResponse},
|
||||||
can_trigger = function(self, event, target, player, data)
|
can_trigger = function(self, event, target, player, data)
|
||||||
if not (target == player and player:hasSkill(self) and
|
return target == player and player:hasSkill(self) and
|
||||||
(data.cardName == "jink" or (data.pattern and Exppattern:Parse(data.pattern):matchExp("jink|0|nosuit|none")))) then return end
|
(data.cardName == "jink" or (data.pattern and Exppattern:Parse(data.pattern):matchExp("jink|0|nosuit|none"))) and
|
||||||
if event == fk.AskForCardUse then
|
(event == fk.AskForCardUse and not player:prohibitUse(Fk:cloneCard("jink")) or not player:prohibitResponse(Fk:cloneCard("jink")))
|
||||||
return not player:prohibitUse(Fk:cloneCard("jink"))
|
|
||||||
else
|
|
||||||
return not player:prohibitResponse(Fk:cloneCard("jink"))
|
|
||||||
end
|
|
||||||
end,
|
end,
|
||||||
on_use = function(self, event, target, player, data)
|
on_use = function(self, event, target, player, data)
|
||||||
local room = player.room
|
local room = player.room
|
||||||
|
|
10
sgs
10
sgs
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"banwords": [ "习近", "近平", "共产党", "介石", "刘少奇", "邓小平", "江泽民", "胡锦涛", "毛泽东" ],
|
|
||||||
"description": "新月杀 [0.4.15] 主力联机服务器!请素质交流、理性对局!<b>交流请去贴吧[新月杀]吧</b>",
|
|
||||||
"iconUrl": "http://175.178.66.93/ba-freekill.png",
|
|
||||||
"capacity": 800,
|
|
||||||
"tempBanTime": 15,
|
|
||||||
"motd": "6.5更新\n\n手杀测试服:司马孚、成济、SP毌丘俭、李昭焦伯;十周年(一将24获奖版初稿):宣公主、徐琨、令狐愚、司马孚\n\n6.3~6.4更新\n\nOL:界法正、蒋琬(注:暂不实现禁用手牌排序,且点击“牌序”按钮并不影响真实顺序,如不小心点击则通过点击武将上的“自若”标记查看真实顺序);十周年:韩嵩、马铁;线下:周姬、鄂焕\n\n5.31~6.1更新\n\n十周年:乐诸葛果、小孙权、乐邹氏、乐祢衡、谋张绣;\n\n\n\n请为新月杀的Github仓库点一个star吧!感谢! https://github.com/Notify-ctrl/FreeKill\n\n## 点此查看游玩教程: https://fkbook-all-in-one.readthedocs.io",
|
|
||||||
"hiddenPacks": [],
|
|
||||||
"enableBots": false
|
|
||||||
}
|
|
|
@ -8,14 +8,10 @@ set(freekill_SRCS
|
||||||
"network/server_socket.cpp"
|
"network/server_socket.cpp"
|
||||||
"network/client_socket.cpp"
|
"network/client_socket.cpp"
|
||||||
"network/router.cpp"
|
"network/router.cpp"
|
||||||
"server/auth.cpp"
|
|
||||||
"server/server.cpp"
|
"server/server.cpp"
|
||||||
"server/serverplayer.cpp"
|
"server/serverplayer.cpp"
|
||||||
"server/roombase.cpp"
|
|
||||||
"server/lobby.cpp"
|
|
||||||
"server/room.cpp"
|
"server/room.cpp"
|
||||||
"server/roomthread.cpp"
|
"server/roomthread.cpp"
|
||||||
"server/scheduler.cpp"
|
|
||||||
"ui/qmlbackend.cpp"
|
"ui/qmlbackend.cpp"
|
||||||
"swig/freekill-wrap.cxx"
|
"swig/freekill-wrap.cxx"
|
||||||
)
|
)
|
||||||
|
@ -25,7 +21,7 @@ if (NOT DEFINED FK_SERVER_ONLY)
|
||||||
"client/client.cpp"
|
"client/client.cpp"
|
||||||
"client/clientplayer.cpp"
|
"client/clientplayer.cpp"
|
||||||
"client/replayer.cpp"
|
"client/replayer.cpp"
|
||||||
# "ui/mod.cpp"
|
"ui/mod.cpp"
|
||||||
)
|
)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "client/client.h"
|
#include "client.h"
|
||||||
#include "client/clientplayer.h"
|
#include "client_socket.h"
|
||||||
#include "ui/qmlbackend.h"
|
#include "clientplayer.h"
|
||||||
#include "core/util.h"
|
#include "qmlbackend.h"
|
||||||
#include "server/server.h"
|
#include "util.h"
|
||||||
#include "network/client_socket.h"
|
#include "server.h"
|
||||||
|
#include <qforeach.h>
|
||||||
|
#include <qlogging.h>
|
||||||
|
|
||||||
Client *ClientInstance = nullptr;
|
Client *ClientInstance = nullptr;
|
||||||
ClientPlayer *Self = nullptr;
|
ClientPlayer *Self = nullptr;
|
||||||
|
|
|
@ -3,11 +3,12 @@
|
||||||
#ifndef _CLIENT_H
|
#ifndef _CLIENT_H
|
||||||
#define _CLIENT_H
|
#define _CLIENT_H
|
||||||
|
|
||||||
#include "network/router.h"
|
#include "router.h"
|
||||||
#include "client/clientplayer.h"
|
#include "clientplayer.h"
|
||||||
|
#include <qfilesystemwatcher.h>
|
||||||
|
|
||||||
#ifndef FK_SERVER_ONLY
|
#ifndef FK_SERVER_ONLY
|
||||||
#include "ui/qmlbackend.h"
|
#include "qmlbackend.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class Client : public QObject {
|
class Client : public QObject {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "client/clientplayer.h"
|
#include "clientplayer.h"
|
||||||
|
|
||||||
ClientPlayer::ClientPlayer(int id, QObject *parent) : Player(parent) {
|
ClientPlayer::ClientPlayer(int id, QObject *parent) : Player(parent) {
|
||||||
setId(id);
|
setId(id);
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
#ifndef _CLIENTPLAYER_H
|
#ifndef _CLIENTPLAYER_H
|
||||||
#define _CLIENTPLAYER_H
|
#define _CLIENTPLAYER_H
|
||||||
|
|
||||||
#include "core/player.h"
|
#include "player.h"
|
||||||
|
|
||||||
class ClientPlayer : public Player {
|
class ClientPlayer : public Player {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "client/replayer.h"
|
#include "replayer.h"
|
||||||
#include "client/client.h"
|
#include "client.h"
|
||||||
#include "ui/qmlbackend.h"
|
#include "qmlbackend.h"
|
||||||
#include "core/util.h"
|
#include "util.h"
|
||||||
|
|
||||||
Replayer::Replayer(QObject *parent, const QString &filename) :
|
Replayer::Replayer(QObject *parent, const QString &filename) :
|
||||||
QThread(parent), fileName(filename), roomSettings(""), origPlayerInfo(""),
|
QThread(parent), fileName(filename), roomSettings(""), origPlayerInfo(""),
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "core/packman.h"
|
#include "packman.h"
|
||||||
#include "git2.h"
|
#include "git2.h"
|
||||||
#include "core/util.h"
|
#include "util.h"
|
||||||
#include "ui/qmlbackend.h"
|
#include "qmlbackend.h"
|
||||||
|
#include <qjsondocument.h>
|
||||||
|
|
||||||
PackMan *Pacman;
|
PackMan *Pacman;
|
||||||
|
|
||||||
|
@ -69,16 +70,13 @@ void PackMan::loadSummary(const QString &jsonData, bool useThread) {
|
||||||
auto obj = e.toObject();
|
auto obj = e.toObject();
|
||||||
auto name = obj["name"].toString();
|
auto name = obj["name"].toString();
|
||||||
auto url = obj["url"].toString();
|
auto url = obj["url"].toString();
|
||||||
bool toast_showed = false;
|
#ifndef FK_SERVER_ONLY
|
||||||
|
Backend->showToast(tr("[%1/%2] upgrading package '%3'").arg(i).arg(arr.count()).arg(name));
|
||||||
|
#endif
|
||||||
if (SelectFromDatabase(
|
if (SelectFromDatabase(
|
||||||
db,
|
db,
|
||||||
QString("SELECT name FROM packages WHERE name='%1';").arg(name))
|
QString("SELECT name FROM packages WHERE name='%1';").arg(name))
|
||||||
.isEmpty()) {
|
.isEmpty()) {
|
||||||
#ifndef FK_SERVER_ONLY
|
|
||||||
Backend->showToast(tr("[%1/%2] upgrading package '%3'")
|
|
||||||
.arg(i).arg(arr.count()).arg(name));
|
|
||||||
toast_showed = true;
|
|
||||||
#endif
|
|
||||||
downloadNewPack(url);
|
downloadNewPack(url);
|
||||||
}
|
}
|
||||||
ExecSQL(db, QString("UPDATE packages SET hash='%1' WHERE name='%2'")
|
ExecSQL(db, QString("UPDATE packages SET hash='%1' WHERE name='%2'")
|
||||||
|
@ -87,11 +85,6 @@ void PackMan::loadSummary(const QString &jsonData, bool useThread) {
|
||||||
enablePack(name);
|
enablePack(name);
|
||||||
|
|
||||||
if (head(name) != obj["hash"].toString()) {
|
if (head(name) != obj["hash"].toString()) {
|
||||||
#ifndef FK_SERVER_ONLY
|
|
||||||
if (!toast_showed)
|
|
||||||
Backend->showToast(tr("[%1/%2] upgrading package '%3'")
|
|
||||||
.arg(i).arg(arr.count()).arg(name));
|
|
||||||
#endif
|
|
||||||
updatePack(name);
|
updatePack(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -178,7 +171,7 @@ void PackMan::updatePack(const QString &pack) {
|
||||||
if (error != 0) {
|
if (error != 0) {
|
||||||
#ifndef FK_SERVER_ONLY
|
#ifndef FK_SERVER_ONLY
|
||||||
if (Backend != nullptr) {
|
if (Backend != nullptr) {
|
||||||
Backend->dialog("critical", tr("packages/%1: some error occured.").arg(pack));
|
Backend->showToast(tr("packages/%1: some error occured.").arg(pack));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return;
|
return;
|
||||||
|
@ -200,7 +193,7 @@ void PackMan::upgradePack(const QString &pack) {
|
||||||
if (error != 0) {
|
if (error != 0) {
|
||||||
#ifndef FK_SERVER_ONLY
|
#ifndef FK_SERVER_ONLY
|
||||||
if (Backend != nullptr) {
|
if (Backend != nullptr) {
|
||||||
Backend->showDialog("critical", tr("packages/%1: some error occured.").arg(pack));
|
Backend->showToast(tr("packages/%1: some error occured.").arg(pack));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
#ifndef _PACKMAN_H
|
#ifndef _PACKMAN_H
|
||||||
#define _PACKMAN_H
|
#define _PACKMAN_H
|
||||||
|
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
// 管理拓展包所需的类,本质上是libgit2接口的再封装。
|
// 管理拓展包所需的类,本质上是libgit2接口的再封装。
|
||||||
class PackMan : public QObject {
|
class PackMan : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "core/player.h"
|
#include "player.h"
|
||||||
|
|
||||||
Player::Player(QObject *parent)
|
Player::Player(QObject *parent)
|
||||||
: QObject(parent), id(0), state(Player::Invalid), totalGameTime(0), ready(false),
|
: QObject(parent), id(0), state(Player::Invalid), totalGameTime(0), ready(false),
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "core/util.h"
|
#include "util.h"
|
||||||
#include "core/packman.h"
|
#include "packman.h"
|
||||||
|
#include <qcryptographichash.h>
|
||||||
|
#include <qnamespace.h>
|
||||||
|
#include <qregularexpression.h>
|
||||||
#include <QSysInfo>
|
#include <QSysInfo>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
@ -169,17 +172,6 @@ static void writeDirMD5(QFile &dest, const QString &dir,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void writeFkVerMD5(QFile &dest) {
|
|
||||||
QFile flist("fk_ver");
|
|
||||||
if (flist.exists() && flist.open(QIODevice::ReadOnly)) {
|
|
||||||
while (true) {
|
|
||||||
QByteArray bytes = flist.readLine().simplified();
|
|
||||||
if (bytes.isNull()) break;
|
|
||||||
writeFileMD5(dest, bytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QString calcFileMD5() {
|
QString calcFileMD5() {
|
||||||
// First, generate flist.txt
|
// First, generate flist.txt
|
||||||
// flist.txt is a file contains all md5sum for code files
|
// flist.txt is a file contains all md5sum for code files
|
||||||
|
@ -188,13 +180,12 @@ QString calcFileMD5() {
|
||||||
qFatal("Cannot open flist.txt. Quitting.");
|
qFatal("Cannot open flist.txt. Quitting.");
|
||||||
}
|
}
|
||||||
|
|
||||||
writeFkVerMD5(flist);
|
|
||||||
writeDirMD5(flist, "packages", "*.lua");
|
writeDirMD5(flist, "packages", "*.lua");
|
||||||
writeDirMD5(flist, "packages", "*.qml");
|
writeDirMD5(flist, "packages", "*.qml");
|
||||||
writeDirMD5(flist, "packages", "*.js");
|
writeDirMD5(flist, "packages", "*.js");
|
||||||
// writeDirMD5(flist, "lua", "*.lua");
|
writeDirMD5(flist, "lua", "*.lua");
|
||||||
// writeDirMD5(flist, "Fk", "*.qml");
|
writeDirMD5(flist, "Fk", "*.qml");
|
||||||
// writeDirMD5(flist, "Fk", "*.js");
|
writeDirMD5(flist, "Fk", "*.js");
|
||||||
|
|
||||||
// then, return flist.txt's md5
|
// then, return flist.txt's md5
|
||||||
flist.close();
|
flist.close();
|
||||||
|
|
20
src/main.cpp
20
src/main.cpp
|
@ -1,14 +1,14 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "client/client.h"
|
#include "client.h"
|
||||||
#include "core/util.h"
|
#include "util.h"
|
||||||
using namespace fkShell;
|
using namespace fkShell;
|
||||||
|
|
||||||
#include "core/packman.h"
|
#include "packman.h"
|
||||||
#include "server/server.h"
|
#include "server.h"
|
||||||
|
|
||||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||||
#include "server/shell.h"
|
#include "shell.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_WIN32)
|
#if defined(Q_OS_WIN32)
|
||||||
|
@ -22,7 +22,7 @@ using namespace fkShell;
|
||||||
#ifndef Q_OS_ANDROID
|
#ifndef Q_OS_ANDROID
|
||||||
#include <QQuickStyle>
|
#include <QQuickStyle>
|
||||||
#endif
|
#endif
|
||||||
#include "ui/qmlbackend.h"
|
#include "qmlbackend.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID)
|
#if defined(Q_OS_ANDROID)
|
||||||
|
@ -113,10 +113,10 @@ void fkMsgHandler(QtMsgType type, const QMessageLogContext &context,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(stderr, "%02d/%02d ", date.month(), date.day());
|
fprintf(stderr, "\r%02d/%02d ", date.month(), date.day());
|
||||||
fprintf(stderr, "%s ",
|
fprintf(stderr, "%s ",
|
||||||
QTime::currentTime().toString("hh:mm:ss").toLatin1().constData());
|
QTime::currentTime().toString("hh:mm:ss").toLatin1().constData());
|
||||||
fprintf(file, "%02d/%02d ", date.month(), date.day());
|
fprintf(file, "\r%02d/%02d ", date.month(), date.day());
|
||||||
fprintf(file, "%s ",
|
fprintf(file, "%s ",
|
||||||
QTime::currentTime().toString("hh:mm:ss").toLatin1().constData());
|
QTime::currentTime().toString("hh:mm:ss").toLatin1().constData());
|
||||||
|
|
||||||
|
@ -150,7 +150,8 @@ void fkMsgHandler(QtMsgType type, const QMessageLogContext &context,
|
||||||
"C", localMsg.constData());
|
"C", localMsg.constData());
|
||||||
#ifndef FK_SERVER_ONLY
|
#ifndef FK_SERVER_ONLY
|
||||||
if (Backend != nullptr) {
|
if (Backend != nullptr) {
|
||||||
Backend->notifyUI("ErrorDialog",
|
Backend->notifyUI(
|
||||||
|
"ErrorDialog",
|
||||||
QString("⛔ %1/Error occured!\n %2").arg(threadName).arg(localMsg));
|
QString("⛔ %1/Error occured!\n %2").arg(threadName).arg(localMsg));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -328,7 +329,6 @@ int main(int argc, char *argv[]) {
|
||||||
#if defined(Q_OS_ANDROID)
|
#if defined(Q_OS_ANDROID)
|
||||||
system = "Android";
|
system = "Android";
|
||||||
#elif defined(Q_OS_WIN32)
|
#elif defined(Q_OS_WIN32)
|
||||||
qputenv("QT_MEDIA_BACKEND", "windows");
|
|
||||||
system = "Win";
|
system = "Win";
|
||||||
::system("chcp 65001");
|
::system("chcp 65001");
|
||||||
#elif defined(Q_OS_LINUX)
|
#elif defined(Q_OS_LINUX)
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "network/client_socket.h"
|
#include "client_socket.h"
|
||||||
#include <openssl/aes.h>
|
#include <openssl/aes.h>
|
||||||
|
#include <qabstractsocket.h>
|
||||||
|
#include <qrandom.h>
|
||||||
|
|
||||||
ClientSocket::ClientSocket() : socket(new QTcpSocket(this)) {
|
ClientSocket::ClientSocket() : socket(new QTcpSocket(this)) {
|
||||||
aes_ready = false;
|
aes_ready = false;
|
||||||
|
@ -33,7 +35,7 @@ void ClientSocket::connectToHost(const QString &address, ushort port) {
|
||||||
void ClientSocket::getMessage() {
|
void ClientSocket::getMessage() {
|
||||||
while (socket->canReadLine()) {
|
while (socket->canReadLine()) {
|
||||||
auto msg = socket->readLine();
|
auto msg = socket->readLine();
|
||||||
msg = aesDec(msg);
|
msg = aesDecrypt(msg);
|
||||||
if (msg.startsWith("Compressed")) {
|
if (msg.startsWith("Compressed")) {
|
||||||
msg = msg.sliced(10);
|
msg = msg.sliced(10);
|
||||||
msg = qUncompress(QByteArray::fromBase64(msg));
|
msg = qUncompress(QByteArray::fromBase64(msg));
|
||||||
|
@ -52,9 +54,9 @@ void ClientSocket::send(const QByteArray &msg) {
|
||||||
if (msg.length() >= 1024) {
|
if (msg.length() >= 1024) {
|
||||||
auto comp = qCompress(msg);
|
auto comp = qCompress(msg);
|
||||||
_msg = "Compressed" + comp.toBase64();
|
_msg = "Compressed" + comp.toBase64();
|
||||||
_msg = aesEnc(_msg) + "\n";
|
_msg = aesEncrypt(_msg) + "\n";
|
||||||
} else {
|
} else {
|
||||||
_msg = aesEnc(msg) + "\n";
|
_msg = aesEncrypt(msg) + "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
socket->write(_msg);
|
socket->write(_msg);
|
||||||
|
@ -154,7 +156,7 @@ void ClientSocket::installAESKey(const QByteArray &key) {
|
||||||
aes_ready = true;
|
aes_ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray ClientSocket::aesEnc(const QByteArray &in) {
|
QByteArray ClientSocket::aesEncrypt(const QByteArray &in) {
|
||||||
if (!aes_ready) {
|
if (!aes_ready) {
|
||||||
return in;
|
return in;
|
||||||
}
|
}
|
||||||
|
@ -180,7 +182,7 @@ QByteArray ClientSocket::aesEnc(const QByteArray &in) {
|
||||||
|
|
||||||
return iv + out.toBase64();
|
return iv + out.toBase64();
|
||||||
}
|
}
|
||||||
QByteArray ClientSocket::aesDec(const QByteArray &in) {
|
QByteArray ClientSocket::aesDecrypt(const QByteArray &in) {
|
||||||
if (!aes_ready) {
|
if (!aes_ready) {
|
||||||
return in;
|
return in;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,8 +33,8 @@ private slots:
|
||||||
void raiseError(QAbstractSocket::SocketError error);
|
void raiseError(QAbstractSocket::SocketError error);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QByteArray aesEnc(const QByteArray &in);
|
QByteArray aesEncrypt(const QByteArray &in);
|
||||||
QByteArray aesDec(const QByteArray &out);
|
QByteArray aesDecrypt(const QByteArray &out);
|
||||||
AES_KEY aes_key;
|
AES_KEY aes_key;
|
||||||
bool aes_ready;
|
bool aes_ready;
|
||||||
QTcpSocket *socket;
|
QTcpSocket *socket;
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "network/router.h"
|
#include "router.h"
|
||||||
#include "client/client.h"
|
#include "client.h"
|
||||||
#include "network/client_socket.h"
|
#include "client_socket.h"
|
||||||
#include "server/roomthread.h"
|
#include "roomthread.h"
|
||||||
#include "server/server.h"
|
#include <qjsondocument.h>
|
||||||
#include "server/serverplayer.h"
|
#include "server.h"
|
||||||
#include "core/util.h"
|
#include "serverplayer.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
Router::Router(QObject *parent, ClientSocket *socket, RouterType type)
|
Router::Router(QObject *parent, ClientSocket *socket, RouterType type)
|
||||||
: QObject(parent) {
|
: QObject(parent) {
|
||||||
|
@ -159,7 +160,7 @@ void Router::handlePacket(const QByteArray &rawPacket) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto room = player->getRoom();
|
Room *room = player->getRoom();
|
||||||
room->handlePacket(player, command, jsonData);
|
room->handlePacket(player, command, jsonData);
|
||||||
}
|
}
|
||||||
} else if (type & TYPE_REQUEST) {
|
} else if (type & TYPE_REQUEST) {
|
||||||
|
@ -179,13 +180,10 @@ void Router::handlePacket(const QByteArray &rawPacket) {
|
||||||
|
|
||||||
ServerPlayer *player = qobject_cast<ServerPlayer *>(parent());
|
ServerPlayer *player = qobject_cast<ServerPlayer *>(parent());
|
||||||
player->setThinking(false);
|
player->setThinking(false);
|
||||||
auto _room = player->getRoom();
|
// qDebug() << "wake up!";
|
||||||
if (!_room->isLobby()) {
|
auto room = player->getRoom();
|
||||||
auto room = qobject_cast<Room *>(_room);
|
if (room->getThread()) {
|
||||||
if (room->getThread()) {
|
room->getThread()->wakeUp();
|
||||||
room->getThread()->wakeUp(room->getId());
|
|
||||||
// TODO: signal
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requestId != this->expectedReplyId)
|
if (requestId != this->expectedReplyId)
|
||||||
|
|
|
@ -1,23 +1,15 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "network/server_socket.h"
|
#include "server_socket.h"
|
||||||
#include "network/client_socket.h"
|
#include "client_socket.h"
|
||||||
#include "server/server.h"
|
|
||||||
#include "core/util.h"
|
|
||||||
#include <QNetworkDatagram>
|
|
||||||
|
|
||||||
ServerSocket::ServerSocket(QObject *parent) : QObject(parent) {
|
ServerSocket::ServerSocket() {
|
||||||
server = new QTcpServer(this);
|
server = new QTcpServer(this);
|
||||||
connect(server, &QTcpServer::newConnection, this,
|
connect(server, &QTcpServer::newConnection, this,
|
||||||
&ServerSocket::processNewConnection);
|
&ServerSocket::processNewConnection);
|
||||||
|
|
||||||
udpSocket = new QUdpSocket(this);
|
|
||||||
connect(udpSocket, &QUdpSocket::readyRead,
|
|
||||||
this, &ServerSocket::readPendingDatagrams);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ServerSocket::listen(const QHostAddress &address, ushort port) {
|
bool ServerSocket::listen(const QHostAddress &address, ushort port) {
|
||||||
udpSocket->bind(port);
|
|
||||||
return server->listen(address, port);
|
return server->listen(address, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,29 +20,3 @@ void ServerSocket::processNewConnection() {
|
||||||
[connection]() { connection->deleteLater(); });
|
[connection]() { connection->deleteLater(); });
|
||||||
emit new_connection(connection);
|
emit new_connection(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ServerSocket::readPendingDatagrams() {
|
|
||||||
while (udpSocket->hasPendingDatagrams()) {
|
|
||||||
QNetworkDatagram datagram = udpSocket->receiveDatagram();
|
|
||||||
if (datagram.isValid()) {
|
|
||||||
processDatagram(datagram.data(), datagram.senderAddress(), datagram.senderPort());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ServerSocket::processDatagram(const QByteArray &msg, const QHostAddress &addr, uint port) {
|
|
||||||
auto server = qobject_cast<Server *>(parent());
|
|
||||||
if (msg == "fkDetectServer") {
|
|
||||||
udpSocket->writeDatagram("me", addr, port);
|
|
||||||
} else if (msg.startsWith("fkGetDetail,")) {
|
|
||||||
udpSocket->writeDatagram(JsonArray2Bytes(QJsonArray({
|
|
||||||
FK_VERSION,
|
|
||||||
server->getConfig("iconUrl"),
|
|
||||||
server->getConfig("description"),
|
|
||||||
server->getConfig("capacity"),
|
|
||||||
server->getPlayers().count(),
|
|
||||||
msg.sliced(12).constData(),
|
|
||||||
})), addr, port);
|
|
||||||
}
|
|
||||||
udpSocket->flush();
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ class ServerSocket : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ServerSocket(QObject *parent = nullptr);
|
ServerSocket();
|
||||||
|
|
||||||
bool listen(const QHostAddress &address = QHostAddress::Any, ushort port = 9527u);
|
bool listen(const QHostAddress &address = QHostAddress::Any, ushort port = 9527u);
|
||||||
|
|
||||||
|
@ -20,12 +20,9 @@ signals:
|
||||||
private slots:
|
private slots:
|
||||||
// 新建一个ClientSocket,然后立刻交给Server相关函数处理。
|
// 新建一个ClientSocket,然后立刻交给Server相关函数处理。
|
||||||
void processNewConnection();
|
void processNewConnection();
|
||||||
void readPendingDatagrams();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QTcpServer *server;
|
QTcpServer *server;
|
||||||
QUdpSocket *udpSocket; // 服务器列表页面显示服务器信息用
|
|
||||||
void processDatagram(const QByteArray &msg, const QHostAddress &addr, uint port);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // _SERVER_SOCKET_H
|
#endif // _SERVER_SOCKET_H
|
||||||
|
|
|
@ -1,196 +0,0 @@
|
||||||
#include "server/auth.h"
|
|
||||||
#include "server/server.h"
|
|
||||||
#include "server/serverplayer.h"
|
|
||||||
#include "core/util.h"
|
|
||||||
#include "network/client_socket.h"
|
|
||||||
#include <openssl/bn.h>
|
|
||||||
|
|
||||||
AuthManager::AuthManager(QObject *parent) : QObject(parent) {
|
|
||||||
rsa = initRSA();
|
|
||||||
|
|
||||||
QFile file("server/rsa_pub");
|
|
||||||
file.open(QIODevice::ReadOnly);
|
|
||||||
QTextStream in(&file);
|
|
||||||
public_key = in.readAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthManager::~AuthManager() noexcept {
|
|
||||||
RSA_free(rsa);
|
|
||||||
}
|
|
||||||
|
|
||||||
RSA *AuthManager::initRSA() {
|
|
||||||
RSA *rsa = RSA_new();
|
|
||||||
if (!QFile::exists("server/rsa_pub")) {
|
|
||||||
BIGNUM *bne = BN_new();
|
|
||||||
BN_set_word(bne, RSA_F4);
|
|
||||||
RSA_generate_key_ex(rsa, 2048, bne, NULL);
|
|
||||||
|
|
||||||
BIO *bp_pub = BIO_new_file("server/rsa_pub", "w+");
|
|
||||||
PEM_write_bio_RSAPublicKey(bp_pub, rsa);
|
|
||||||
BIO *bp_pri = BIO_new_file("server/rsa", "w+");
|
|
||||||
PEM_write_bio_RSAPrivateKey(bp_pri, rsa, NULL, NULL, 0, NULL, NULL);
|
|
||||||
|
|
||||||
BIO_free_all(bp_pub);
|
|
||||||
BIO_free_all(bp_pri);
|
|
||||||
QFile("server/rsa")
|
|
||||||
.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner);
|
|
||||||
BN_free(bne);
|
|
||||||
}
|
|
||||||
FILE *keyFile = fopen("server/rsa_pub", "r");
|
|
||||||
PEM_read_RSAPublicKey(keyFile, &rsa, NULL, NULL);
|
|
||||||
fclose(keyFile);
|
|
||||||
keyFile = fopen("server/rsa", "r");
|
|
||||||
PEM_read_RSAPrivateKey(keyFile, &rsa, NULL, NULL);
|
|
||||||
fclose(keyFile);
|
|
||||||
return rsa;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AuthManager::checkClientVersion(ClientSocket *client, const QString &cver) {
|
|
||||||
auto server = qobject_cast<Server *>(parent());
|
|
||||||
auto client_ver = QVersionNumber::fromString(cver);
|
|
||||||
auto ver = QVersionNumber::fromString(FK_VERSION);
|
|
||||||
int cmp = QVersionNumber::compare(ver, client_ver);
|
|
||||||
if (cmp != 0) {
|
|
||||||
auto errmsg = QString();
|
|
||||||
if (cmp < 0) {
|
|
||||||
errmsg = QString("[\"server is still on version %%2\",\"%1\"]")
|
|
||||||
.arg(FK_VERSION, "1");
|
|
||||||
} else {
|
|
||||||
errmsg = QString("[\"server is using version %%2, please update\",\"%1\"]")
|
|
||||||
.arg(FK_VERSION, "1");
|
|
||||||
}
|
|
||||||
|
|
||||||
server->sendEarlyPacket(client, "ErrorDlg", errmsg);
|
|
||||||
client->disconnectFromHost();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject AuthManager::queryUserInfo(ClientSocket *client, const QString &name,
|
|
||||||
const QByteArray &password) {
|
|
||||||
auto server = qobject_cast<Server *>(parent());
|
|
||||||
auto db = server->getDatabase();
|
|
||||||
auto pw = password;
|
|
||||||
|
|
||||||
auto sql_find = QString("SELECT * FROM userinfo WHERE name='%1';")
|
|
||||||
.arg(name);
|
|
||||||
|
|
||||||
auto result = SelectFromDatabase(db, sql_find);
|
|
||||||
if (result.isEmpty()) {
|
|
||||||
auto salt_gen = QRandomGenerator::securelySeeded();
|
|
||||||
auto salt = QByteArray::number(salt_gen(), 16);
|
|
||||||
pw.append(salt);
|
|
||||||
auto passwordHash =
|
|
||||||
QCryptographicHash::hash(pw, QCryptographicHash::Sha256).toHex();
|
|
||||||
|
|
||||||
auto sql_reg = QString("INSERT INTO userinfo (name,password,salt,\
|
|
||||||
avatar,lastLoginIp,banned) VALUES ('%1','%2','%3','%4','%5',%6);")
|
|
||||||
.arg(name).arg(QString(passwordHash))
|
|
||||||
.arg(salt).arg("liubei").arg(client->peerAddress())
|
|
||||||
.arg("FALSE");
|
|
||||||
|
|
||||||
ExecSQL(db, sql_reg);
|
|
||||||
result = SelectFromDatabase(db, sql_find); // refresh result
|
|
||||||
auto obj = result[0].toObject();
|
|
||||||
|
|
||||||
auto info_update = QString("INSERT INTO usergameinfo (id, registerTime) VALUES (%1, %2);").arg(obj["id"].toString().toInt()).arg(QDateTime::currentSecsSinceEpoch());
|
|
||||||
ExecSQL(db, info_update);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result[0].toObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject AuthManager::checkPassword(ClientSocket *client, const QString &name,
|
|
||||||
const QString &password) {
|
|
||||||
|
|
||||||
auto server = qobject_cast<Server *>(parent());
|
|
||||||
bool passed = false;
|
|
||||||
QString error_msg;
|
|
||||||
QJsonObject obj;
|
|
||||||
int id;
|
|
||||||
QByteArray salt;
|
|
||||||
QByteArray passwordHash;
|
|
||||||
auto players = server->getPlayers();
|
|
||||||
|
|
||||||
auto encryted_pw = QByteArray::fromBase64(password.toLatin1());
|
|
||||||
unsigned char buf[4096] = {0};
|
|
||||||
RSA_private_decrypt(RSA_size(rsa), (const unsigned char *)encryted_pw.data(),
|
|
||||||
buf, rsa, RSA_PKCS1_PADDING);
|
|
||||||
auto decrypted_pw =
|
|
||||||
QByteArray::fromRawData((const char *)buf, strlen((const char *)buf));
|
|
||||||
|
|
||||||
if (decrypted_pw.length() > 32) {
|
|
||||||
auto aes_bytes = decrypted_pw.first(32);
|
|
||||||
|
|
||||||
// tell client to install aes key
|
|
||||||
server->sendEarlyPacket(client, "InstallKey", "");
|
|
||||||
client->installAESKey(aes_bytes);
|
|
||||||
decrypted_pw.remove(0, 32);
|
|
||||||
} else {
|
|
||||||
// FIXME
|
|
||||||
// decrypted_pw = "\xFF";
|
|
||||||
error_msg = "unknown password error";
|
|
||||||
goto FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!CheckSqlString(name) || !server->checkBanWord(name)) {
|
|
||||||
error_msg = "invalid user name";
|
|
||||||
goto FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (server->getConfig("whitelist").isArray() &&
|
|
||||||
!server->getConfig("whitelist").toArray().toVariantList().contains(name)) {
|
|
||||||
error_msg = "user name not in whitelist";
|
|
||||||
goto FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
obj = queryUserInfo(client, name, decrypted_pw);
|
|
||||||
|
|
||||||
// check ban account
|
|
||||||
id = obj["id"].toString().toInt();
|
|
||||||
passed = obj["banned"].toString().toInt() == 0;
|
|
||||||
if (!passed) {
|
|
||||||
error_msg = "you have been banned!";
|
|
||||||
goto FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if password is the same
|
|
||||||
salt = obj["salt"].toString().toLatin1();
|
|
||||||
decrypted_pw.append(salt);
|
|
||||||
passwordHash =
|
|
||||||
QCryptographicHash::hash(decrypted_pw, QCryptographicHash::Sha256).toHex();
|
|
||||||
passed = (passwordHash == obj["password"].toString());
|
|
||||||
if (!passed) {
|
|
||||||
error_msg = "username or password error";
|
|
||||||
goto FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (players.value(id)) {
|
|
||||||
auto player = players.value(id);
|
|
||||||
// 顶号机制,如果在线的话就让他变成不在线
|
|
||||||
if (player->getState() == Player::Online) {
|
|
||||||
player->doNotify("ErrorDlg", "others logged in again with this name");
|
|
||||||
emit player->kicked();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (player->getState() == Player::Offline) {
|
|
||||||
player->reconnect(client);
|
|
||||||
passed = true;
|
|
||||||
return QJsonObject();
|
|
||||||
} else {
|
|
||||||
error_msg = "others logged in with this name";
|
|
||||||
passed = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FAIL:
|
|
||||||
if (!passed) {
|
|
||||||
qInfo() << client->peerAddress() << "lost connection:" << error_msg;
|
|
||||||
server->sendEarlyPacket(client, "ErrorDlg", error_msg);
|
|
||||||
client->disconnectFromHost();
|
|
||||||
return QJsonObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
#ifndef _AUTH_H
|
|
||||||
#define _AUTH_H
|
|
||||||
|
|
||||||
#include <openssl/rsa.h>
|
|
||||||
#include <openssl/pem.h>
|
|
||||||
|
|
||||||
class ClientSocket;
|
|
||||||
|
|
||||||
class AuthManager : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
AuthManager(QObject *parent = nullptr);
|
|
||||||
~AuthManager() noexcept;
|
|
||||||
auto getPublicKey() const { return public_key; }
|
|
||||||
|
|
||||||
bool checkClientVersion(ClientSocket *client, const QString &ver);
|
|
||||||
QJsonObject checkPassword(ClientSocket *client, const QString &name, const QString &password);
|
|
||||||
|
|
||||||
private:
|
|
||||||
RSA *rsa;
|
|
||||||
QString public_key;
|
|
||||||
|
|
||||||
static RSA *initRSA();
|
|
||||||
QJsonObject queryUserInfo(ClientSocket *client, const QString &name, const QByteArray &password);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // _AUTH_H
|
|
|
@ -1,162 +0,0 @@
|
||||||
#include "server/lobby.h"
|
|
||||||
#include "server/server.h"
|
|
||||||
#include "server/serverplayer.h"
|
|
||||||
#include "core/util.h"
|
|
||||||
|
|
||||||
Lobby::Lobby(Server *server) {
|
|
||||||
this->server = server;
|
|
||||||
setParent(server);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Lobby::addPlayer(ServerPlayer *player) {
|
|
||||||
if (!player) return;
|
|
||||||
|
|
||||||
players.append(player);
|
|
||||||
player->setRoom(this);
|
|
||||||
|
|
||||||
if (player->getState() == Player::Robot) {
|
|
||||||
removePlayer(player);
|
|
||||||
player->deleteLater();
|
|
||||||
} else {
|
|
||||||
player->doNotify("EnterLobby", "[]");
|
|
||||||
}
|
|
||||||
|
|
||||||
server->updateOnlineInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Lobby::removePlayer(ServerPlayer *player) {
|
|
||||||
players.removeOne(player);
|
|
||||||
server->updateOnlineInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Lobby::updateAvatar(ServerPlayer *sender, const QString &jsonData) {
|
|
||||||
auto arr = String2Json(jsonData).array();
|
|
||||||
auto avatar = arr[0].toString();
|
|
||||||
|
|
||||||
if (CheckSqlString(avatar)) {
|
|
||||||
auto sql = QString("UPDATE userinfo SET avatar='%1' WHERE id=%2;")
|
|
||||||
.arg(avatar)
|
|
||||||
.arg(sender->getId());
|
|
||||||
ExecSQL(ServerInstance->getDatabase(), sql);
|
|
||||||
sender->setAvatar(avatar);
|
|
||||||
sender->doNotify("UpdateAvatar", avatar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Lobby::updatePassword(ServerPlayer *sender, const QString &jsonData) {
|
|
||||||
auto arr = String2Json(jsonData).array();
|
|
||||||
auto oldpw = arr[0].toString();
|
|
||||||
auto newpw = arr[1].toString();
|
|
||||||
auto sql_find =
|
|
||||||
QString("SELECT password, salt FROM userinfo WHERE id=%1;")
|
|
||||||
.arg(sender->getId());
|
|
||||||
|
|
||||||
auto passed = false;
|
|
||||||
auto arr2 = SelectFromDatabase(ServerInstance->getDatabase(), sql_find);
|
|
||||||
auto result = arr2[0].toObject();
|
|
||||||
passed = (result["password"].toString() ==
|
|
||||||
QCryptographicHash::hash(
|
|
||||||
oldpw.append(result["salt"].toString()).toLatin1(),
|
|
||||||
QCryptographicHash::Sha256)
|
|
||||||
.toHex());
|
|
||||||
if (passed) {
|
|
||||||
auto sql_update =
|
|
||||||
QString("UPDATE userinfo SET password='%1' WHERE id=%2;")
|
|
||||||
.arg(QCryptographicHash::hash(
|
|
||||||
newpw.append(result["salt"].toString()).toLatin1(),
|
|
||||||
QCryptographicHash::Sha256)
|
|
||||||
.toHex())
|
|
||||||
.arg(sender->getId());
|
|
||||||
ExecSQL(ServerInstance->getDatabase(), sql_update);
|
|
||||||
}
|
|
||||||
|
|
||||||
sender->doNotify("UpdatePassword", passed ? "1" : "0");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Lobby::createRoom(ServerPlayer *sender, const QString &jsonData) {
|
|
||||||
auto arr = String2Json(jsonData).array();
|
|
||||||
auto name = arr[0].toString();
|
|
||||||
auto capacity = arr[1].toInt();
|
|
||||||
auto timeout = arr[2].toInt();
|
|
||||||
auto settings =
|
|
||||||
QJsonDocument(arr[3].toObject()).toJson(QJsonDocument::Compact);
|
|
||||||
ServerInstance->createRoom(sender, name, capacity, timeout, settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Lobby::getRoomConfig(ServerPlayer *sender, const QString &jsonData) {
|
|
||||||
auto arr = String2Json(jsonData).array();
|
|
||||||
auto roomId = arr[0].toInt();
|
|
||||||
auto room = ServerInstance->findRoom(roomId);
|
|
||||||
if (room) {
|
|
||||||
auto settings = room->getSettings();
|
|
||||||
// 手搓JSON数组 跳过编码解码
|
|
||||||
sender->doNotify("GetRoomConfig", QString("[%1,%2]").arg(roomId).arg(settings));
|
|
||||||
} else {
|
|
||||||
sender->doNotify("ErrorMsg", "no such room");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Lobby::enterRoom(ServerPlayer *sender, const QString &jsonData) {
|
|
||||||
auto arr = String2Json(jsonData).array();
|
|
||||||
auto roomId = arr[0].toInt();
|
|
||||||
auto room = ServerInstance->findRoom(roomId);
|
|
||||||
if (room) {
|
|
||||||
auto settings = QJsonDocument::fromJson(room->getSettings());
|
|
||||||
auto password = settings["password"].toString();
|
|
||||||
if (password.isEmpty() || arr[1].toString() == password) {
|
|
||||||
if (room->isOutdated()) {
|
|
||||||
sender->doNotify("ErrorMsg", "room is outdated");
|
|
||||||
} else {
|
|
||||||
room->addPlayer(sender);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sender->doNotify("ErrorMsg", "room password error");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sender->doNotify("ErrorMsg", "no such room");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Lobby::observeRoom(ServerPlayer *sender, const QString &jsonData) {
|
|
||||||
auto arr = String2Json(jsonData).array();
|
|
||||||
auto roomId = arr[0].toInt();
|
|
||||||
auto room = ServerInstance->findRoom(roomId);
|
|
||||||
if (room) {
|
|
||||||
auto settings = QJsonDocument::fromJson(room->getSettings());
|
|
||||||
auto password = settings["password"].toString();
|
|
||||||
if (password.isEmpty() || arr[1].toString() == password) {
|
|
||||||
if (room->isOutdated()) {
|
|
||||||
sender->doNotify("ErrorMsg", "room is outdated");
|
|
||||||
} else {
|
|
||||||
room->addObserver(sender);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sender->doNotify("ErrorMsg", "room password error");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sender->doNotify("ErrorMsg", "no such room");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Lobby::refreshRoomList(ServerPlayer *sender, const QString &) {
|
|
||||||
ServerInstance->updateRoomList(sender);
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef void (Lobby::*room_cb)(ServerPlayer *, const QString &);
|
|
||||||
|
|
||||||
void Lobby::handlePacket(ServerPlayer *sender, const QString &command,
|
|
||||||
const QString &jsonData) {
|
|
||||||
static const QMap<QString, room_cb> lobby_actions = {
|
|
||||||
{"UpdateAvatar", &Lobby::updateAvatar},
|
|
||||||
{"UpdatePassword", &Lobby::updatePassword},
|
|
||||||
{"CreateRoom", &Lobby::createRoom},
|
|
||||||
{"GetRoomConfig", &Lobby::getRoomConfig},
|
|
||||||
{"EnterRoom", &Lobby::enterRoom},
|
|
||||||
{"ObserveRoom", &Lobby::observeRoom},
|
|
||||||
{"RefreshRoomList", &Lobby::refreshRoomList},
|
|
||||||
{"Chat", &Lobby::chat},
|
|
||||||
};
|
|
||||||
|
|
||||||
auto func = lobby_actions[command];
|
|
||||||
if (func) (this->*func)(sender, jsonData);
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
#ifndef _LOBBY_H
|
|
||||||
#define _LOBBY_H
|
|
||||||
|
|
||||||
#include "server/roombase.h"
|
|
||||||
|
|
||||||
class Lobby : public RoomBase {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
Lobby(Server *server);
|
|
||||||
|
|
||||||
void addPlayer(ServerPlayer *player);
|
|
||||||
void removePlayer(ServerPlayer *player);
|
|
||||||
|
|
||||||
void handlePacket(ServerPlayer *sender, const QString &command,
|
|
||||||
const QString &jsonData);
|
|
||||||
private:
|
|
||||||
// for handle packet
|
|
||||||
void updateAvatar(ServerPlayer *, const QString &);
|
|
||||||
void updatePassword(ServerPlayer *, const QString &);
|
|
||||||
void createRoom(ServerPlayer *, const QString &);
|
|
||||||
void getRoomConfig(ServerPlayer *, const QString &);
|
|
||||||
void enterRoom(ServerPlayer *, const QString &);
|
|
||||||
void observeRoom(ServerPlayer *, const QString &);
|
|
||||||
void refreshRoomList(ServerPlayer *, const QString &);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // _LOBBY_H
|
|
|
@ -1,25 +1,28 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "server/room.h"
|
#include "room.h"
|
||||||
#include "server/lobby.h"
|
|
||||||
|
#include <qjsonarray.h>
|
||||||
|
#include <qjsondocument.h>
|
||||||
|
|
||||||
#ifdef FK_SERVER_ONLY
|
#ifdef FK_SERVER_ONLY
|
||||||
static void *ClientInstance = nullptr;
|
static void *ClientInstance = nullptr;
|
||||||
#else
|
#else
|
||||||
#include "client/client.h"
|
#include "client.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "network/client_socket.h"
|
#include "client_socket.h"
|
||||||
#include "server/roomthread.h"
|
#include "roomthread.h"
|
||||||
#include "server/server.h"
|
#include "server.h"
|
||||||
#include "server/serverplayer.h"
|
#include "serverplayer.h"
|
||||||
#include "core/util.h"
|
#include "util.h"
|
||||||
|
|
||||||
Room::Room(RoomThread *m_thread) {
|
Room::Room(RoomThread *m_thread) {
|
||||||
auto server = ServerInstance;
|
auto server = ServerInstance;
|
||||||
id = server->nextRoomId;
|
id = server->nextRoomId;
|
||||||
server->nextRoomId++;
|
server->nextRoomId++;
|
||||||
this->server = server;
|
this->server = server;
|
||||||
|
setThread(m_thread);
|
||||||
if (m_thread) { // In case of lobby
|
if (m_thread) { // In case of lobby
|
||||||
m_thread->addRoom(this);
|
m_thread->addRoom(this);
|
||||||
}
|
}
|
||||||
|
@ -33,8 +36,14 @@ Room::Room(RoomThread *m_thread) {
|
||||||
|
|
||||||
m_ready = true;
|
m_ready = true;
|
||||||
|
|
||||||
connect(this, &Room::playerAdded, server->lobby(), &Lobby::removePlayer);
|
// 如果是普通房间而不是大厅,就初始化Lua,否则置Lua为nullptr
|
||||||
connect(this, &Room::playerRemoved, server->lobby(), &Lobby::addPlayer);
|
if (!isLobby()) {
|
||||||
|
// 如果不是大厅,那么:
|
||||||
|
// * 只要房间添加人了,那么从大厅中移掉这个人
|
||||||
|
// * 只要有人离开房间,那就把他加到大厅去
|
||||||
|
connect(this, &Room::playerAdded, server->lobby(), &Room::removePlayer);
|
||||||
|
connect(this, &Room::playerRemoved, server->lobby(), &Room::addPlayer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Room::~Room() {
|
Room::~Room() {
|
||||||
|
@ -47,6 +56,8 @@ Room::~Room() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Server *Room::getServer() const { return server; }
|
||||||
|
|
||||||
RoomThread *Room::getThread() const { return m_thread; }
|
RoomThread *Room::getThread() const { return m_thread; }
|
||||||
|
|
||||||
void Room::setThread(RoomThread *t) {
|
void Room::setThread(RoomThread *t) {
|
||||||
|
@ -60,6 +71,8 @@ int Room::getId() const { return id; }
|
||||||
|
|
||||||
void Room::setId(int id) { this->id = id; }
|
void Room::setId(int id) { this->id = id; }
|
||||||
|
|
||||||
|
bool Room::isLobby() const { return id == 0; }
|
||||||
|
|
||||||
QString Room::getName() const { return name; }
|
QString Room::getName() const { return name; }
|
||||||
|
|
||||||
void Room::setName(const QString &name) { this->name = name; }
|
void Room::setName(const QString &name) { this->name = name; }
|
||||||
|
@ -75,6 +88,9 @@ const QByteArray Room::getSettings() const { return settings; }
|
||||||
void Room::setSettings(QByteArray settings) { this->settings = settings; }
|
void Room::setSettings(QByteArray settings) { this->settings = settings; }
|
||||||
|
|
||||||
bool Room::isAbandoned() const {
|
bool Room::isAbandoned() const {
|
||||||
|
if (isLobby())
|
||||||
|
return false;
|
||||||
|
|
||||||
if (players.isEmpty())
|
if (players.isEmpty())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
@ -135,60 +151,72 @@ void Room::addPlayer(ServerPlayer *player) {
|
||||||
auto mode = settings["gameMode"].toString();
|
auto mode = settings["gameMode"].toString();
|
||||||
|
|
||||||
// 告诉房里所有玩家有新人进来了
|
// 告诉房里所有玩家有新人进来了
|
||||||
jsonData << player->getId();
|
if (!isLobby()) {
|
||||||
jsonData << player->getScreenName();
|
jsonData << player->getId();
|
||||||
jsonData << player->getAvatar();
|
jsonData << player->getScreenName();
|
||||||
jsonData << player->isReady();
|
jsonData << player->getAvatar();
|
||||||
jsonData << player->getTotalGameTime();
|
jsonData << player->isReady();
|
||||||
doBroadcastNotify(getPlayers(), "AddPlayer", JsonArray2Bytes(jsonData));
|
jsonData << player->getTotalGameTime();
|
||||||
|
doBroadcastNotify(getPlayers(), "AddPlayer", JsonArray2Bytes(jsonData));
|
||||||
|
}
|
||||||
|
|
||||||
players.append(player);
|
players.append(player);
|
||||||
player->setRoom(this);
|
player->setRoom(this);
|
||||||
|
|
||||||
// Second, let the player enter room and add other players
|
if (isLobby()) {
|
||||||
jsonData = QJsonArray();
|
// 有机器人进入大厅(可能因为被踢),那么改为销毁
|
||||||
jsonData << this->capacity;
|
if (player->getState() == Player::Robot) {
|
||||||
jsonData << this->timeout;
|
removePlayer(player);
|
||||||
jsonData << QJsonDocument::fromJson(this->settings).object();
|
player->deleteLater();
|
||||||
player->doNotify("EnterRoom", JsonArray2Bytes(jsonData));
|
} else {
|
||||||
|
player->doNotify("EnterLobby", "[]");
|
||||||
foreach (ServerPlayer *p, getOtherPlayers(player)) {
|
|
||||||
jsonData = QJsonArray();
|
|
||||||
jsonData << p->getId();
|
|
||||||
jsonData << p->getScreenName();
|
|
||||||
jsonData << p->getAvatar();
|
|
||||||
jsonData << p->isReady();
|
|
||||||
jsonData << p->getTotalGameTime();
|
|
||||||
player->doNotify("AddPlayer", JsonArray2Bytes(jsonData));
|
|
||||||
|
|
||||||
jsonData = QJsonArray();
|
|
||||||
jsonData << p->getId();
|
|
||||||
foreach (int i, p->getGameData()) {
|
|
||||||
jsonData << i;
|
|
||||||
}
|
}
|
||||||
player->doNotify("UpdateGameData", JsonArray2Bytes(jsonData));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->owner != nullptr) {
|
|
||||||
jsonData = QJsonArray();
|
|
||||||
jsonData << this->owner->getId();
|
|
||||||
player->doNotify("RoomOwner", JsonArray2Bytes(jsonData));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (player->getLastGameMode() != mode) {
|
|
||||||
player->setLastGameMode(mode);
|
|
||||||
updatePlayerGameData(player->getId(), mode);
|
|
||||||
} else {
|
} else {
|
||||||
auto jsonData = QJsonArray();
|
// Second, let the player enter room and add other players
|
||||||
jsonData << player->getId();
|
jsonData = QJsonArray();
|
||||||
foreach (int i, player->getGameData()) {
|
jsonData << this->capacity;
|
||||||
jsonData << i;
|
jsonData << this->timeout;
|
||||||
|
jsonData << QJsonDocument::fromJson(this->settings).object();
|
||||||
|
player->doNotify("EnterRoom", JsonArray2Bytes(jsonData));
|
||||||
|
|
||||||
|
foreach (ServerPlayer *p, getOtherPlayers(player)) {
|
||||||
|
jsonData = QJsonArray();
|
||||||
|
jsonData << p->getId();
|
||||||
|
jsonData << p->getScreenName();
|
||||||
|
jsonData << p->getAvatar();
|
||||||
|
jsonData << p->isReady();
|
||||||
|
jsonData << p->getTotalGameTime();
|
||||||
|
player->doNotify("AddPlayer", JsonArray2Bytes(jsonData));
|
||||||
|
|
||||||
|
jsonData = QJsonArray();
|
||||||
|
jsonData << p->getId();
|
||||||
|
foreach (int i, p->getGameData()) {
|
||||||
|
jsonData << i;
|
||||||
|
}
|
||||||
|
player->doNotify("UpdateGameData", JsonArray2Bytes(jsonData));
|
||||||
}
|
}
|
||||||
doBroadcastNotify(getPlayers(), "UpdateGameData", JsonArray2Bytes(jsonData));
|
|
||||||
|
if (this->owner != nullptr) {
|
||||||
|
jsonData = QJsonArray();
|
||||||
|
jsonData << this->owner->getId();
|
||||||
|
player->doNotify("RoomOwner", JsonArray2Bytes(jsonData));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player->getLastGameMode() != mode) {
|
||||||
|
player->setLastGameMode(mode);
|
||||||
|
updatePlayerGameData(player->getId(), mode);
|
||||||
|
} else {
|
||||||
|
auto jsonData = QJsonArray();
|
||||||
|
jsonData << player->getId();
|
||||||
|
foreach (int i, player->getGameData()) {
|
||||||
|
jsonData << i;
|
||||||
|
}
|
||||||
|
doBroadcastNotify(getPlayers(), "UpdateGameData", JsonArray2Bytes(jsonData));
|
||||||
|
}
|
||||||
|
// 玩家手动启动
|
||||||
|
// if (isFull() && !gameStarted)
|
||||||
|
// start();
|
||||||
}
|
}
|
||||||
// 玩家手动启动
|
|
||||||
// if (isFull() && !gameStarted)
|
|
||||||
// start();
|
|
||||||
emit playerAdded(player);
|
emit playerAdded(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,7 +251,12 @@ void Room::removePlayer(ServerPlayer *player) {
|
||||||
}
|
}
|
||||||
emit playerRemoved(player);
|
emit playerRemoved(player);
|
||||||
|
|
||||||
doBroadcastNotify(getPlayers(), "RemovePlayer", JsonArray2Bytes({ player->getId() }));
|
if (isLobby())
|
||||||
|
return;
|
||||||
|
|
||||||
|
QJsonArray jsonData;
|
||||||
|
jsonData << player->getId();
|
||||||
|
doBroadcastNotify(getPlayers(), "RemovePlayer", JsonArray2Bytes(jsonData));
|
||||||
} else {
|
} else {
|
||||||
// 否则给跑路玩家召唤个AI代打
|
// 否则给跑路玩家召唤个AI代打
|
||||||
// TODO: if the player is died..
|
// TODO: if the player is died..
|
||||||
|
@ -254,7 +287,7 @@ void Room::removePlayer(ServerPlayer *player) {
|
||||||
// 原先的跑路机器人会在游戏结束后自动销毁掉
|
// 原先的跑路机器人会在游戏结束后自动销毁掉
|
||||||
server->addPlayer(runner);
|
server->addPlayer(runner);
|
||||||
|
|
||||||
// m_thread->wakeUp();
|
m_thread->wakeUp();
|
||||||
|
|
||||||
// 发出信号,让大厅添加这个人
|
// 发出信号,让大厅添加这个人
|
||||||
emit playerRemoved(runner);
|
emit playerRemoved(runner);
|
||||||
|
@ -279,6 +312,22 @@ void Room::removePlayer(ServerPlayer *player) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QList<ServerPlayer *> Room::getPlayers() const { return players; }
|
||||||
|
|
||||||
|
QList<ServerPlayer *> Room::getOtherPlayers(ServerPlayer *expect) const {
|
||||||
|
QList<ServerPlayer *> others = getPlayers();
|
||||||
|
others.removeOne(expect);
|
||||||
|
return others;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerPlayer *Room::findPlayer(int id) const {
|
||||||
|
foreach (ServerPlayer *p, players) {
|
||||||
|
if (p->getId() == id)
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
void Room::addObserver(ServerPlayer *player) {
|
void Room::addObserver(ServerPlayer *player) {
|
||||||
// 首先只能旁观在运行的房间,因为旁观是由Lua处理的
|
// 首先只能旁观在运行的房间,因为旁观是由Lua处理的
|
||||||
if (!gameStarted) {
|
if (!gameStarted) {
|
||||||
|
@ -322,10 +371,6 @@ int Room::getTimeout() const { return timeout; }
|
||||||
|
|
||||||
void Room::setTimeout(int timeout) { this->timeout = timeout; }
|
void Room::setTimeout(int timeout) { this->timeout = timeout; }
|
||||||
|
|
||||||
void Room::delay(int ms) {
|
|
||||||
m_thread->delay(id, ms);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Room::isOutdated() {
|
bool Room::isOutdated() {
|
||||||
bool ret = md5 != server->getMd5();
|
bool ret = md5 != server->getMd5();
|
||||||
if (ret) md5 = "";
|
if (ret) md5 = "";
|
||||||
|
@ -334,6 +379,42 @@ bool Room::isOutdated() {
|
||||||
|
|
||||||
bool Room::isStarted() const { return gameStarted; }
|
bool Room::isStarted() const { return gameStarted; }
|
||||||
|
|
||||||
|
void Room::doBroadcastNotify(const QList<ServerPlayer *> targets,
|
||||||
|
const QString &command, const QString &jsonData) {
|
||||||
|
foreach (ServerPlayer *p, targets) {
|
||||||
|
p->doNotify(command, jsonData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Room::chat(ServerPlayer *sender, const QString &jsonData) {
|
||||||
|
auto doc = String2Json(jsonData).object();
|
||||||
|
auto type = doc["type"].toInt();
|
||||||
|
doc["sender"] = sender->getId();
|
||||||
|
|
||||||
|
// 屏蔽.号,防止有人在HTML文本发链接,而正常发链接看不出来有啥改动
|
||||||
|
auto msg = doc["msg"].toString();
|
||||||
|
msg.replace(".", "․");
|
||||||
|
// 300字限制,与客户端相同
|
||||||
|
msg.erase(msg.begin() + 300, msg.end());
|
||||||
|
doc["msg"] = msg;
|
||||||
|
if (!server->checkBanWord(msg)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == 1) {
|
||||||
|
doc["userName"] = sender->getScreenName();
|
||||||
|
auto json = QJsonDocument(doc).toJson(QJsonDocument::Compact);
|
||||||
|
doBroadcastNotify(players, "Chat", json);
|
||||||
|
} else {
|
||||||
|
auto json = QJsonDocument(doc).toJson(QJsonDocument::Compact);
|
||||||
|
doBroadcastNotify(players, "Chat", json);
|
||||||
|
doBroadcastNotify(observers, "Chat", json);
|
||||||
|
}
|
||||||
|
|
||||||
|
qInfo("[Chat] %s: %s", sender->getScreenName().toUtf8().constData(),
|
||||||
|
doc["msg"].toString().toUtf8().constData());
|
||||||
|
}
|
||||||
|
|
||||||
static const QString findWinRate =
|
static const QString findWinRate =
|
||||||
QString("SELECT win, lose, draw "
|
QString("SELECT win, lose, draw "
|
||||||
"FROM winRate WHERE id = %1 and general = '%2' and mode = '%3';");
|
"FROM winRate WHERE id = %1 and general = '%2' and mode = '%3';");
|
||||||
|
@ -470,18 +551,18 @@ void Room::updatePlayerGameData(int id, const QString &mode) {
|
||||||
auto room = player->getRoom();
|
auto room = player->getRoom();
|
||||||
player->setGameData(total, win, run);
|
player->setGameData(total, win, run);
|
||||||
auto data_arr = QJsonArray({ player->getId(), total, win, run });
|
auto data_arr = QJsonArray({ player->getId(), total, win, run });
|
||||||
room->doBroadcastNotify(room->getPlayers(), "UpdateGameData", JsonArray2Bytes(data_arr));
|
if (!room->isLobby()) {
|
||||||
|
room->doBroadcastNotify(room->getPlayers(), "UpdateGameData", JsonArray2Bytes(data_arr));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Room::gameOver() {
|
void Room::gameOver() {
|
||||||
if (!gameStarted) return;
|
if (!gameStarted) return;
|
||||||
insideGameOver = true;
|
|
||||||
gameStarted = false;
|
gameStarted = false;
|
||||||
runned_players.clear();
|
runned_players.clear();
|
||||||
// 清理所有状态不是“在线”的玩家,增加逃率、游戏时长
|
// 清理所有状态不是“在线”的玩家,增加逃率、游戏时长
|
||||||
auto settings = QJsonDocument::fromJson(this->settings);
|
auto settings = QJsonDocument::fromJson(this->settings);
|
||||||
auto mode = settings["gameMode"].toString();
|
auto mode = settings["gameMode"].toString();
|
||||||
server->beginTransaction();
|
|
||||||
foreach (ServerPlayer *p, players) {
|
foreach (ServerPlayer *p, players) {
|
||||||
auto pid = p->getId();
|
auto pid = p->getId();
|
||||||
|
|
||||||
|
@ -497,7 +578,7 @@ void Room::gameOver() {
|
||||||
realPlayer->doNotify("AddTotalGameTime", bytes);
|
realPlayer->doNotify("AddTotalGameTime", bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将游戏时间更新到数据库中
|
// 摸了,这么写总之不会有问题
|
||||||
auto info_update = QString("UPDATE usergameinfo SET totalGameTime = "
|
auto info_update = QString("UPDATE usergameinfo SET totalGameTime = "
|
||||||
"IIF(totalGameTime IS NULL, %2, totalGameTime + %2) WHERE id = %1;").arg(pid).arg(time);
|
"IIF(totalGameTime IS NULL, %2, totalGameTime + %2) WHERE id = %1;").arg(pid).arg(time);
|
||||||
ExecSQL(server->getDatabase(), info_update);
|
ExecSQL(server->getDatabase(), info_update);
|
||||||
|
@ -506,13 +587,18 @@ void Room::gameOver() {
|
||||||
if (p->getState() != Player::Online) {
|
if (p->getState() != Player::Online) {
|
||||||
if (p->getState() == Player::Offline) {
|
if (p->getState() == Player::Offline) {
|
||||||
addRunRate(pid, mode);
|
addRunRate(pid, mode);
|
||||||
|
// addRunRate(pid, mode);
|
||||||
server->temporarilyBan(pid);
|
server->temporarilyBan(pid);
|
||||||
}
|
}
|
||||||
p->deleteLater();
|
p->deleteLater();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
server->endTransaction();
|
// 旁观者不能在这清除,因为removePlayer逻辑不一样
|
||||||
insideGameOver = true;
|
// observers.clear();
|
||||||
|
// 玩家也不能在这里清除,因为要能返回原来房间继续玩呢
|
||||||
|
// players.clear();
|
||||||
|
// owner = nullptr;
|
||||||
|
// clearRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Room::manuallyStart() {
|
void Room::manuallyStart() {
|
||||||
|
@ -534,6 +620,7 @@ void Room::pushRequest(const QString &req) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Room::addRejectId(int id) {
|
void Room::addRejectId(int id) {
|
||||||
|
if (isLobby()) return;
|
||||||
rejected_players << id;
|
rejected_players << id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -542,84 +629,184 @@ void Room::removeRejectId(int id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------
|
// ------------------------------------------------
|
||||||
void Room::quitRoom(ServerPlayer *player, const QString &) {
|
static void updateAvatar(ServerPlayer *sender, const QString &jsonData) {
|
||||||
removePlayer(player);
|
auto arr = String2Json(jsonData).array();
|
||||||
if (isOutdated()) {
|
auto avatar = arr[0].toString();
|
||||||
|
|
||||||
|
if (CheckSqlString(avatar)) {
|
||||||
|
auto sql = QString("UPDATE userinfo SET avatar='%1' WHERE id=%2;")
|
||||||
|
.arg(avatar)
|
||||||
|
.arg(sender->getId());
|
||||||
|
ExecSQL(ServerInstance->getDatabase(), sql);
|
||||||
|
sender->setAvatar(avatar);
|
||||||
|
sender->doNotify("UpdateAvatar", avatar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void updatePassword(ServerPlayer *sender, const QString &jsonData) {
|
||||||
|
auto arr = String2Json(jsonData).array();
|
||||||
|
auto oldpw = arr[0].toString();
|
||||||
|
auto newpw = arr[1].toString();
|
||||||
|
auto sql_find =
|
||||||
|
QString("SELECT password, salt FROM userinfo WHERE id=%1;")
|
||||||
|
.arg(sender->getId());
|
||||||
|
|
||||||
|
auto passed = false;
|
||||||
|
auto arr2 = SelectFromDatabase(ServerInstance->getDatabase(), sql_find);
|
||||||
|
auto result = arr2[0].toObject();
|
||||||
|
passed = (result["password"].toString() ==
|
||||||
|
QCryptographicHash::hash(
|
||||||
|
oldpw.append(result["salt"].toString()).toLatin1(),
|
||||||
|
QCryptographicHash::Sha256)
|
||||||
|
.toHex());
|
||||||
|
if (passed) {
|
||||||
|
auto sql_update =
|
||||||
|
QString("UPDATE userinfo SET password='%1' WHERE id=%2;")
|
||||||
|
.arg(QCryptographicHash::hash(
|
||||||
|
newpw.append(result["salt"].toString()).toLatin1(),
|
||||||
|
QCryptographicHash::Sha256)
|
||||||
|
.toHex())
|
||||||
|
.arg(sender->getId());
|
||||||
|
ExecSQL(ServerInstance->getDatabase(), sql_update);
|
||||||
|
}
|
||||||
|
|
||||||
|
sender->doNotify("UpdatePassword", passed ? "1" : "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void createRoom(ServerPlayer *sender, const QString &jsonData) {
|
||||||
|
auto arr = String2Json(jsonData).array();
|
||||||
|
auto name = arr[0].toString();
|
||||||
|
auto capacity = arr[1].toInt();
|
||||||
|
auto timeout = arr[2].toInt();
|
||||||
|
auto settings =
|
||||||
|
QJsonDocument(arr[3].toObject()).toJson(QJsonDocument::Compact);
|
||||||
|
ServerInstance->createRoom(sender, name, capacity, timeout, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void enterRoom(ServerPlayer *sender, const QString &jsonData) {
|
||||||
|
auto arr = String2Json(jsonData).array();
|
||||||
|
auto roomId = arr[0].toInt();
|
||||||
|
auto room = ServerInstance->findRoom(roomId);
|
||||||
|
if (room) {
|
||||||
|
auto settings = QJsonDocument::fromJson(room->getSettings());
|
||||||
|
auto password = settings["password"].toString();
|
||||||
|
if (password.isEmpty() || arr[1].toString() == password) {
|
||||||
|
if (room->isOutdated()) {
|
||||||
|
sender->doNotify("ErrorMsg", "room is outdated");
|
||||||
|
} else {
|
||||||
|
room->addPlayer(sender);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sender->doNotify("ErrorMsg", "room password error");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sender->doNotify("ErrorMsg", "no such room");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void observeRoom(ServerPlayer *sender, const QString &jsonData) {
|
||||||
|
auto arr = String2Json(jsonData).array();
|
||||||
|
auto roomId = arr[0].toInt();
|
||||||
|
auto room = ServerInstance->findRoom(roomId);
|
||||||
|
if (room) {
|
||||||
|
auto settings = QJsonDocument::fromJson(room->getSettings());
|
||||||
|
auto password = settings["password"].toString();
|
||||||
|
if (password.isEmpty() || arr[1].toString() == password) {
|
||||||
|
if (room->isOutdated()) {
|
||||||
|
sender->doNotify("ErrorMsg", "room is outdated");
|
||||||
|
} else {
|
||||||
|
room->addObserver(sender);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sender->doNotify("ErrorMsg", "room password error");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sender->doNotify("ErrorMsg", "no such room");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void refreshRoomList(ServerPlayer *sender, const QString &) {
|
||||||
|
ServerInstance->updateRoomList(sender);
|
||||||
|
};
|
||||||
|
|
||||||
|
static void quitRoom(ServerPlayer *player, const QString &) {
|
||||||
|
auto room = player->getRoom();
|
||||||
|
room->removePlayer(player);
|
||||||
|
if (room->isOutdated()) {
|
||||||
player->kicked();
|
player->kicked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Room::addRobotRequest(ServerPlayer *player, const QString &) {
|
static void addRobot(ServerPlayer *player, const QString &) {
|
||||||
|
auto room = player->getRoom();
|
||||||
if (ServerInstance->getConfig("enableBots").toBool())
|
if (ServerInstance->getConfig("enableBots").toBool())
|
||||||
addRobot(player);
|
room->addRobot(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Room::kickPlayer(ServerPlayer *player, const QString &jsonData) {
|
static void kickPlayer(ServerPlayer *player, const QString &jsonData) {
|
||||||
|
auto room = player->getRoom();
|
||||||
int i = jsonData.toInt();
|
int i = jsonData.toInt();
|
||||||
auto p = findPlayer(i);
|
auto p = room->findPlayer(i);
|
||||||
if (p && !isStarted()) {
|
if (p && !room->isStarted()) {
|
||||||
removePlayer(p);
|
room->removePlayer(p);
|
||||||
addRejectId(i);
|
room->addRejectId(i);
|
||||||
QTimer::singleShot(30000, this, [=]() {
|
QTimer::singleShot(30000, room, [=]() {
|
||||||
removeRejectId(i);
|
room->removeRejectId(i);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Room::ready(ServerPlayer *player, const QString &) {
|
static void ready(ServerPlayer *player, const QString &) {
|
||||||
|
auto room = player->getRoom();
|
||||||
player->setReady(!player->isReady());
|
player->setReady(!player->isReady());
|
||||||
doBroadcastNotify(getPlayers(), "ReadyChanged",
|
room->doBroadcastNotify(room->getPlayers(), "ReadyChanged",
|
||||||
QString("[%1,%2]").arg(player->getId()).arg(player->isReady()));
|
QString("[%1,%2]").arg(player->getId()).arg(player->isReady()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Room::startGame(ServerPlayer *player, const QString &) {
|
static void startGame(ServerPlayer *player, const QString &) {
|
||||||
if (isOutdated()) {
|
auto room = player->getRoom();
|
||||||
foreach (auto p, getPlayers()) {
|
if (room->isOutdated()) {
|
||||||
|
foreach (auto p, room->getPlayers()) {
|
||||||
p->doNotify("ErrorMsg", "room is outdated");
|
p->doNotify("ErrorMsg", "room is outdated");
|
||||||
p->kicked();
|
p->kicked();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
manuallyStart();
|
room->manuallyStart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef void (Room::*room_cb)(ServerPlayer *, const QString &);
|
typedef void (*room_cb)(ServerPlayer *, const QString &);
|
||||||
|
static const QMap<QString, room_cb> lobby_actions = {
|
||||||
|
{"UpdateAvatar", updateAvatar},
|
||||||
|
{"UpdatePassword", updatePassword},
|
||||||
|
{"CreateRoom", createRoom},
|
||||||
|
{"EnterRoom", enterRoom},
|
||||||
|
{"ObserveRoom", observeRoom},
|
||||||
|
{"RefreshRoomList", refreshRoomList},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const QMap<QString, room_cb> room_actions = {
|
||||||
|
{"QuitRoom", quitRoom},
|
||||||
|
{"AddRobot", addRobot},
|
||||||
|
{"KickPlayer", kickPlayer},
|
||||||
|
{"Ready", ready},
|
||||||
|
{"StartGame", startGame},
|
||||||
|
};
|
||||||
|
|
||||||
void Room::handlePacket(ServerPlayer *sender, const QString &command,
|
void Room::handlePacket(ServerPlayer *sender, const QString &command,
|
||||||
const QString &jsonData) {
|
const QString &jsonData) {
|
||||||
static const QMap<QString, room_cb> room_actions = {
|
if (command == "Chat") {
|
||||||
{"QuitRoom", &Room::quitRoom},
|
chat(sender, jsonData);
|
||||||
{"AddRobot", &Room::addRobotRequest},
|
|
||||||
{"KickPlayer", &Room::kickPlayer},
|
|
||||||
{"Ready", &Room::ready},
|
|
||||||
{"StartGame", &Room::startGame},
|
|
||||||
{"Chat", &Room::chat},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (command == "PushRequest") {
|
|
||||||
pushRequest(QString("%1,").arg(sender->getId()) + jsonData);
|
|
||||||
return;
|
return;
|
||||||
|
} else if (command == "PushRequest") {
|
||||||
|
if (!isLobby())
|
||||||
|
pushRequest(QString("%1,").arg(sender->getId()) + jsonData);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto func = room_actions[command];
|
auto func_table = lobby_actions;
|
||||||
if (func) (this->*func)(sender, jsonData);
|
if (!isLobby()) func_table = room_actions;
|
||||||
}
|
auto func = func_table[command];
|
||||||
|
if (func) {
|
||||||
// Lua用:request之前设置计时器防止等到死。
|
func(sender, jsonData);
|
||||||
void Room::setRequestTimer(int ms) {
|
}
|
||||||
request_timer = new QTimer();
|
|
||||||
request_timer->setSingleShot(true);
|
|
||||||
request_timer->setInterval(ms);
|
|
||||||
connect(request_timer, &QTimer::timeout, this, [=](){
|
|
||||||
m_thread->wakeUp(id);
|
|
||||||
});
|
|
||||||
request_timer->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lua用:当request完成后手动销毁计时器。
|
|
||||||
void Room::destroyRequestTimer() {
|
|
||||||
if (!request_timer) return;
|
|
||||||
request_timer->stop();
|
|
||||||
delete request_timer;
|
|
||||||
request_timer = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,11 @@
|
||||||
#ifndef _ROOM_H
|
#ifndef _ROOM_H
|
||||||
#define _ROOM_H
|
#define _ROOM_H
|
||||||
|
|
||||||
#include "server/roombase.h"
|
|
||||||
|
|
||||||
class Server;
|
class Server;
|
||||||
class ServerPlayer;
|
class ServerPlayer;
|
||||||
class RoomThread;
|
class RoomThread;
|
||||||
|
|
||||||
class Room : public RoomBase {
|
class Room : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit Room(RoomThread *m_thread);
|
explicit Room(RoomThread *m_thread);
|
||||||
|
@ -17,11 +15,12 @@ class Room : public RoomBase {
|
||||||
|
|
||||||
// Property reader & setter
|
// Property reader & setter
|
||||||
// ==================================={
|
// ==================================={
|
||||||
|
Server *getServer() const;
|
||||||
RoomThread *getThread() const;
|
RoomThread *getThread() const;
|
||||||
void setThread(RoomThread *t);
|
void setThread(RoomThread *t);
|
||||||
|
|
||||||
int getId() const;
|
int getId() const;
|
||||||
void setId(int id);
|
void setId(int id);
|
||||||
|
bool isLobby() const;
|
||||||
QString getName() const;
|
QString getName() const;
|
||||||
void setName(const QString &name);
|
void setName(const QString &name);
|
||||||
int getCapacity() const;
|
int getCapacity() const;
|
||||||
|
@ -39,6 +38,9 @@ class Room : public RoomBase {
|
||||||
void addPlayer(ServerPlayer *player);
|
void addPlayer(ServerPlayer *player);
|
||||||
void addRobot(ServerPlayer *player);
|
void addRobot(ServerPlayer *player);
|
||||||
void removePlayer(ServerPlayer *player);
|
void removePlayer(ServerPlayer *player);
|
||||||
|
QList<ServerPlayer *> getPlayers() const;
|
||||||
|
QList<ServerPlayer *> getOtherPlayers(ServerPlayer *expect) const;
|
||||||
|
ServerPlayer *findPlayer(int id) const;
|
||||||
|
|
||||||
void addObserver(ServerPlayer *player);
|
void addObserver(ServerPlayer *player);
|
||||||
void removeObserver(ServerPlayer *player);
|
void removeObserver(ServerPlayer *player);
|
||||||
|
@ -47,13 +49,16 @@ class Room : public RoomBase {
|
||||||
|
|
||||||
int getTimeout() const;
|
int getTimeout() const;
|
||||||
void setTimeout(int timeout);
|
void setTimeout(int timeout);
|
||||||
void delay(int ms);
|
|
||||||
|
|
||||||
bool isOutdated();
|
bool isOutdated();
|
||||||
|
|
||||||
bool isStarted() const;
|
bool isStarted() const;
|
||||||
// ====================================}
|
// ====================================}
|
||||||
|
|
||||||
|
void doBroadcastNotify(const QList<ServerPlayer *> targets,
|
||||||
|
const QString &command, const QString &jsonData);
|
||||||
|
void chat(ServerPlayer *sender, const QString &jsonData);
|
||||||
|
|
||||||
void updateWinRate(int id, const QString &general, const QString &mode,
|
void updateWinRate(int id, const QString &general, const QString &mode,
|
||||||
int result, bool dead);
|
int result, bool dead);
|
||||||
void gameOver();
|
void gameOver();
|
||||||
|
@ -66,13 +71,6 @@ class Room : public RoomBase {
|
||||||
// router用
|
// router用
|
||||||
void handlePacket(ServerPlayer *sender, const QString &command,
|
void handlePacket(ServerPlayer *sender, const QString &command,
|
||||||
const QString &jsonData);
|
const QString &jsonData);
|
||||||
|
|
||||||
void setRequestTimer(int ms);
|
|
||||||
void destroyRequestTimer();
|
|
||||||
|
|
||||||
// FIXME
|
|
||||||
volatile bool insideGameOver = false;
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void abandoned();
|
void abandoned();
|
||||||
|
|
||||||
|
@ -80,7 +78,8 @@ class Room : public RoomBase {
|
||||||
void playerRemoved(ServerPlayer *player);
|
void playerRemoved(ServerPlayer *player);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
RoomThread *m_thread = nullptr;
|
Server *server;
|
||||||
|
RoomThread *m_thread;
|
||||||
int id; // Lobby's id is 0
|
int id; // Lobby's id is 0
|
||||||
QString name; // “阴间大乱斗”
|
QString name; // “阴间大乱斗”
|
||||||
int capacity; // by default is 5, max is 8
|
int capacity; // by default is 5, max is 8
|
||||||
|
@ -88,6 +87,8 @@ class Room : public RoomBase {
|
||||||
bool m_abandoned; // If room is empty, delete it
|
bool m_abandoned; // If room is empty, delete it
|
||||||
|
|
||||||
ServerPlayer *owner; // who created this room?
|
ServerPlayer *owner; // who created this room?
|
||||||
|
QList<ServerPlayer *> players;
|
||||||
|
QList<ServerPlayer *> observers;
|
||||||
QList<int> runned_players;
|
QList<int> runned_players;
|
||||||
QList<int> rejected_players;
|
QList<int> rejected_players;
|
||||||
int robot_id;
|
int robot_id;
|
||||||
|
@ -97,17 +98,8 @@ class Room : public RoomBase {
|
||||||
int timeout;
|
int timeout;
|
||||||
QString md5;
|
QString md5;
|
||||||
|
|
||||||
QTimer *request_timer = nullptr;
|
|
||||||
|
|
||||||
void addRunRate(int id, const QString &mode);
|
void addRunRate(int id, const QString &mode);
|
||||||
void updatePlayerGameData(int id, const QString &mode);
|
void updatePlayerGameData(int id, const QString &mode);
|
||||||
|
|
||||||
// handle packet
|
|
||||||
void quitRoom(ServerPlayer *, const QString &);
|
|
||||||
void addRobotRequest(ServerPlayer *, const QString &);
|
|
||||||
void kickPlayer(ServerPlayer *, const QString &);
|
|
||||||
void ready(ServerPlayer *, const QString &);
|
|
||||||
void startGame(ServerPlayer *, const QString &);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // _ROOM_H
|
#endif // _ROOM_H
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
#include "server/roombase.h"
|
|
||||||
#include "server/serverplayer.h"
|
|
||||||
#include "server/server.h"
|
|
||||||
#include "core/util.h"
|
|
||||||
|
|
||||||
Server *RoomBase::getServer() const { return server; }
|
|
||||||
|
|
||||||
bool RoomBase::isLobby() const {
|
|
||||||
return inherits("Lobby");
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<ServerPlayer *> RoomBase::getPlayers() const { return players; }
|
|
||||||
|
|
||||||
QList<ServerPlayer *> RoomBase::getOtherPlayers(ServerPlayer *expect) const {
|
|
||||||
QList<ServerPlayer *> others = getPlayers();
|
|
||||||
others.removeOne(expect);
|
|
||||||
return others;
|
|
||||||
}
|
|
||||||
|
|
||||||
ServerPlayer *RoomBase::findPlayer(int id) const {
|
|
||||||
foreach (ServerPlayer *p, players) {
|
|
||||||
if (p->getId() == id)
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoomBase::doBroadcastNotify(const QList<ServerPlayer *> targets,
|
|
||||||
const QString &command, const QString &jsonData) {
|
|
||||||
foreach (ServerPlayer *p, targets) {
|
|
||||||
p->doNotify(command, jsonData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoomBase::chat(ServerPlayer *sender, const QString &jsonData) {
|
|
||||||
auto doc = String2Json(jsonData).object();
|
|
||||||
auto type = doc["type"].toInt();
|
|
||||||
doc["sender"] = sender->getId();
|
|
||||||
|
|
||||||
// 屏蔽.号,防止有人在HTML文本发链接,而正常发链接看不出来有啥改动
|
|
||||||
auto msg = doc["msg"].toString();
|
|
||||||
msg.replace(".", "․");
|
|
||||||
// 300字限制,与客户端相同
|
|
||||||
msg.erase(msg.begin() + 300, msg.end());
|
|
||||||
doc["msg"] = msg;
|
|
||||||
if (!server->checkBanWord(msg)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == 1) {
|
|
||||||
doc["userName"] = sender->getScreenName();
|
|
||||||
auto json = QJsonDocument(doc).toJson(QJsonDocument::Compact);
|
|
||||||
doBroadcastNotify(players, "Chat", json);
|
|
||||||
} else {
|
|
||||||
auto json = QJsonDocument(doc).toJson(QJsonDocument::Compact);
|
|
||||||
doBroadcastNotify(players, "Chat", json);
|
|
||||||
doBroadcastNotify(observers, "Chat", json);
|
|
||||||
}
|
|
||||||
|
|
||||||
qInfo("[Chat] %s: %s", sender->getScreenName().toUtf8().constData(),
|
|
||||||
doc["msg"].toString().toUtf8().constData());
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
#ifndef _ROOMBASE_H
|
|
||||||
#define _ROOMBASE_H
|
|
||||||
|
|
||||||
class Server;
|
|
||||||
class ServerPlayer;
|
|
||||||
|
|
||||||
class RoomBase : public QObject {
|
|
||||||
public:
|
|
||||||
Server *getServer() const;
|
|
||||||
bool isLobby() const;
|
|
||||||
QList<ServerPlayer *> getPlayers() const;
|
|
||||||
QList<ServerPlayer *> getOtherPlayers(ServerPlayer *expect) const;
|
|
||||||
ServerPlayer *findPlayer(int id) const;
|
|
||||||
|
|
||||||
void doBroadcastNotify(const QList<ServerPlayer *> targets,
|
|
||||||
const QString &command, const QString &jsonData);
|
|
||||||
|
|
||||||
void chat(ServerPlayer *sender, const QString &jsonData);
|
|
||||||
|
|
||||||
virtual void addPlayer(ServerPlayer *player) = 0;
|
|
||||||
virtual void removePlayer(ServerPlayer *player) = 0;
|
|
||||||
virtual void handlePacket(ServerPlayer *sender, const QString &command,
|
|
||||||
const QString &jsonData) = 0;
|
|
||||||
protected:
|
|
||||||
Server *server;
|
|
||||||
QList<ServerPlayer *> players;
|
|
||||||
QList<ServerPlayer *> observers;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // _ROOMBASE_H
|
|
|
@ -1,42 +1,45 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "server/roomthread.h"
|
#include "roomthread.h"
|
||||||
#include "server/scheduler.h"
|
#include "server.h"
|
||||||
#include "server/server.h"
|
#include "util.h"
|
||||||
|
#include <lua.h>
|
||||||
|
|
||||||
#ifndef FK_SERVER_ONLY
|
#ifndef FK_SERVER_ONLY
|
||||||
#include "client/client.h"
|
#include "client.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
RoomThread::RoomThread(Server *m_server) {
|
RoomThread::RoomThread(Server *m_server) {
|
||||||
setObjectName("Room");
|
setObjectName("Room");
|
||||||
this->m_server = m_server;
|
this->m_server = m_server;
|
||||||
m_capacity = 100; // TODO: server cfg
|
m_capacity = 100; // TODO: server cfg
|
||||||
|
terminated = false;
|
||||||
md5 = m_server->getMd5();
|
md5 = m_server->getMd5();
|
||||||
|
|
||||||
|
L = CreateLuaState();
|
||||||
|
if (QFile::exists("packages/freekill-core") &&
|
||||||
|
!GetDisabledPacks().contains("freekill-core")) {
|
||||||
|
// 危险的cd操作,记得在lua中切回游戏根目录
|
||||||
|
QDir::setCurrent("packages/freekill-core");
|
||||||
|
}
|
||||||
|
|
||||||
|
DoLuaScript(L, "lua/freekill.lua");
|
||||||
|
DoLuaScript(L, "lua/server/scheduler.lua");
|
||||||
start();
|
start();
|
||||||
}
|
}
|
||||||
|
|
||||||
RoomThread::~RoomThread() {
|
RoomThread::~RoomThread() {
|
||||||
|
tryTerminate();
|
||||||
if (isRunning()) {
|
if (isRunning()) {
|
||||||
quit();
|
wait();
|
||||||
}
|
}
|
||||||
delete m_scheduler;
|
lua_close(L);
|
||||||
m_server->removeThread(this);
|
m_server->removeThread(this);
|
||||||
// foreach (auto room, room_list) {
|
// foreach (auto room, room_list) {
|
||||||
// room->deleteLater();
|
// room->deleteLater();
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
void RoomThread::run() {
|
|
||||||
// 在run中创建,这样就能在接下来的exec中处理事件了
|
|
||||||
m_scheduler = new Scheduler(this);
|
|
||||||
connect(this, &RoomThread::pushRequest, m_scheduler, &Scheduler::handleRequest);
|
|
||||||
connect(this, &RoomThread::delay, m_scheduler, &Scheduler::doDelay);
|
|
||||||
connect(this, &RoomThread::wakeUp, m_scheduler, &Scheduler::resumeRoom);
|
|
||||||
exec();
|
|
||||||
}
|
|
||||||
|
|
||||||
Server *RoomThread::getServer() const {
|
Server *RoomThread::getServer() const {
|
||||||
return m_server;
|
return m_server;
|
||||||
}
|
}
|
||||||
|
@ -53,7 +56,7 @@ Room *RoomThread::getRoom(int id) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void RoomThread::addRoom(Room *room) {
|
void RoomThread::addRoom(Room *room) {
|
||||||
room->setThread(this);
|
Q_UNUSED(room);
|
||||||
m_capacity--;
|
m_capacity--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +69,6 @@ void RoomThread::removeRoom(Room *room) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
QString RoomThread::fetchRequest() {
|
QString RoomThread::fetchRequest() {
|
||||||
// if (!gameStarted)
|
// if (!gameStarted)
|
||||||
// return "";
|
// return "";
|
||||||
|
@ -122,7 +124,6 @@ void RoomThread::tryTerminate() {
|
||||||
bool RoomThread::isTerminated() const {
|
bool RoomThread::isTerminated() const {
|
||||||
return terminated;
|
return terminated;
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
bool RoomThread::isConsoleStart() const {
|
bool RoomThread::isConsoleStart() const {
|
||||||
#ifndef FK_SERVER_ONLY
|
#ifndef FK_SERVER_ONLY
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue