Jink&null (#32)

* rewrite toUtf8().data()

* jink, askforuse, askforresp

* nullification
This commit is contained in:
notify 2022-12-18 21:19:35 +08:00 committed by GitHub
parent c4f90a50c7
commit 22235ee6ec
13 changed files with 230 additions and 56 deletions

View File

@ -256,6 +256,9 @@ Fk:loadTranslationTable{
["$Equip"] = "装备区", ["$Equip"] = "装备区",
["$Judge"] = "判定区", ["$Judge"] = "判定区",
["#AskForUseActiveSkill"] = "请使用技能 %1", ["#AskForUseActiveSkill"] = "请使用技能 %1",
["#AskForUseCard"] = "请使用卡牌 %1",
["#AskForResponseCard"] = "请打出卡牌 %1",
["#AskForNullification"] = "无懈",
["Trust"] = "托管", ["Trust"] = "托管",
["Sort Cards"] = "牌序", ["Sort Cards"] = "牌序",

View File

@ -295,7 +295,7 @@ end
---@param players ServerPlayer[] ---@param players ServerPlayer[]
function Room:doRaceRequest(command, players, jsonData) function Room:doRaceRequest(command, players, jsonData)
players = players or self.players players = players or self.players
self:notifyMoveFocus(players, command) -- self:notifyMoveFocus(players, command)
for _, p in ipairs(players) do for _, p in ipairs(players) do
self:doRequest(p, command, jsonData or p.request_data, false) self:doRequest(p, command, jsonData or p.request_data, false)
end end
@ -304,6 +304,7 @@ function Room:doRaceRequest(command, players, jsonData)
local currentTime = os.time() local currentTime = os.time()
local elapsed = 0 local elapsed = 0
local winner local winner
local canceled_players = {}
while true do while true do
elapsed = os.time() - currentTime elapsed = os.time() - currentTime
if remainTime - elapsed <= 0 then if remainTime - elapsed <= 0 then
@ -315,11 +316,19 @@ function Room:doRaceRequest(command, players, jsonData)
winner = p winner = p
break break
end end
if p.reply_cancel then
table.insertIfNeed(canceled_players, p)
end
end end
if winner then if winner then
self:doBroadcastNotify("CancelRequest", "") self:doBroadcastNotify("CancelRequest", "")
return winner return winner
end end
if #players == #canceled_players then
return nil
end
end end
end end
@ -566,8 +575,104 @@ function Room:askForSkillInvoke(player, skill_name, data)
return invoked return invoked
end end
function Room:askForUseCard() end ---@param player ServerPlayer
function Room:askForResponse() end ---@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 -- use card logic, and wrappers
@ -580,7 +685,7 @@ function Room:askForResponse() end
local onAim = function(room, cardUseEvent, aimEventCollaborators) local onAim = function(room, cardUseEvent, aimEventCollaborators)
local eventStages = { fk.TargetSpecifying, fk.TargetConfirming, fk.TargetSpecified, fk.TargetConfirmed } local eventStages = { fk.TargetSpecifying, fk.TargetConfirming, fk.TargetSpecified, fk.TargetConfirmed }
for _, stage in ipairs(eventStages) do for _, stage in ipairs(eventStages) do
if not cardUseEvent.tos then if (not cardUseEvent.tos) or #cardUseEvent.tos == 0 then
return false return false
end end
@ -871,8 +976,6 @@ function Room:doCardEffect(cardEffectEvent)
end end
if event == fk.PreCardEffect then if event == fk.PreCardEffect then
-- TODO: use jink
if Fk:getCardById(cardEffectEvent.cardId).name == 'slash' and if Fk:getCardById(cardEffectEvent.cardId).name == 'slash' and
not ( not (
cardEffectEvent.disresponsive or cardEffectEvent.disresponsive or
@ -880,38 +983,31 @@ function Room:doCardEffect(cardEffectEvent)
table.contains(cardEffectEvent.disresponsiveList or {}, cardEffectEvent.to) or table.contains(cardEffectEvent.disresponsiveList or {}, cardEffectEvent.to) or
table.contains(cardEffectEvent.unoffsetableList or {}, cardEffectEvent.to) table.contains(cardEffectEvent.unoffsetableList or {}, cardEffectEvent.to)
) then ) then
local result = self:doRequest(self:getPlayerById(cardEffectEvent.to), "PlayCard", cardEffectEvent.to) local to = self:getPlayerById(cardEffectEvent.to)
if result ~= '' then local use = self:askForUseCard(to, "jink")
local data = json.decode(result) if use then
local card = data.card use.toCardId = cardEffectEvent.cardId
local targets = data.targets use.responseToEvent = cardEffectEvent
if type(card) == "string" then self:useCard(use)
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
end end
elseif Fk:getCardById(cardEffectEvent.cardId).type == Card.TypeTrick then 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 local use = self:askForNullification(players)
-- use.from = cardEffectEvent.to if use then
-- use.toCardId = cardEffectEvent.cardId use.toCardId = cardEffectEvent.cardId
-- use.responseToEvent = cardEffectEvent use.responseToEvent = cardEffectEvent
-- use.cardId = card self:useCard(use)
-- self:useCard(use) end
end end
end end

View File

@ -6,6 +6,7 @@
---@field client_reply string ---@field client_reply string
---@field default_reply string ---@field default_reply string
---@field reply_ready boolean ---@field reply_ready boolean
---@field reply_cancel boolean
---@field phases Phase[] ---@field phases Phase[]
---@field phase_state table[] ---@field phase_state table[]
---@field phase_index integer ---@field phase_index integer
@ -25,6 +26,7 @@ function ServerPlayer:initialize(_self)
self.client_reply = "" self.client_reply = ""
self.default_reply = "" self.default_reply = ""
self.reply_ready = false self.reply_ready = false
self.reply_cancel = false
self.phases = {} self.phases = {}
end end
@ -44,6 +46,7 @@ function ServerPlayer:doRequest(command, jsonData, timeout)
timeout = timeout or self.room.timeout timeout = timeout or self.room.timeout
self.client_reply = "" self.client_reply = ""
self.reply_ready = false self.reply_ready = false
self.reply_cancel = false
self.serverplayer:doRequest(command, jsonData, timeout) self.serverplayer:doRequest(command, jsonData, timeout)
end end
@ -61,6 +64,10 @@ function ServerPlayer:waitForReply(timeout)
end end
self.request_data = "" self.request_data = ""
self.client_reply = result self.client_reply = result
if result == "__cancel" then
result = ""
self.reply_cancel = true
end
if result ~= "" then self.reply_ready = true end if result ~= "" then self.reply_ready = true end
return result return result
end end

View File

@ -77,6 +77,9 @@ extension:addCards({
local jinkSkill = fk.CreateActiveSkill{ local jinkSkill = fk.CreateActiveSkill{
name = "jink_skill", name = "jink_skill",
can_use = function()
return false
end,
on_effect = function(self, room, effect) on_effect = function(self, room, effect)
if effect.responseToEvent then if effect.responseToEvent then
effect.responseToEvent.isCancellOut = true effect.responseToEvent.isCancellOut = true

View File

@ -22,6 +22,8 @@ Item {
property alias dynamicCardArea: dynamicCardArea property alias dynamicCardArea: dynamicCardArea
property var selected_targets: [] property var selected_targets: []
property string responding_card
property bool respond_play: false
Image { Image {
source: AppPath + "/image/gamebg" source: AppPath + "/image/gamebg"
@ -104,8 +106,8 @@ Item {
from: "*"; to: "responding" from: "*"; to: "responding"
ScriptAction { ScriptAction {
script: { script: {
dashboard.enableCards(); dashboard.enableCards(responding_card);
dashboard.enableSkills(); dashboard.enableSkills(responding_card);
progress.visible = true; progress.visible = true;
okCancel.visible = true; okCancel.visible = true;
} }

View File

@ -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 // TODO: expand pile
let ids = [], cards = handcardAreaItem.cards; let ids = [], cards = handcardAreaItem.cards;
for (let i = 0; i < cards.length; i++) { for (let i = 0; i < cards.length; i++) {
@ -230,7 +240,11 @@ RowLayout {
skillPanel.loseSkill(skill_name); skillPanel.loseSkill(skill_name);
} }
function enableSkills() { function enableSkills(cname) {
if (cname) {
// TODO: vs skill
return;
}
for (let i = 0; i < skillButtons.count; i++) { for (let i = 0; i < skillButtons.count; i++) {
let item = skillButtons.itemAt(i); let item = skillButtons.itemAt(i);
item.enabled = JSON.parse(Backend.callLuaFunction("ActiveCanUse", [item.orig])); item.enabled = JSON.parse(Backend.callLuaFunction("ActiveCanUse", [item.orig]));

View File

@ -88,11 +88,11 @@ function doCancelButton() {
dashboard.stopPending(); dashboard.stopPending();
dashboard.deactivateSkillButton(); dashboard.deactivateSkillButton();
dashboard.unSelectAll(); dashboard.unSelectAll();
replyToServer(""); replyToServer("__cancel");
return; return;
} }
replyToServer(""); replyToServer("__cancel");
} }
function replyToServer(jsonData) { function replyToServer(jsonData) {
@ -246,6 +246,12 @@ callbacks["AddPlayer"] = function(jsonData) {
} }
function enableTargets(card) { // card: int | { skill: string, subcards: int[] } 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 i = 0;
let candidate = (!isNaN(card) && card !== -1) || typeof(card) === "string"; let candidate = (!isNaN(card) && card !== -1) || typeof(card) === "string";
let all_photos = [dashboard.self]; let all_photos = [dashboard.self];
@ -591,6 +597,7 @@ callbacks["AskForUseActiveSkill"] = function(jsonData) {
} }
// TODO: process prompt // TODO: process prompt
roomScene.respond_play = false;
roomScene.state = "responding"; roomScene.state = "responding";
dashboard.startPending(skill_name); dashboard.startPending(skill_name);
cancelButton.enabled = cancelable; cancelButton.enabled = cancelable;
@ -603,3 +610,37 @@ callbacks["CancelRequest"] = function() {
callbacks["GameLog"] = function(jsonData) { callbacks["GameLog"] = function(jsonData) {
roomScene.addToLog(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";
}

View File

@ -108,7 +108,8 @@ static int callback(void *jsonDoc, int argc, char **argv, char **cols) {
QJsonObject SelectFromDatabase(sqlite3 *db, const QString &sql) { QJsonObject SelectFromDatabase(sqlite3 *db, const QString &sql) {
QJsonObject obj; 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; return obj;
} }
@ -118,7 +119,8 @@ QString SelectFromDb(sqlite3 *db, const QString &sql) {
} }
void ExecSQL(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) { void CloseDatabase(sqlite3 *db) {

View File

@ -44,23 +44,23 @@ static bool copyPath(const QString &srcFilePath, const QString &tgtFilePath)
void fkMsgHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { void fkMsgHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
fprintf(stderr, "\r[%s] ", QTime::currentTime().toString("hh:mm:ss").toLatin1().constData()); 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(); auto threadName = QThread::currentThread()->objectName().toLatin1().constData();
switch (type) { switch (type) {
case QtDebugMsg: 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; break;
case QtInfoMsg: 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; break;
case QtWarningMsg: 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; break;
case QtCriticalMsg: 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; break;
case QtFatalMsg: 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; break;
} }
} }

View File

@ -99,7 +99,7 @@ QString ServerPlayer::waitForReply(int timeout)
QString ret; QString ret;
if (getState() != Player::Online) { if (getState() != Player::Online) {
QThread::sleep(1); QThread::sleep(1);
ret = ""; ret = "__cancel";
} else { } else {
ret = router->waitForReply(timeout); ret = router->waitForReply(timeout);
} }

View File

@ -33,7 +33,8 @@ const char *Shell::ColoredText(const char *input, Color color, TextType type) {
header.append(QString::number(30 + color)); header.append(QString::number(30 + color));
header.append("m"); header.append("m");
header.append(str); header.append(str);
return header.toUtf8().constData(); auto bytes = header.toUtf8();
return bytes.constData();
} }
void Shell::helpCommand(QStringList &) { void Shell::helpCommand(QStringList &) {
@ -103,7 +104,8 @@ void Shell::run() {
auto command_list = command.split(' '); auto command_list = command.split(' ');
auto func = handler_map[command_list.first()]; auto func = handler_map[command_list.first()];
if (!func) { 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 { } else {
command_list.removeFirst(); command_list.removeFirst();
(this->*func)(command_list); (this->*func)(command_list);

View File

@ -61,7 +61,8 @@ lua_createtable(L, $1.length(), 0);
for (int i = 0; i < $1.length(); i++) { for (int i = 0; i < $1.length(); i++) {
QString str = $1.at(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); lua_rawseti(L, -2, i + 1);
} }

View File

@ -99,7 +99,8 @@ bool QmlBackend::isDir(const QString &file) {
QString QmlBackend::translate(const QString &src) { QString QmlBackend::translate(const QString &src) {
lua_State *L = ClientInstance->getLuaState(); lua_State *L = ClientInstance->getLuaState();
lua_getglobal(L, "Translate"); 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); int err = lua_pcall(L, 1, 1, 0);
const char *result = lua_tostring(L, -1); const char *result = lua_tostring(L, -1);
@ -125,9 +126,11 @@ void QmlBackend::pushLuaValue(lua_State *L, QVariant v) {
case QMetaType::Double: case QMetaType::Double:
lua_pushnumber(L, v.toDouble()); lua_pushnumber(L, v.toDouble());
break; break;
case QMetaType::QString: case QMetaType::QString: {
lua_pushstring(L, v.toString().toUtf8().data()); auto bytes = v.toString().toUtf8();
lua_pushstring(L, bytes.data());
break; break;
}
case QMetaType::QVariantList: case QMetaType::QVariantList:
lua_newtable(L); lua_newtable(L);
list = v.toList(); list = v.toList();