mirror of
https://github.com/Qsgs-Fans/FreeKill.git
synced 2024-11-16 11:42:45 +08:00
Enhancement (#263)
- smart_ai搭了个壳子 - askForPoxi - 增加判断额外回合之法以及fix - 修trigger - 增加使用和打出的禁止技提示 - 修复卡牌标记,attach主动技显示为蓝色按钮
This commit is contained in:
parent
048071a6d5
commit
203736e38e
|
@ -139,7 +139,7 @@ Item {
|
||||||
Text {
|
Text {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
text: Backend.translate("Room List")
|
text: Backend.translate("Room List").arg(roomModel.count)
|
||||||
}
|
}
|
||||||
ListView {
|
ListView {
|
||||||
id: roomList
|
id: roomList
|
||||||
|
|
|
@ -59,7 +59,7 @@ Item {
|
||||||
id: bgm
|
id: bgm
|
||||||
source: config.bgmFile
|
source: config.bgmFile
|
||||||
|
|
||||||
// loops: MediaPlayer.Infinite
|
loops: MediaPlayer.Infinite
|
||||||
onPlaybackStateChanged: {
|
onPlaybackStateChanged: {
|
||||||
if (playbackState == MediaPlayer.StoppedState && roomScene.isStarted)
|
if (playbackState == MediaPlayer.StoppedState && roomScene.isStarted)
|
||||||
play();
|
play();
|
||||||
|
|
|
@ -1070,6 +1070,31 @@ callbacks["AskForCardsChosen"] = (jsonData) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
callbacks["AskForPoxi"] = (jsonData) => {
|
||||||
|
const { type, data } = JSON.parse(jsonData);
|
||||||
|
|
||||||
|
roomScene.state = "replying";
|
||||||
|
roomScene.popupBox.sourceComponent = Qt.createComponent("../RoomElement/PoxiBox.qml");
|
||||||
|
const box = roomScene.popupBox.item;
|
||||||
|
box.poxi_type = type;
|
||||||
|
box.card_data = data;
|
||||||
|
for (let d of data) {
|
||||||
|
const arr = [];
|
||||||
|
const ids = d[1];
|
||||||
|
|
||||||
|
ids.forEach(id => {
|
||||||
|
const card_data = JSON.parse(Backend.callLuaFunction("GetCardData", [id]));
|
||||||
|
arr.push(card_data);
|
||||||
|
});
|
||||||
|
box.addCustomCards(d[0], arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
roomScene.popupBox.moveToCenter();
|
||||||
|
box.cardsSelected.connect((ids) => {
|
||||||
|
replyToServer(JSON.stringify(ids));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
callbacks["AskForMoveCardInBoard"] = (jsonData) => {
|
callbacks["AskForMoveCardInBoard"] = (jsonData) => {
|
||||||
const data = JSON.parse(jsonData);
|
const data = JSON.parse(jsonData);
|
||||||
const { cards, cardsPosition, generalNames, playerIds } = data;
|
const { cards, cardsPosition, generalNames, playerIds } = data;
|
||||||
|
|
|
@ -62,7 +62,12 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mark_name.startsWith('@$')) {
|
if (mark_name.startsWith('@$')) {
|
||||||
params.cardNames = mark_extra.split(',');
|
let data = mark_extra.split(',');
|
||||||
|
if (!Object.is(parseInt(data[0]), NaN)) {
|
||||||
|
params.ids = data.map(s => parseInt(s));
|
||||||
|
} else {
|
||||||
|
params.cardNames = data;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let data = JSON.parse(Backend.callLuaFunction("GetPile", [root.parent.playerid, mark_name]));
|
let data = JSON.parse(Backend.callLuaFunction("GetPile", [root.parent.playerid, mark_name]));
|
||||||
data = data.filter((e) => e !== -1);
|
data = data.filter((e) => e !== -1);
|
||||||
|
|
|
@ -31,11 +31,13 @@ Item {
|
||||||
property string color: "" // only use when suit is empty
|
property string color: "" // only use when suit is empty
|
||||||
property string footnote: "" // footnote, e.g. "A use card to B"
|
property string footnote: "" // footnote, e.g. "A use card to B"
|
||||||
property bool footnoteVisible: false
|
property bool footnoteVisible: false
|
||||||
|
property string prohibitReason: ""
|
||||||
property bool known: true // if false it only show a card back
|
property bool known: true // if false it only show a card back
|
||||||
property bool enabled: true // if false the card will be grey
|
property bool enabled: true // if false the card will be grey
|
||||||
property alias card: cardItem
|
property alias card: cardItem
|
||||||
property alias glow: glowItem
|
property alias glow: glowItem
|
||||||
property var mark: ({})
|
property var mark: ({})
|
||||||
|
property alias chosenInBox: chosen.visible
|
||||||
|
|
||||||
function getColor() {
|
function getColor() {
|
||||||
if (suit != "")
|
if (suit != "")
|
||||||
|
@ -88,6 +90,7 @@ Item {
|
||||||
visible: false
|
visible: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: cardItem
|
id: cardItem
|
||||||
source: known ? SkinBank.getCardPicture(cid || name)
|
source: known ? SkinBank.getCardPicture(cid || name)
|
||||||
|
@ -217,6 +220,15 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: chosen
|
||||||
|
visible: false
|
||||||
|
source: SkinBank.CARD_DIR + "chosen"
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
y: 90
|
||||||
|
scale: 1.25
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
visible: !root.selectable
|
visible: !root.selectable
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
@ -224,6 +236,24 @@ Item {
|
||||||
opacity: 0.7
|
opacity: 0.7
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: prohibitText
|
||||||
|
visible: !root.selectable
|
||||||
|
anchors.centerIn: parent
|
||||||
|
font.family: fontLibian.name
|
||||||
|
font.pixelSize: 18
|
||||||
|
opacity: 0.9
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
lineHeight: 18
|
||||||
|
lineHeightMode: Text.FixedHeight
|
||||||
|
color: "snow"
|
||||||
|
width: 20
|
||||||
|
wrapMode: Text.WrapAnywhere
|
||||||
|
style: Text.Outline
|
||||||
|
styleColor: "red"
|
||||||
|
text: prohibitReason
|
||||||
|
}
|
||||||
|
|
||||||
TapHandler {
|
TapHandler {
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.NoButton
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.NoButton
|
||||||
gesturePolicy: TapHandler.WithinBounds
|
gesturePolicy: TapHandler.WithinBounds
|
||||||
|
|
|
@ -166,17 +166,35 @@ RowLayout {
|
||||||
const ids = [];
|
const ids = [];
|
||||||
let cards = handcardAreaItem.cards;
|
let cards = handcardAreaItem.cards;
|
||||||
for (let i = 0; i < cards.length; i++) {
|
for (let i = 0; i < cards.length; i++) {
|
||||||
|
cards[i].prohibitReason = "";
|
||||||
if (cardValid(cards[i].cid, cname)) {
|
if (cardValid(cards[i].cid, cname)) {
|
||||||
ids.push(cards[i].cid);
|
ids.push(cards[i].cid);
|
||||||
|
} else {
|
||||||
|
const prohibitReason = Backend.callLuaFunction(
|
||||||
|
"GetCardProhibitReason",
|
||||||
|
[cards[i].cid, roomScene.respond_play ? "response" : "use", cname]
|
||||||
|
);
|
||||||
|
if (prohibitReason) {
|
||||||
|
cards[i].prohibitReason = prohibitReason;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cards = self.equipArea.getAllCards();
|
cards = self.equipArea.getAllCards();
|
||||||
cards.forEach(c => {
|
cards.forEach(c => {
|
||||||
|
c.prohibitReason = "";
|
||||||
if (cardValid(c.cid, cname)) {
|
if (cardValid(c.cid, cname)) {
|
||||||
ids.push(c.cid);
|
ids.push(c.cid);
|
||||||
if (!expanded_piles["_equip"]) {
|
if (!expanded_piles["_equip"]) {
|
||||||
expandPile("_equip");
|
expandPile("_equip");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
const prohibitReason = Backend.callLuaFunction(
|
||||||
|
"GetCardProhibitReason",
|
||||||
|
[c.cid, roomScene.respond_play ? "response" : "use", cname]
|
||||||
|
);
|
||||||
|
if (prohibitReason) {
|
||||||
|
c.prohibitReason = prohibitReason;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -202,6 +220,7 @@ RowLayout {
|
||||||
|
|
||||||
const ids = [], cards = handcardAreaItem.cards;
|
const ids = [], cards = handcardAreaItem.cards;
|
||||||
for (let i = 0; i < cards.length; i++) {
|
for (let i = 0; i < cards.length; i++) {
|
||||||
|
cards[i].prohibitReason = "";
|
||||||
if (JSON.parse(Backend.callLuaFunction("CanUseCard", [cards[i].cid, Self.id]))) {
|
if (JSON.parse(Backend.callLuaFunction("CanUseCard", [cards[i].cid, Self.id]))) {
|
||||||
ids.push(cards[i].cid);
|
ids.push(cards[i].cid);
|
||||||
} else {
|
} else {
|
||||||
|
@ -214,6 +233,14 @@ RowLayout {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// still cannot use? show message on card
|
||||||
|
if (!ids.includes(cards[i].cid)) {
|
||||||
|
const prohibitReason = Backend.callLuaFunction("GetCardProhibitReason", [cards[i].cid, "play"]);
|
||||||
|
if (prohibitReason) {
|
||||||
|
cards[i].prohibitReason = prohibitReason;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handcardAreaItem.enableCards(ids)
|
handcardAreaItem.enableCards(ids)
|
||||||
|
@ -372,6 +399,11 @@ RowLayout {
|
||||||
item.enabled = item.pressed;
|
item.enabled = item.pressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cards = handcardAreaItem.cards;
|
||||||
|
for (let i = 0; i < cards.length; i++) {
|
||||||
|
cards[i].prohibitReason = "";
|
||||||
|
}
|
||||||
|
|
||||||
updatePending();
|
updatePending();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@ Item {
|
||||||
card.selectable = false;
|
card.selectable = false;
|
||||||
card.showDetail = false;
|
card.showDetail = false;
|
||||||
card.selectedChanged.disconnect(adjustCards);
|
card.selectedChanged.disconnect(adjustCards);
|
||||||
|
card.prohibitReason = "";
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,10 +78,10 @@ GraphicsBox {
|
||||||
}
|
}
|
||||||
onSelectedChanged: {
|
onSelectedChanged: {
|
||||||
if (selected) {
|
if (selected) {
|
||||||
virt_name = "$Selected";
|
chosenInBox = true;
|
||||||
root.selected_ids.push(cid);
|
root.selected_ids.push(cid);
|
||||||
} else {
|
} else {
|
||||||
virt_name = "";
|
chosenInBox = false;
|
||||||
root.selected_ids.splice(root.selected_ids.indexOf(cid), 1);
|
root.selected_ids.splice(root.selected_ids.indexOf(cid), 1);
|
||||||
}
|
}
|
||||||
root.selected_ids = root.selected_ids;
|
root.selected_ids = root.selected_ids;
|
||||||
|
@ -122,38 +122,6 @@ GraphicsBox {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addHandcards(cards) {
|
|
||||||
let handcards = findAreaModel('$Hand').areaCards;
|
|
||||||
if (cards instanceof Array) {
|
|
||||||
for (let i = 0; i < cards.length; i++)
|
|
||||||
handcards.append(cards[i]);
|
|
||||||
} else {
|
|
||||||
handcards.append(cards);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addEquips(cards)
|
|
||||||
{
|
|
||||||
let equips = findAreaModel('$Equip').areaCards;
|
|
||||||
if (cards instanceof Array) {
|
|
||||||
for (let i = 0; i < cards.length; i++)
|
|
||||||
equips.append(cards[i]);
|
|
||||||
} else {
|
|
||||||
equips.append(cards);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addDelayedTricks(cards)
|
|
||||||
{
|
|
||||||
let delayedTricks = findAreaModel('$Judge').areaCards;
|
|
||||||
if (cards instanceof Array) {
|
|
||||||
for (let i = 0; i < cards.length; i++)
|
|
||||||
delayedTricks.append(cards[i]);
|
|
||||||
} else {
|
|
||||||
delayedTricks.append(cards);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addCustomCards(name, cards) {
|
function addCustomCards(name, cards) {
|
||||||
let area = findAreaModel(name).areaCards;
|
let area = findAreaModel(name).areaCards;
|
||||||
if (cards instanceof Array) {
|
if (cards instanceof Array) {
|
||||||
|
|
138
Fk/RoomElement/PoxiBox.qml
Normal file
138
Fk/RoomElement/PoxiBox.qml
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Fk.Pages
|
||||||
|
|
||||||
|
GraphicsBox {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
title.text: Backend.callLuaFunction("PoxiPrompt", [poxi_type, card_data])
|
||||||
|
|
||||||
|
// TODO: Adjust the UI design in case there are more than 7 cards
|
||||||
|
width: 70 + 700
|
||||||
|
height: 64 + Math.min(cardView.contentHeight, 400) + 20
|
||||||
|
|
||||||
|
signal cardSelected(int cid)
|
||||||
|
signal cardsSelected(var ids)
|
||||||
|
property var selected_ids: []
|
||||||
|
property string poxi_type
|
||||||
|
property var card_data
|
||||||
|
|
||||||
|
ListModel {
|
||||||
|
id: cardModel
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: cardView
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.topMargin: 40
|
||||||
|
anchors.leftMargin: 20
|
||||||
|
anchors.rightMargin: 20
|
||||||
|
anchors.bottomMargin: 20
|
||||||
|
spacing: 20
|
||||||
|
model: cardModel
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
delegate: RowLayout {
|
||||||
|
spacing: 15
|
||||||
|
visible: areaCards.count > 0
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
border.color: "#A6967A"
|
||||||
|
radius: 5
|
||||||
|
color: "transparent"
|
||||||
|
width: 18
|
||||||
|
height: 130
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
|
||||||
|
Text {
|
||||||
|
color: "#E4D5A0"
|
||||||
|
text: Backend.translate(areaName)
|
||||||
|
anchors.fill: parent
|
||||||
|
wrapMode: Text.WrapAnywhere
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
font.pixelSize: 15
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GridLayout {
|
||||||
|
columns: 7
|
||||||
|
Repeater {
|
||||||
|
model: areaCards
|
||||||
|
|
||||||
|
CardItem {
|
||||||
|
cid: model.cid
|
||||||
|
name: model.name || ""
|
||||||
|
suit: model.suit || ""
|
||||||
|
number: model.number || 0
|
||||||
|
autoBack: false
|
||||||
|
known: model.cid !== -1
|
||||||
|
selectable: {
|
||||||
|
return root.selected_ids.includes(model.cid) || JSON.parse(Backend.callLuaFunction(
|
||||||
|
"PoxiFilter",
|
||||||
|
[root.poxi_type, model.cid, root.selected_ids, root.card_data]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
onSelectedChanged: {
|
||||||
|
if (selected) {
|
||||||
|
chosenInBox = true;
|
||||||
|
root.selected_ids.push(cid);
|
||||||
|
} else {
|
||||||
|
chosenInBox = false;
|
||||||
|
root.selected_ids.splice(root.selected_ids.indexOf(cid), 1);
|
||||||
|
}
|
||||||
|
root.selected_ids = root.selected_ids;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MetroButton {
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
text: Backend.translate("OK")
|
||||||
|
enabled: {
|
||||||
|
return JSON.parse(Backend.callLuaFunction(
|
||||||
|
"PoxiFeasible",
|
||||||
|
[root.poxi_type, root.selected_ids, root.card_data]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
onClicked: root.cardsSelected(root.selected_ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
onCardSelected: finished();
|
||||||
|
|
||||||
|
function findAreaModel(name) {
|
||||||
|
let ret;
|
||||||
|
for (let i = 0; i < cardModel.count; i++) {
|
||||||
|
let item = cardModel.get(i);
|
||||||
|
if (item.areaName == name) {
|
||||||
|
ret = item;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!ret) {
|
||||||
|
ret = {
|
||||||
|
areaName: name,
|
||||||
|
areaCards: [],
|
||||||
|
}
|
||||||
|
cardModel.append(ret);
|
||||||
|
ret = findAreaModel(name);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function addCustomCards(name, cards) {
|
||||||
|
let area = findAreaModel(name).areaCards;
|
||||||
|
if (cards instanceof Array) {
|
||||||
|
for (let i = 0; i < cards.length; i++)
|
||||||
|
area.append(cards[i]);
|
||||||
|
} else {
|
||||||
|
area.append(cards);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,9 +23,17 @@ Item {
|
||||||
x: -13 - 120 * 0.166
|
x: -13 - 120 * 0.166
|
||||||
y: -6 - 55 * 0.166
|
y: -6 - 55 * 0.166
|
||||||
scale: 0.66
|
scale: 0.66
|
||||||
source: type === "notactive" ? ""
|
source: {
|
||||||
: AppPath + "/image/button/skill/" + type + "/"
|
if (type === "notactive") {
|
||||||
+ (enabled ? (pressed ? "pressed" : "normal") : "disabled")
|
return "";
|
||||||
|
}
|
||||||
|
let ret = AppPath + "/image/button/skill/" + type + "/";
|
||||||
|
let suffix = enabled ? (pressed ? "pressed" : "normal") : "disabled";
|
||||||
|
if (enabled && type === "active" && orig.endsWith("&")) {
|
||||||
|
suffix += "-attach";
|
||||||
|
}
|
||||||
|
return ret + suffix;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
|
|
BIN
image/button/skill/active/normal-attach.png
Normal file
BIN
image/button/skill/active/normal-attach.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.2 KiB |
BIN
image/button/skill/active/pressed-attach.png
Normal file
BIN
image/button/skill/active/pressed-attach.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.9 KiB |
BIN
image/card/chosen.png
Normal file
BIN
image/card/chosen.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
|
@ -684,4 +684,52 @@ function SaveRecord()
|
||||||
c.client:saveRecord(json.encode(c.record), c.record[2])
|
c.client:saveRecord(json.encode(c.record), c.record[2])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function GetCardProhibitReason(cid, method, pattern)
|
||||||
|
local card = Fk:getCardById(cid)
|
||||||
|
if not card then return "" end
|
||||||
|
if method == "play" and not card.skill:canUse(Self, card) then return "" end
|
||||||
|
if method ~= "play" and not card:matchPattern(pattern) then return "" end
|
||||||
|
if method == "play" then method = "use" end
|
||||||
|
|
||||||
|
local status_skills = Fk:currentRoom().status_skills[ProhibitSkill] or Util.DummyTable
|
||||||
|
local s
|
||||||
|
for _, skill in ipairs(status_skills) do
|
||||||
|
local fn = method == "use" and skill.prohibitUse or skill.prohibitResponse
|
||||||
|
if fn(skill, Self, card) then
|
||||||
|
s = skill
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not s then return "" end
|
||||||
|
|
||||||
|
-- try to return a translated string
|
||||||
|
local skillName = s.name
|
||||||
|
local ret = Fk:translate(skillName)
|
||||||
|
if ret ~= skillName then
|
||||||
|
-- TODO: translate
|
||||||
|
return ret .. "禁" .. (method == "use" and "使用" or "打出")
|
||||||
|
elseif skillName:endsWith("_prohibit") and skillName:startsWith("#") then
|
||||||
|
return Fk:translate(skillName:sub(2, -10)) .. "禁" .. (method == "use" and "使用" or "打出")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function PoxiPrompt(poxi_type, data)
|
||||||
|
local poxi = Fk.poxi_methods[poxi_type]
|
||||||
|
if not poxi or not poxi.prompt then return "" end
|
||||||
|
if type(poxi.prompt) == "string" then return Fk:translate(poxi.prompt) end
|
||||||
|
return poxi.prompt(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
function PoxiFilter(poxi_type, to_select, selected, data)
|
||||||
|
local poxi = Fk.poxi_methods[poxi_type]
|
||||||
|
if not poxi then return "false" end
|
||||||
|
return json.encode(poxi.card_filter(to_select, selected, data))
|
||||||
|
end
|
||||||
|
|
||||||
|
function PoxiFeasible(poxi_type, selected, data)
|
||||||
|
local poxi = Fk.poxi_methods[poxi_type]
|
||||||
|
if not poxi then return "false" end
|
||||||
|
return json.encode(poxi.feasible(selected, data))
|
||||||
|
end
|
||||||
|
|
||||||
dofile "lua/client/i18n/init.lua"
|
dofile "lua/client/i18n/init.lua"
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Fk:loadTranslationTable({
|
Fk:loadTranslationTable({
|
||||||
-- Lobby
|
-- Lobby
|
||||||
-- ["Room List"] = "房间列表",
|
["Room List"] = "Room List (currently have %1 rooms)",
|
||||||
-- ["Enter"] = "进入",
|
-- ["Enter"] = "进入",
|
||||||
-- ["Observe"] = "旁观",
|
-- ["Observe"] = "旁观",
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Fk:loadTranslationTable{
|
Fk:loadTranslationTable{
|
||||||
-- Lobby
|
-- Lobby
|
||||||
["Room List"] = "房间列表",
|
["Room List"] = "房间列表 (共%1个房间)",
|
||||||
["Enter"] = "进入",
|
["Enter"] = "进入",
|
||||||
["Observe"] = "旁观",
|
["Observe"] = "旁观",
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
---@field public filtered_cards table<integer, Card> @ 被锁视技影响的卡牌
|
---@field public filtered_cards table<integer, Card> @ 被锁视技影响的卡牌
|
||||||
---@field public printed_cards table<integer, Card> @ 被某些房间现场打印的卡牌,id都是负数且从-2开始
|
---@field public printed_cards table<integer, Card> @ 被某些房间现场打印的卡牌,id都是负数且从-2开始
|
||||||
---@field private _custom_events any[] @ 自定义事件列表
|
---@field private _custom_events any[] @ 自定义事件列表
|
||||||
|
---@field public poxi_methods table<string, PoxiSpec> @ “魄袭”框操作方法表
|
||||||
local Engine = class("Engine")
|
local Engine = class("Engine")
|
||||||
|
|
||||||
--- Engine的构造函数。
|
--- Engine的构造函数。
|
||||||
|
@ -55,6 +56,7 @@ function Engine:initialize()
|
||||||
self.game_mode_disabled = {}
|
self.game_mode_disabled = {}
|
||||||
self.kingdoms = {}
|
self.kingdoms = {}
|
||||||
self._custom_events = {}
|
self._custom_events = {}
|
||||||
|
self.poxi_methods = {}
|
||||||
|
|
||||||
self:loadPackages()
|
self:loadPackages()
|
||||||
self:loadDisabled()
|
self:loadDisabled()
|
||||||
|
@ -334,6 +336,16 @@ function Engine:addGameEvent(name, pfunc, mfunc, cfunc, efunc)
|
||||||
table.insert(self._custom_events, { name = name, p = pfunc, m = mfunc, c = cfunc, e = efunc })
|
table.insert(self._custom_events, { name = name, p = pfunc, m = mfunc, c = cfunc, e = efunc })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param spec PoxiSpec
|
||||||
|
function Engine:addPoxiMethod(spec)
|
||||||
|
assert(type(spec.name) == "string")
|
||||||
|
assert(type(spec.card_filter) == "function")
|
||||||
|
assert(type(spec.feasible) == "function")
|
||||||
|
self.poxi_methods[spec.name] = spec
|
||||||
|
spec.default_choice = spec.default_choice or function() return {} end
|
||||||
|
spec.post_select = spec.post_select or function(s) return s end
|
||||||
|
end
|
||||||
|
|
||||||
--- 从已经开启的拓展包中,随机选出若干名武将。
|
--- 从已经开启的拓展包中,随机选出若干名武将。
|
||||||
---
|
---
|
||||||
--- 对于同名武将不会重复选取。
|
--- 对于同名武将不会重复选取。
|
||||||
|
|
|
@ -588,3 +588,13 @@ function fk.CreateGameMode(spec)
|
||||||
end
|
end
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- other
|
||||||
|
|
||||||
|
---@class PoxiSpec
|
||||||
|
---@field name string
|
||||||
|
---@field card_filter fun(to_select: int, selected: int[], data: any): bool
|
||||||
|
---@field feasible fun(selected: int[], data: any): bool
|
||||||
|
---@field post_select nil | fun(selected: int[], data: any): int[]
|
||||||
|
---@field default_choice nil | fun(data: any): int[]
|
||||||
|
---@field prompt nil | string | fun(data: any): string
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
---@alias null nil
|
---@alias null nil
|
||||||
---@alias bool boolean | nil
|
---@alias bool boolean | nil
|
||||||
|
---@alias int integer
|
||||||
|
|
||||||
---@class fk
|
---@class fk
|
||||||
---FreeKill's lua API
|
---FreeKill's lua API
|
||||||
|
|
|
@ -3,3 +3,25 @@
|
||||||
AI = require "server.ai.ai"
|
AI = require "server.ai.ai"
|
||||||
TrustAI = require "server.ai.trust_ai"
|
TrustAI = require "server.ai.trust_ai"
|
||||||
RandomAI = require "server.ai.random_ai"
|
RandomAI = require "server.ai.random_ai"
|
||||||
|
SmartAI = require "server.ai.smart_ai"
|
||||||
|
|
||||||
|
-- load ai module from packages
|
||||||
|
local directories = FileIO.ls("packages")
|
||||||
|
require "packages.standard.ai"
|
||||||
|
require "packages.standard_cards.ai"
|
||||||
|
require "packages.maneuvering.ai"
|
||||||
|
table.removeOne(directories, "standard")
|
||||||
|
table.removeOne(directories, "standard_cards")
|
||||||
|
table.removeOne(directories, "maneuvering")
|
||||||
|
|
||||||
|
local _disable_packs = json.decode(fk.GetDisabledPacks())
|
||||||
|
|
||||||
|
for _, dir in ipairs(directories) do
|
||||||
|
if (not string.find(dir, ".disabled")) and not table.contains(_disable_packs, dir)
|
||||||
|
and FileIO.isDir("packages/" .. dir)
|
||||||
|
and FileIO.exists("packages/" .. dir .. "/ai/init.lua") then
|
||||||
|
|
||||||
|
require(string.format("packages.%s.ai", dir))
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
10
lua/server/ai/smart_ai.lua
Normal file
10
lua/server/ai/smart_ai.lua
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
-- SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
---@class SmartAI: AI
|
||||||
|
local SmartAI = AI:subclass("RandomAI")
|
||||||
|
|
||||||
|
function SmartAI:initialize(player)
|
||||||
|
AI.initialize(self, player)
|
||||||
|
end
|
||||||
|
|
||||||
|
return SmartAI
|
|
@ -73,6 +73,13 @@ function GameEvent:addExitFunc(f)
|
||||||
table.insert(self.extra_exit_funcs, f)
|
table.insert(self.extra_exit_funcs, f)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function GameEvent:prependExitFunc(f)
|
||||||
|
if self.extra_exit_funcs == Util.DummyTable then
|
||||||
|
self.extra_exit_funcs = {}
|
||||||
|
end
|
||||||
|
table.insert(self.extra_exit_funcs, 1, f)
|
||||||
|
end
|
||||||
|
|
||||||
function GameEvent:findParent(eventType, includeSelf)
|
function GameEvent:findParent(eventType, includeSelf)
|
||||||
if includeSelf and self.event == eventType then return self end
|
if includeSelf and self.event == eventType then return self end
|
||||||
local e = self.parent
|
local e = self.parent
|
||||||
|
|
|
@ -376,14 +376,14 @@ function GameLogic:trigger(event, target, data, refresh_only)
|
||||||
end
|
end
|
||||||
|
|
||||||
repeat do
|
repeat do
|
||||||
local triggerables = table.filter(skills, function(skill)
|
local invoked_skills = {}
|
||||||
|
local filter_func = function(skill)
|
||||||
return skill.priority_table[event] == prio and
|
return skill.priority_table[event] == prio and
|
||||||
|
not table.contains(invoked_skills, skill) and
|
||||||
skill:triggerable(event, target, player, data)
|
skill:triggerable(event, target, player, data)
|
||||||
end)
|
end
|
||||||
|
|
||||||
local skill_names = table.map(triggerables, function(skill)
|
local skill_names = table.map(table.filter(skills, filter_func), Util.NameMapper)
|
||||||
return skill.name
|
|
||||||
end)
|
|
||||||
|
|
||||||
while #skill_names > 0 do
|
while #skill_names > 0 do
|
||||||
local skill_name = prio <= 0 and table.random(skill_names) or
|
local skill_name = prio <= 0 and table.random(skill_names) or
|
||||||
|
@ -392,23 +392,14 @@ function GameLogic:trigger(event, target, data, refresh_only)
|
||||||
local skill = skill_name == "game_rule" and GameRule
|
local skill = skill_name == "game_rule" and GameRule
|
||||||
or Fk.skills[skill_name]
|
or Fk.skills[skill_name]
|
||||||
|
|
||||||
local len = #skills
|
table.insert(invoked_skills, skill)
|
||||||
broken = skill:trigger(event, target, player, data)
|
broken = skill:trigger(event, target, player, data)
|
||||||
|
skill_names = table.map(table.filter(skills, filter_func), Util.NameMapper)
|
||||||
table.insertTable(
|
|
||||||
skill_names,
|
|
||||||
table.map(table.filter(table.slice(skills, len - #skills), function(s)
|
|
||||||
return
|
|
||||||
s.priority_table[event] == prio and
|
|
||||||
s:triggerable(event, target, player, data)
|
|
||||||
end), function(s) return s.name end)
|
|
||||||
)
|
|
||||||
|
|
||||||
broken = broken or (event == fk.AskForPeaches
|
broken = broken or (event == fk.AskForPeaches
|
||||||
and room:getPlayerById(data.who).hp > 0)
|
and room:getPlayerById(data.who).hp > 0)
|
||||||
|
|
||||||
if broken then break end
|
if broken then break end
|
||||||
table.removeOne(skill_names, skill_name)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if broken then break end
|
if broken then break end
|
||||||
|
@ -428,6 +419,18 @@ 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
|
||||||
|
|
||||||
|
--- 如果当前事件刚好是技能生效事件,就返回那个技能名,否则返回空串。
|
||||||
|
function GameLogic:getCurrentSkillName()
|
||||||
|
local skillEvent = self:getCurrentEvent()
|
||||||
|
local ret = ""
|
||||||
|
if skillEvent.event == GameEvent.SkillEffect then
|
||||||
|
local _, _, _skill = table.unpack(skillEvent.data)
|
||||||
|
local skill = _skill.main_skill and _skill.main_skill or _skill
|
||||||
|
ret = skill.name
|
||||||
|
end
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
|
||||||
-- 在指定历史范围中找至多n个符合条件的事件
|
-- 在指定历史范围中找至多n个符合条件的事件
|
||||||
---@param eventType integer @ 要查找的事件类型
|
---@param eventType integer @ 要查找的事件类型
|
||||||
---@param n integer @ 最多找多少个
|
---@param n integer @ 最多找多少个
|
||||||
|
|
|
@ -1457,6 +1457,33 @@ function Room:askForCardsChosen(chooser, target, min, max, flag, reason)
|
||||||
return new_ret
|
return new_ret
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- 谋askForCardsChosen,需使用Fk:addPoxiMethod定义好方法
|
||||||
|
---
|
||||||
|
--- 选卡规则和返回值啥的全部自己想办法解决,data填入所有卡的列表(类似ui.card_data)
|
||||||
|
---
|
||||||
|
--- 注意一定要返回一个表,毕竟本质上是选卡函数
|
||||||
|
---@param player ServerPlayer
|
||||||
|
---@param poxi_type string
|
||||||
|
---@param data any
|
||||||
|
---@return integer[]
|
||||||
|
function Room:askForPoxi(player, poxi_type, data)
|
||||||
|
local poxi = Fk.poxi_methods[poxi_type]
|
||||||
|
if not poxi then return {} end
|
||||||
|
|
||||||
|
local command = "AskForPoxi"
|
||||||
|
self:notifyMoveFocus(player, poxi_type)
|
||||||
|
local result = self:doRequest(player, command, json.encode {
|
||||||
|
type = poxi_type,
|
||||||
|
data = data,
|
||||||
|
})
|
||||||
|
|
||||||
|
if result == "" then
|
||||||
|
return poxi.default_choice(data)
|
||||||
|
else
|
||||||
|
return poxi.post_select(json.decode(result), data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
--- 询问一名玩家从众多选项中选择一个。
|
--- 询问一名玩家从众多选项中选择一个。
|
||||||
---@param player ServerPlayer @ 要询问的玩家
|
---@param player ServerPlayer @ 要询问的玩家
|
||||||
---@param choices string[] @ 可选选项列表
|
---@param choices string[] @ 可选选项列表
|
||||||
|
|
|
@ -469,7 +469,7 @@ function ServerPlayer:gainAnExtraPhase(phase, delay)
|
||||||
local logic = room.logic
|
local logic = room.logic
|
||||||
local turn = logic:getCurrentEvent():findParent(GameEvent.Phase, true)
|
local turn = logic:getCurrentEvent():findParent(GameEvent.Phase, true)
|
||||||
if turn then
|
if turn then
|
||||||
turn:addExitFunc(function() self:gainAnExtraPhase(phase, false) end)
|
turn:prependExitFunc(function() self:gainAnExtraPhase(phase, false) end)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -484,7 +484,6 @@ function ServerPlayer:gainAnExtraPhase(phase, delay)
|
||||||
arg = phase_name_table[phase],
|
arg = phase_name_table[phase],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
GameEvent(GameEvent.Phase, self, self.phase):exec()
|
GameEvent(GameEvent.Phase, self, self.phase):exec()
|
||||||
|
|
||||||
self.phase = current
|
self.phase = current
|
||||||
|
@ -580,6 +579,7 @@ function ServerPlayer:skip(phase)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- 当进行到出牌阶段空闲点时,结束出牌阶段。
|
||||||
function ServerPlayer:endPlayPhase()
|
function ServerPlayer:endPlayPhase()
|
||||||
self._play_phase_end = true
|
self._play_phase_end = true
|
||||||
-- TODO: send log
|
-- TODO: send log
|
||||||
|
@ -592,7 +592,7 @@ function ServerPlayer:gainAnExtraTurn(delay)
|
||||||
local logic = room.logic
|
local logic = room.logic
|
||||||
local turn = logic:getCurrentEvent():findParent(GameEvent.Turn, true)
|
local turn = logic:getCurrentEvent():findParent(GameEvent.Turn, true)
|
||||||
if turn then
|
if turn then
|
||||||
turn:addExitFunc(function() self:gainAnExtraTurn(false) end)
|
turn:prependExitFunc(function() self:gainAnExtraTurn(false) end)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -604,10 +604,32 @@ function ServerPlayer:gainAnExtraTurn(delay)
|
||||||
|
|
||||||
local current = room.current
|
local current = room.current
|
||||||
room.current = self
|
room.current = self
|
||||||
|
|
||||||
|
self.tag["_extra_turn_count"] = self.tag["_extra_turn_count"] or {}
|
||||||
|
local ex_tag = self.tag["_extra_turn_count"]
|
||||||
|
local skillName = room.logic:getCurrentSkillName()
|
||||||
|
table.insert(ex_tag, skillName)
|
||||||
|
|
||||||
GameEvent(GameEvent.Turn, self):exec()
|
GameEvent(GameEvent.Turn, self):exec()
|
||||||
|
|
||||||
|
table.remove(ex_tag)
|
||||||
|
|
||||||
room.current = current
|
room.current = current
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function ServerPlayer:insideExtraTurn()
|
||||||
|
return self.tag["_extra_turn_count"] and #self.tag["_extra_turn_count"] > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return string
|
||||||
|
function ServerPlayer:getCurrentExtraTurnReason()
|
||||||
|
local ex_tag = self.tag["_extra_turn_count"]
|
||||||
|
if (not ex_tag) or #ex_tag == 0 then
|
||||||
|
return "game_rule"
|
||||||
|
end
|
||||||
|
return ex_tag[#ex_tag]
|
||||||
|
end
|
||||||
|
|
||||||
function ServerPlayer:drawCards(num, skillName, fromPlace)
|
function ServerPlayer:drawCards(num, skillName, fromPlace)
|
||||||
return self.room:drawCards(self, num, skillName, fromPlace)
|
return self.room:drawCards(self, num, skillName, fromPlace)
|
||||||
end
|
end
|
||||||
|
|
0
packages/maneuvering/ai/init.lua
Normal file
0
packages/maneuvering/ai/init.lua
Normal file
0
packages/standard/ai/init.lua
Normal file
0
packages/standard/ai/init.lua
Normal file
0
packages/standard_cards/ai/init.lua
Normal file
0
packages/standard_cards/ai/init.lua
Normal file
|
@ -90,6 +90,12 @@ local control = fk.CreateActiveSkill{
|
||||||
-- room:swapSeat(from, to)
|
-- room:swapSeat(from, to)
|
||||||
for _, pid in ipairs(effect.tos) do
|
for _, pid in ipairs(effect.tos) do
|
||||||
local to = room:getPlayerById(pid)
|
local to = room:getPlayerById(pid)
|
||||||
|
-- p(room:askForPoxi(from, "test", {
|
||||||
|
-- { "你自己", from:getCardIds "h" },
|
||||||
|
-- { "对方", to:getCardIds "h" },
|
||||||
|
-- }))
|
||||||
|
-- room:setPlayerMark(from, "@$a", {1,2,3})
|
||||||
|
-- room:setPlayerMark(from, "@$b", {'slash','duel','axe'})
|
||||||
if to:getMark("mouxushengcontrolled") == 0 then
|
if to:getMark("mouxushengcontrolled") == 0 then
|
||||||
room:addPlayerMark(to, "mouxushengcontrolled")
|
room:addPlayerMark(to, "mouxushengcontrolled")
|
||||||
from:control(to)
|
from:control(to)
|
||||||
|
@ -124,6 +130,22 @@ local control = fk.CreateActiveSkill{
|
||||||
-- room:useVirtualCard("slash", nil, from, room:getOtherPlayers(from), self.name, true)
|
-- room:useVirtualCard("slash", nil, from, room:getOtherPlayers(from), self.name, true)
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
--[[
|
||||||
|
Fk:addPoxiMethod{
|
||||||
|
name = "test",
|
||||||
|
card_filter = function(to_select, selected, data)
|
||||||
|
local s = Fk:getCardById(to_select).suit
|
||||||
|
for _, id in ipairs(selected) do
|
||||||
|
if Fk:getCardById(id).suit == s then return false end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
feasible = function(selected, data)
|
||||||
|
return #selected == 0 or #selected == 4
|
||||||
|
end,
|
||||||
|
prompt = "魄袭:选你们俩手牌总共四个花色,或者不选直接按确定按钮"
|
||||||
|
}
|
||||||
|
--]]
|
||||||
local test_vs = fk.CreateViewAsSkill{
|
local test_vs = fk.CreateViewAsSkill{
|
||||||
name = "test_vs",
|
name = "test_vs",
|
||||||
pattern = "nullification",
|
pattern = "nullification",
|
||||||
|
|
Loading…
Reference in New Issue
Block a user