From 22235ee6ec40dfc5ff9880b6e901ad7977869c0e Mon Sep 17 00:00:00 2001 From: notify Date: Sun, 18 Dec 2022 21:19:35 +0800 Subject: [PATCH] Jink&null (#32) * rewrite toUtf8().data() * jink, askforuse, askforresp * nullification --- lua/client/client_util.lua | 3 + lua/server/room.lua | 166 ++++++++++++++++++++++------ lua/server/serverplayer.lua | 7 ++ packages/standard_cards/init.lua | 3 + qml/Pages/Room.qml | 6 +- qml/Pages/RoomElement/Dashboard.qml | 18 ++- qml/Pages/RoomLogic.js | 45 +++++++- src/core/util.cpp | 6 +- src/main.cpp | 12 +- src/server/serverplayer.cpp | 2 +- src/server/shell.cpp | 6 +- src/swig/naturalvar.i | 3 +- src/ui/qmlbackend.cpp | 9 +- 13 files changed, 230 insertions(+), 56 deletions(-) diff --git a/lua/client/client_util.lua b/lua/client/client_util.lua index f4d5635f..1bd23bbc 100644 --- a/lua/client/client_util.lua +++ b/lua/client/client_util.lua @@ -256,6 +256,9 @@ Fk:loadTranslationTable{ ["$Equip"] = "装备区", ["$Judge"] = "判定区", ["#AskForUseActiveSkill"] = "请使用技能 %1", + ["#AskForUseCard"] = "请使用卡牌 %1", + ["#AskForResponseCard"] = "请打出卡牌 %1", + ["#AskForNullification"] = "无懈", ["Trust"] = "托管", ["Sort Cards"] = "牌序", diff --git a/lua/server/room.lua b/lua/server/room.lua index 6d2840e3..21b79204 100644 --- a/lua/server/room.lua +++ b/lua/server/room.lua @@ -295,7 +295,7 @@ end ---@param players ServerPlayer[] function Room:doRaceRequest(command, players, jsonData) players = players or self.players - self:notifyMoveFocus(players, command) + -- self:notifyMoveFocus(players, command) for _, p in ipairs(players) do self:doRequest(p, command, jsonData or p.request_data, false) end @@ -304,6 +304,7 @@ function Room:doRaceRequest(command, players, jsonData) local currentTime = os.time() local elapsed = 0 local winner + local canceled_players = {} while true do elapsed = os.time() - currentTime if remainTime - elapsed <= 0 then @@ -315,11 +316,19 @@ function Room:doRaceRequest(command, players, jsonData) winner = p break end + + if p.reply_cancel then + table.insertIfNeed(canceled_players, p) + end end if winner then self:doBroadcastNotify("CancelRequest", "") return winner end + + if #players == #canceled_players then + return nil + end end end @@ -566,8 +575,104 @@ function Room:askForSkillInvoke(player, skill_name, data) return invoked end -function Room:askForUseCard() end -function Room:askForResponse() end +---@param player ServerPlayer +---@return CardUseStruct +function Room:askForUseCard(player, card_name, prompt, cancelable, extra_data) + local command = "AskForUseCard" + self:notifyMoveFocus(player, card_name) + cancelable = cancelable or false + extra_data = extra_data or {} + prompt = prompt or "#AskForUseCard" + + local data = {card_name, prompt, cancelable, extra_data} + local result = self:doRequest(player, command, json.encode(data)) + if result ~= "" then + data = json.decode(result) + local card = data.card + local targets = data.targets + if type(card) == "string" then + -- TODO: ViewAsSkill + return nil + else + local use = {} ---@type CardUseStruct + use.from = player.id + use.tos = {} + for _, target in ipairs(targets) do + table.insert(use.tos, { target }) + end + if #use.tos == 0 then + use.tos = nil + end + use.cardId = card + return use + end + end + return nil +end + +function Room:askForResponse(player, card_name, prompt, cancelable, extra_data) + local command = "AskForResponseCard" + self:notifyMoveFocus(player, card_name) + cancelable = cancelable or false + extra_data = extra_data or {} + prompt = prompt or "#AskForResponseCard" + + local data = {card_name, prompt, cancelable, extra_data} + local result = self:doRequest(player, command, json.encode(data)) + if result ~= "" then + data = json.decode(result) + local card = data.card + local targets = data.targets + if type(card) == "string" then + -- TODO: ViewAsSkill + return nil + else + return card + end + end + return nil +end + +function Room:askForNullification(players, card_name, prompt, cancelable, extra_data) + if #players == 0 then + return nil + end + + local command = "AskForUseCard" + card_name = card_name or "nullification" + cancelable = cancelable or false + extra_data = extra_data or {} + prompt = prompt or "#AskForUseCard" + + self:notifyMoveFocus(self.players, card_name) + self:doBroadcastNotify("WaitForNullification", "") + + local data = {card_name, prompt, cancelable, extra_data} + local winner = self:doRaceRequest(command, players, json.encode(data)) + if winner then + local result = winner.client_reply + data = json.decode(result) + local card = data.card + local targets = data.targets + if type(card) == "string" then + -- TODO: ViewAsSkill + return nil + else + local use = {} ---@type CardUseStruct + use.from = winner.id + use.tos = {} + for _, target in ipairs(targets) do + table.insert(use.tos, { target }) + end + if #use.tos == 0 then + use.tos = nil + end + use.cardId = card + return use + end + end + return nil +end ------------------------------------------------------------------------ -- use card logic, and wrappers @@ -580,7 +685,7 @@ function Room:askForResponse() end local onAim = function(room, cardUseEvent, aimEventCollaborators) local eventStages = { fk.TargetSpecifying, fk.TargetConfirming, fk.TargetSpecified, fk.TargetConfirmed } for _, stage in ipairs(eventStages) do - if not cardUseEvent.tos then + if (not cardUseEvent.tos) or #cardUseEvent.tos == 0 then return false end @@ -871,8 +976,6 @@ function Room:doCardEffect(cardEffectEvent) end if event == fk.PreCardEffect then - -- TODO: use jink - if Fk:getCardById(cardEffectEvent.cardId).name == 'slash' and not ( cardEffectEvent.disresponsive or @@ -880,38 +983,31 @@ function Room:doCardEffect(cardEffectEvent) table.contains(cardEffectEvent.disresponsiveList or {}, cardEffectEvent.to) or table.contains(cardEffectEvent.unoffsetableList or {}, cardEffectEvent.to) ) then - local result = self:doRequest(self:getPlayerById(cardEffectEvent.to), "PlayCard", cardEffectEvent.to) - if result ~= '' then - local data = json.decode(result) - local card = data.card - local targets = data.targets - if type(card) == "string" then - local card_data = json.decode(card) - local skill = Fk.skills[card_data.skill] - local selected_cards = card_data.subcards - skill:onEffect(self, { - from = cardEffectEvent.to, - cards = selected_cards, - tos = targets, - }) - else - local use = {} ---@type CardUseStruct - use.from = cardEffectEvent.to - use.toCardId = cardEffectEvent.cardId - use.responseToEvent = cardEffectEvent - use.cardId = card - self:useCard(use) - end + local to = self:getPlayerById(cardEffectEvent.to) + local use = self:askForUseCard(to, "jink") + if use then + use.toCardId = cardEffectEvent.cardId + use.responseToEvent = cardEffectEvent + self:useCard(use) end elseif Fk:getCardById(cardEffectEvent.cardId).type == Card.TypeTrick then - -- TODO: use nullification + local players = {} + for _, p in ipairs(self.players) do + local cards = p.player_cards[Player.Hand] + for _, cid in ipairs(cards) do + if Fk:getCardById(cid).name == "nullification" then + table.insert(players, p) + break + end + end + end - -- local use = {} ---@type CardUseStruct - -- use.from = cardEffectEvent.to - -- use.toCardId = cardEffectEvent.cardId - -- use.responseToEvent = cardEffectEvent - -- use.cardId = card - -- self:useCard(use) + local use = self:askForNullification(players) + if use then + use.toCardId = cardEffectEvent.cardId + use.responseToEvent = cardEffectEvent + self:useCard(use) + end end end diff --git a/lua/server/serverplayer.lua b/lua/server/serverplayer.lua index 99e1f319..e301487e 100644 --- a/lua/server/serverplayer.lua +++ b/lua/server/serverplayer.lua @@ -6,6 +6,7 @@ ---@field client_reply string ---@field default_reply string ---@field reply_ready boolean +---@field reply_cancel boolean ---@field phases Phase[] ---@field phase_state table[] ---@field phase_index integer @@ -25,6 +26,7 @@ function ServerPlayer:initialize(_self) self.client_reply = "" self.default_reply = "" self.reply_ready = false + self.reply_cancel = false self.phases = {} end @@ -44,6 +46,7 @@ function ServerPlayer:doRequest(command, jsonData, timeout) timeout = timeout or self.room.timeout self.client_reply = "" self.reply_ready = false + self.reply_cancel = false self.serverplayer:doRequest(command, jsonData, timeout) end @@ -61,6 +64,10 @@ function ServerPlayer:waitForReply(timeout) end self.request_data = "" self.client_reply = result + if result == "__cancel" then + result = "" + self.reply_cancel = true + end if result ~= "" then self.reply_ready = true end return result end diff --git a/packages/standard_cards/init.lua b/packages/standard_cards/init.lua index c60a4205..25785201 100644 --- a/packages/standard_cards/init.lua +++ b/packages/standard_cards/init.lua @@ -77,6 +77,9 @@ extension:addCards({ local jinkSkill = fk.CreateActiveSkill{ name = "jink_skill", + can_use = function() + return false + end, on_effect = function(self, room, effect) if effect.responseToEvent then effect.responseToEvent.isCancellOut = true diff --git a/qml/Pages/Room.qml b/qml/Pages/Room.qml index 7813597f..09ce90e6 100644 --- a/qml/Pages/Room.qml +++ b/qml/Pages/Room.qml @@ -22,6 +22,8 @@ Item { property alias dynamicCardArea: dynamicCardArea property var selected_targets: [] + property string responding_card + property bool respond_play: false Image { source: AppPath + "/image/gamebg" @@ -104,8 +106,8 @@ Item { from: "*"; to: "responding" ScriptAction { script: { - dashboard.enableCards(); - dashboard.enableSkills(); + dashboard.enableCards(responding_card); + dashboard.enableSkills(responding_card); progress.visible = true; okCancel.visible = true; } diff --git a/qml/Pages/RoomElement/Dashboard.qml b/qml/Pages/RoomElement/Dashboard.qml index 84fa0c3b..a725a4d9 100644 --- a/qml/Pages/RoomElement/Dashboard.qml +++ b/qml/Pages/RoomElement/Dashboard.qml @@ -111,7 +111,17 @@ RowLayout { } } - function enableCards() { + // If cname is set, we are responding card. + function enableCards(cname) { + if (cname) { + let ids = [], cards = handcardAreaItem.cards; + for (let i = 0; i < cards.length; i++) { + if (cards[i].name === cname) + ids.push(cards[i].cid); + } + handcardAreaItem.enableCards(ids); + return; + } // TODO: expand pile let ids = [], cards = handcardAreaItem.cards; for (let i = 0; i < cards.length; i++) { @@ -230,7 +240,11 @@ RowLayout { skillPanel.loseSkill(skill_name); } - function enableSkills() { + function enableSkills(cname) { + if (cname) { + // TODO: vs skill + return; + } for (let i = 0; i < skillButtons.count; i++) { let item = skillButtons.itemAt(i); item.enabled = JSON.parse(Backend.callLuaFunction("ActiveCanUse", [item.orig])); diff --git a/qml/Pages/RoomLogic.js b/qml/Pages/RoomLogic.js index acaa32e0..552df9b6 100644 --- a/qml/Pages/RoomLogic.js +++ b/qml/Pages/RoomLogic.js @@ -88,11 +88,11 @@ function doCancelButton() { dashboard.stopPending(); dashboard.deactivateSkillButton(); dashboard.unSelectAll(); - replyToServer(""); + replyToServer("__cancel"); return; } - replyToServer(""); + replyToServer("__cancel"); } function replyToServer(jsonData) { @@ -246,6 +246,12 @@ callbacks["AddPlayer"] = function(jsonData) { } function enableTargets(card) { // card: int | { skill: string, subcards: int[] } + if (roomScene.respond_play) { + let candidate = (!isNaN(card) && card !== -1) || typeof(card) === "string"; + okButton.enabled = candidate; + return; + } + let i = 0; let candidate = (!isNaN(card) && card !== -1) || typeof(card) === "string"; let all_photos = [dashboard.self]; @@ -591,6 +597,7 @@ callbacks["AskForUseActiveSkill"] = function(jsonData) { } // TODO: process prompt + roomScene.respond_play = false; roomScene.state = "responding"; dashboard.startPending(skill_name); cancelButton.enabled = cancelable; @@ -603,3 +610,37 @@ callbacks["CancelRequest"] = function() { callbacks["GameLog"] = function(jsonData) { roomScene.addToLog(jsonData) } + +callbacks["AskForUseCard"] = function(jsonData) { + // jsonData: card, prompt, cancelable, {} + let data = JSON.parse(jsonData); + let cardname = data[0]; + let prompt = data[1]; + + roomScene.promptText = Backend.translate(prompt) + .arg(Backend.translate(cardname)); + roomScene.responding_card = cardname; + roomScene.respond_play = false; + roomScene.state = "responding"; + okButton.enabled = false; + cancelButton.enabled = true; +} + +callbacks["AskForResponseCard"] = function(jsonData) { + // jsonData: card, prompt, cancelable, {} + let data = JSON.parse(jsonData); + let cardname = data[0]; + let prompt = data[1]; + + roomScene.promptText = Backend.translate(prompt) + .arg(Backend.translate(cardname)); + roomScene.responding_card = cardname; + roomScene.respond_play = true; + roomScene.state = "responding"; + okButton.enabled = false; + cancelButton.enabled = true; +} + +callbacks["WaitForNullification"] = function() { + roomScene.state = "notactive"; +} diff --git a/src/core/util.cpp b/src/core/util.cpp index 76a0003c..c1eadb2b 100644 --- a/src/core/util.cpp +++ b/src/core/util.cpp @@ -108,7 +108,8 @@ static int callback(void *jsonDoc, int argc, char **argv, char **cols) { QJsonObject SelectFromDatabase(sqlite3 *db, const QString &sql) { QJsonObject obj; - sqlite3_exec(db, sql.toUtf8().data(), callback, (void *)&obj, nullptr); + auto bytes = sql.toUtf8(); + sqlite3_exec(db, bytes.data(), callback, (void *)&obj, nullptr); return obj; } @@ -118,7 +119,8 @@ QString SelectFromDb(sqlite3 *db, const QString &sql) { } void ExecSQL(sqlite3 *db, const QString &sql) { - sqlite3_exec(db, sql.toUtf8().data(), nullptr, nullptr, nullptr); + auto bytes = sql.toUtf8(); + sqlite3_exec(db, bytes.data(), nullptr, nullptr, nullptr); } void CloseDatabase(sqlite3 *db) { diff --git a/src/main.cpp b/src/main.cpp index 10fa60c6..73013009 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -44,23 +44,23 @@ static bool copyPath(const QString &srcFilePath, const QString &tgtFilePath) void fkMsgHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { fprintf(stderr, "\r[%s] ", QTime::currentTime().toString("hh:mm:ss").toLatin1().constData()); - auto localMsg = msg.toUtf8().constData(); + auto localMsg = msg.toUtf8(); auto threadName = QThread::currentThread()->objectName().toLatin1().constData(); switch (type) { case QtDebugMsg: - fprintf(stderr, "[%s/\e[1;30mDEBUG\e[0m] %s\n", threadName, localMsg); + fprintf(stderr, "[%s/\e[1;30mDEBUG\e[0m] %s\n", threadName, localMsg.constData()); break; case QtInfoMsg: - fprintf(stderr, "[%s/\e[1;32mINFO\e[0m] %s\n", threadName, localMsg); + fprintf(stderr, "[%s/\e[1;32mINFO\e[0m] %s\n", threadName, localMsg.constData()); break; case QtWarningMsg: - fprintf(stderr, "[%s/\e[1;33mWARNING\e[0m] %s\n", threadName, localMsg); + fprintf(stderr, "[%s/\e[1;33mWARNING\e[0m] %s\n", threadName, localMsg.constData()); break; case QtCriticalMsg: - fprintf(stderr, "[%s/\e[1;31mCRITICAL\e[0m] %s\n", threadName, localMsg); + fprintf(stderr, "[%s/\e[1;31mCRITICAL\e[0m] %s\n", threadName, localMsg.constData()); break; case QtFatalMsg: - fprintf(stderr, "[%s/\e[1;31mFATAL\e[0m] %s\n", threadName, localMsg); + fprintf(stderr, "[%s/\e[1;31mFATAL\e[0m] %s\n", threadName, localMsg.constData()); break; } } diff --git a/src/server/serverplayer.cpp b/src/server/serverplayer.cpp index fcb13589..26491ce1 100644 --- a/src/server/serverplayer.cpp +++ b/src/server/serverplayer.cpp @@ -99,7 +99,7 @@ QString ServerPlayer::waitForReply(int timeout) QString ret; if (getState() != Player::Online) { QThread::sleep(1); - ret = ""; + ret = "__cancel"; } else { ret = router->waitForReply(timeout); } diff --git a/src/server/shell.cpp b/src/server/shell.cpp index 01dc3b28..0edddae6 100644 --- a/src/server/shell.cpp +++ b/src/server/shell.cpp @@ -33,7 +33,8 @@ const char *Shell::ColoredText(const char *input, Color color, TextType type) { header.append(QString::number(30 + color)); header.append("m"); header.append(str); - return header.toUtf8().constData(); + auto bytes = header.toUtf8(); + return bytes.constData(); } void Shell::helpCommand(QStringList &) { @@ -103,7 +104,8 @@ void Shell::run() { auto command_list = command.split(' '); auto func = handler_map[command_list.first()]; if (!func) { - qWarning("Unknown command \"%s\". Type \"help\" for hints.", command_list.first().toUtf8().constData()); + auto bytes = command_list.first().toUtf8(); + qWarning("Unknown command \"%s\". Type \"help\" for hints.", bytes.constData()); } else { command_list.removeFirst(); (this->*func)(command_list); diff --git a/src/swig/naturalvar.i b/src/swig/naturalvar.i index 03b736d4..8bd216aa 100644 --- a/src/swig/naturalvar.i +++ b/src/swig/naturalvar.i @@ -61,7 +61,8 @@ lua_createtable(L, $1.length(), 0); for (int i = 0; i < $1.length(); i++) { QString str = $1.at(i); - lua_pushstring(L, str.toUtf8().constData()); + auto bytes = str.toUtf8(); + lua_pushstring(L, bytes.constData()); lua_rawseti(L, -2, i + 1); } diff --git a/src/ui/qmlbackend.cpp b/src/ui/qmlbackend.cpp index 8443ee20..2d96ab90 100644 --- a/src/ui/qmlbackend.cpp +++ b/src/ui/qmlbackend.cpp @@ -99,7 +99,8 @@ bool QmlBackend::isDir(const QString &file) { QString QmlBackend::translate(const QString &src) { lua_State *L = ClientInstance->getLuaState(); lua_getglobal(L, "Translate"); - lua_pushstring(L, src.toUtf8().data()); + auto bytes = src.toUtf8(); + lua_pushstring(L, bytes.data()); int err = lua_pcall(L, 1, 1, 0); const char *result = lua_tostring(L, -1); @@ -125,9 +126,11 @@ void QmlBackend::pushLuaValue(lua_State *L, QVariant v) { case QMetaType::Double: lua_pushnumber(L, v.toDouble()); break; - case QMetaType::QString: - lua_pushstring(L, v.toString().toUtf8().data()); + case QMetaType::QString: { + auto bytes = v.toString().toUtf8(); + lua_pushstring(L, bytes.data()); break; + } case QMetaType::QVariantList: lua_newtable(L); list = v.toList();