Enhancement2 (#275)

- TODO: 好友系统,目前在画饼状态
- 修老朱然bug,现在可以在cleaner环节胆守
- 修卢弈死了手谈不消失(UI)
- 修重连时丢失房主信息
This commit is contained in:
notify 2023-10-07 23:00:25 +08:00 committed by GitHub
parent d1619672a2
commit 183dae9ae1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 137 additions and 10 deletions

View File

@ -58,6 +58,9 @@ Item {
if (usedtimes >= 1) { if (usedtimes >= 1) {
x.visible = true; x.visible = true;
bg.source = SkinBank.LIMIT_SKILL_DIR + "limit-used"; bg.source = SkinBank.LIMIT_SKILL_DIR + "limit-used";
} else {
x.visible = false;
bg.source = SkinBank.LIMIT_SKILL_DIR + "limit";
} }
} else if (skilltype === 'switch') { } else if (skilltype === 'switch') {
visible = true; visible = true;

View File

@ -0,0 +1,45 @@
关于好友系统
=============
“好友系统”这几个字可谓是囊括的面有点多啊。总而言之:
- 添加好友、管理好友、删除好友
- 和好友进行私聊
没了,就只有以上两点而已。
好友信息肯定要放在各个服务器自己的数据库中。
登入时自动获取好友列表和消息列表。因此好友列表有上限最多50人不然负载太大。
列表至少要有好友的名字、头像然后最好还有在线状态。前二者查数据库第三者要根据id在Server范围查询玩家然后根据是否找的出、Room是大厅还是确切Room、Room是否已开始分为离线、空闲、等待中、游戏中、观战中四个状态。
好友列表查出之后就要暂时放在ServerPlayer类里面也就是放在RAM中。因为状态一变动就要广播所有好友自己的状态变动的情况有
- 登入/登出;
- 进入房间/进入大厅;
- 游戏开始时/结束时
每当状态变化了就通知好友?还是好友进大厅的时候就获取一次信息?答案是要一直通知。
但是现阶段可以先只针对大厅中的好友通知。毕竟房间里面没地方摆好友UI呢。
简而言之,只要进入大厅,就获取好友信息(和自动获取房间列表性质一致)。然后一直接收通知修改好友状态。
接下来就是处理如何加好友了,顺便处理私聊之事。
首先加好友只能在Room中加这就避免了搜索好友的问题。将加好友请求视为一种特殊的私信吧。
私信
-----
私信的问题在于已读和未读。未读信息要暂存在服务器;已读信息和聊天记录可以放在客户端本地的数据库中。
反正二者都要用到数据库啊。综上,涉及三张表:服务端需要好友关系表、未读信息表;客户端需要聊天记录表。
服务器端 - 好友关系表:
.. code:: sql
CREATE TABLE 好友 {
user1, user2, type
}
type是个数字可能是好友或者黑名单。

View File

@ -621,8 +621,9 @@ end
local function updateLimitSkill(pid, skill, times) local function updateLimitSkill(pid, skill, times)
if not skill.visible then return end if not skill.visible then return end
if skill:isSwitchSkill() then if skill:isSwitchSkill() then
times = ClientInstance:getPlayerById(pid):getSwitchSkillState(skill.switchSkillName) == fk.SwitchYang and 0 or 1 local _times = ClientInstance:getPlayerById(pid):getSwitchSkillState(skill.switchSkillName) == fk.SwitchYang and 0 or 1
ClientInstance:notifyUI("UpdateLimitSkill", json.encode{ pid, skill.switchSkillName, times }) if times == -1 then _times = -1 end
ClientInstance:notifyUI("UpdateLimitSkill", json.encode{ pid, skill.switchSkillName, _times })
elseif skill.frequency == Skill.Limited or skill.frequency == Skill.Wake or skill.frequency == Skill.Quest then elseif skill.frequency == Skill.Limited or skill.frequency == Skill.Wake or skill.frequency == Skill.Quest then
ClientInstance:notifyUI("UpdateLimitSkill", json.encode{ pid, skill.name, times }) ClientInstance:notifyUI("UpdateLimitSkill", json.encode{ pid, skill.name, times })
end end

View File

@ -41,6 +41,7 @@ dofile "lua/server/events/gameflow.lua"
GameEvent.Pindian = 19 GameEvent.Pindian = 19
dofile "lua/server/events/pindian.lua" dofile "lua/server/events/pindian.lua"
-- 20 = CardEffect
GameEvent.ChangeProperty = 21 GameEvent.ChangeProperty = 21
dofile "lua/server/events/misc.lua" dofile "lua/server/events/misc.lua"

View File

@ -13,7 +13,9 @@
---@field public extra_clear_funcs fun(self:GameEvent)[] @ 事件结束时执行的自定义函数列表 ---@field public extra_clear_funcs fun(self:GameEvent)[] @ 事件结束时执行的自定义函数列表
---@field public exit_func fun(self: GameEvent) @ 事件结束后执行的函数 ---@field public exit_func fun(self: GameEvent) @ 事件结束后执行的函数
---@field public extra_exit_funcs fun(self:GameEvent)[] @ 事件结束后执行的自定义函数 ---@field public extra_exit_funcs fun(self:GameEvent)[] @ 事件结束后执行的自定义函数
---@field public interrupted boolean @ 事件是否是因为被强行中断而结束的 ---@field public interrupted boolean @ 事件是否是因为被中断而结束的,可能是防止事件或者被杀
---@field public killed boolean @ 事件因为终止一切结算而被中断(所谓的“被杀”)
---@field public revived boolean @ 事件被killed但因为在cleaner中发生而被复活
local GameEvent = class("GameEvent") local GameEvent = class("GameEvent")
---@type (fun(self: GameEvent): bool)[] ---@type (fun(self: GameEvent): bool)[]
@ -169,6 +171,8 @@ function GameEvent:clear()
end end
end) end)
local zhuran_jmp, zhuran_msg -- SB老朱然
while true do while true do
local err, yield_result, extra_yield_result = coroutine.resume(clear_co) local err, yield_result, extra_yield_result = coroutine.resume(clear_co)
@ -185,12 +189,37 @@ function GameEvent:clear()
if yield_result == "__handleRequest" then if yield_result == "__handleRequest" then
-- yield to requestLoop -- yield to requestLoop
coroutine.yield(yield_result, extra_yield_result) coroutine.yield(yield_result, extra_yield_result)
elseif type(yield_result) == "table" and yield_result.class
and yield_result:isInstanceOf(GameEvent) and self ~= yield_result then
-- 不是谁TM还在cleaner里面玩老朱然啊
-- 总之cleaner不能断
-- 倒是没必要手动resume新一轮while true会自动resume只要把返回值
-- 传回去就行
-- 一般来说都是由cleaner中的trigger引起
-- 以胆守合击为例就是trigger -> SkillEffect事件 -> UseCard事件 -> 胆守
-- 此时胆守的话最后从SkillEffect事件的exec内部yield出来
-- 当前协程就应该正在执行room:useSkill函数resume会去只会让那个函数返回
if zhuran_jmp == nil or zhuran_jmp.id > yield_result.id then
zhuran_jmp = yield_result
zhuran_msg = extra_yield_result
end
-- 自己本来应该被杀的但是因为自己正在执行self:clear()而逃过一劫啊
-- 还是得标记一下被杀才行,顺便因为实际上没死所以标记被复活
self.killed = true
self.revived = true
-- 什么都不做等下轮while自己resume
else else
coroutine.close(clear_co) coroutine.close(clear_co)
break break
end end
end end
-- cleaner顺利执行完了出栈吧
local logic = RoomInstance.logic local logic = RoomInstance.logic
local end_id = logic.current_event_id + 1 local end_id = logic.current_event_id + 1
if self.id ~= end_id - 1 then if self.id ~= end_id - 1 then
@ -203,6 +232,21 @@ function GameEvent:clear()
logic.game_event_stack:pop() logic.game_event_stack:pop()
-- 好了确保cleaner走完了此时中断就会进入下层事件的正常中断处理
if zhuran_jmp then
coroutine.close(self._co)
coroutine.yield(zhuran_jmp, zhuran_msg)
-- 此时仍可能出现在插结在其他事件的clear函数中
-- 但就算被交付回去了也能安然返回而不是继续while
-- 但愿如此吧
end
-- 保险而已其实如果被杀的话应该已经在前面的yield终止了
-- 但担心cleaner嵌套三国杀是这样的还是补一刀
if self.killed then return end
-- 恭喜没被杀掉,我们来执行一些事件结束之后的结算吧
Pcall(self.exit_func, self) Pcall(self.exit_func, self)
for _, f in ipairs(self.extra_exit_funcs) do for _, f in ipairs(self.extra_exit_funcs) do
if type(f) == "function" then if type(f) == "function" then
@ -243,6 +287,7 @@ function GameEvent:exec()
table.insert(logic.event_recorder[self.event], self) table.insert(logic.event_recorder[self.event], self)
local co = coroutine.create(self.main_func) local co = coroutine.create(self.main_func)
self._co = co
while true do while true do
local err, yield_result, extra_yield_result = coroutine.resume(co) local err, yield_result, extra_yield_result = coroutine.resume(co)
@ -269,12 +314,17 @@ function GameEvent:exec()
if self ~= yield_result then if self ~= yield_result then
-- yield to corresponding GameEvent, first pop self from stack -- yield to corresponding GameEvent, first pop self from stack
self.interrupted = true self.interrupted = true
self.killed = true -- 老朱然!你不得好死
self:clear() self:clear()
-- logic.game_event_stack:pop(self) -- logic.game_event_stack:pop(self)
coroutine.close(co) coroutine.close(co)
-- then, call yield -- then, call yield
coroutine.yield(yield_result, extra_yield_result) coroutine.yield(yield_result, extra_yield_result)
-- 如果是在cleaner/exit里面发生此类中断的话是会被cleaner原地返回的
-- 此时正常执行程序流就变成继续while循环了这是不行的
break
elseif extra_yield_result == "__breakEvent" then elseif extra_yield_result == "__breakEvent" then
if breakEvent(self) then if breakEvent(self) then
coroutine.close(co) coroutine.close(co)

View File

@ -335,6 +335,10 @@ function GameLogic:trigger(event, target, data, refresh_only)
local skills_to_refresh = self.refresh_skill_table[event] or Util.DummyTable local skills_to_refresh = self.refresh_skill_table[event] or Util.DummyTable
local _target = room.current -- for iteration local _target = room.current -- for iteration
local player = _target local player = _target
local cur_event = self:getCurrentEvent() or {}
-- 如果当前事件被杀就强制只refresh
-- 因为被杀的事件再进行正常trigger只可能在cleaner和exit了
refresh_only = refresh_only or cur_event.killed
if #skills_to_refresh > 0 then repeat do if #skills_to_refresh > 0 then repeat do
-- refresh skills. This should not be broken -- refresh skills. This should not be broken
@ -380,7 +384,9 @@ function GameLogic:trigger(event, target, data, refresh_only)
skill_names = table.map(table.filter(skills, filter_func), Util.NameMapper) skill_names = table.map(table.filter(skills, filter_func), Util.NameMapper)
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) or cur_event.revived
-- ^^^^^^^^^^^^^^^^^
-- 如果事件复活了那么其实说明事件已经死过了赶紧break掉
if broken then break end if broken then break end
end end

View File

@ -339,6 +339,7 @@ function ServerPlayer:reconnect()
p._splayer:getAvatar(), p._splayer:getAvatar(),
}) })
end end
self:doNotify("RoomOwner", json.encode{ room.room:getOwner():getId() })
local player_circle = {} local player_circle = {}
for i = 1, #room.players do for i = 1, #room.players do

View File

@ -25,6 +25,12 @@ CREATE TABLE IF NOT EXISTS banuuid (
uuid VARCHAR(32) uuid VARCHAR(32)
); );
CREATE TABLE IF NOT EXISTS friendinfo (
id1 INTEGER,
id2 INTEGER,
reltype INTEGER -- 1=好友 2=黑名单
);
-- 胜率相关 -- 胜率相关
CREATE TABLE IF NOT EXISTS winRate ( CREATE TABLE IF NOT EXISTS winRate (

View File

@ -451,15 +451,14 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name,
client->disconnect(this); client->disconnect(this);
if (players.count() <= 10) { if (players.count() <= 10) {
broadcast("ServerMessage", tr("%1 backed").arg(player->getScreenName())); broadcast("ServerMessage", tr("%1 backed").arg(player->getScreenName()));
if (room->getOwner() == player) {
auto owner = room->getOwner();
auto jsonData = QJsonArray();
jsonData << owner->getId();
player->doNotify("RoomOwner", JsonArray2Bytes(jsonData));
}
} }
if (room && !room->isLobby()) { if (room && !room->isLobby()) {
player->doNotify("SetServerSettings", JsonArray2Bytes({
getConfig("motd"),
getConfig("hiddenPacks"),
getConfig("enableBots"),
}));
room->pushRequest(QString("%1,reconnect").arg(id)); room->pushRequest(QString("%1,reconnect").arg(id));
} else { } else {
// 懒得处理掉线玩家在大厅了!踢掉得了 // 懒得处理掉线玩家在大厅了!踢掉得了
@ -670,6 +669,16 @@ void Server::temporarilyBan(int playerId) {
emit player->kicked(); emit player->kicked();
} }
void Server::beginTransaction() {
transaction_mutex.lock();
ExecSQL(db, "BEGIN;");
}
void Server::endTransaction() {
ExecSQL(db, "COMMIT;");
transaction_mutex.unlock();
}
void Server::readPendingDatagrams() { void Server::readPendingDatagrams() {
while (udpSocket->hasPendingDatagrams()) { while (udpSocket->hasPendingDatagrams()) {
QNetworkDatagram datagram = udpSocket->receiveDatagram(); QNetworkDatagram datagram = udpSocket->receiveDatagram();

View File

@ -47,6 +47,9 @@ public:
bool checkBanWord(const QString &str); bool checkBanWord(const QString &str);
void temporarilyBan(int playerId); void temporarilyBan(int playerId);
void beginTransaction();
void endTransaction();
signals: signals:
void roomCreated(Room *room); void roomCreated(Room *room);
void playerAdded(ServerPlayer *player); void playerAdded(ServerPlayer *player);
@ -78,6 +81,7 @@ private:
RSA *rsa; RSA *rsa;
QString public_key; QString public_key;
sqlite3 *db; sqlite3 *db;
QMutex transaction_mutex;
QString md5; QString md5;
static RSA *initServerRSA(); static RSA *initServerRSA();

View File

@ -9,6 +9,7 @@ public:
int getId() const; int getId() const;
QList<ServerPlayer *> getPlayers() const; QList<ServerPlayer *> getPlayers() const;
ServerPlayer *getOwner() const;
QList<ServerPlayer *> getObservers() const; QList<ServerPlayer *> getObservers() const;
bool hasObserver(ServerPlayer *player) const; bool hasObserver(ServerPlayer *player) const;