diff --git a/lua/client/client.lua b/lua/client/client.lua index f19e7a47..17115412 100644 --- a/lua/client/client.lua +++ b/lua/client/client.lua @@ -1,4 +1,7 @@ -local Client = class('Client') +Client = class('Client') + +-- load client classes +ClientPlayer = require "client/clientplayer" freekill.client_callback = {} @@ -15,6 +18,8 @@ function Client:initialize() self:notifyUI(command, jsonData); end end + + self.players = {} -- ClientPlayer[] end freekill.client_callback["Setup"] = function(jsonData) @@ -32,7 +37,42 @@ freekill.client_callback["AddPlayer"] = function(jsonData) -- when other player enter the room, we create clientplayer(C and lua) for them local data = json.decode(jsonData) local id, name, avatar = data[1], data[2], data[3] - ClientInstance:notifyUI("AddPlayer", json.encode({ name, avatar })) + local player = freekill.ClientInstance:addPlayer(id, name, avatar) + table.insert(ClientInstance.players, ClientPlayer:new(player)) + ClientInstance:notifyUI("AddPlayer", jsonData) +end + +freekill.client_callback["RemovePlayer"] = function(jsonData) + -- jsonData: [ int id ] + local data = json.decode(jsonData) + local id = data[1] + freekill.ClientInstance:removePlayer(id) + for _, p in ipairs(ClientInstance.players) do + if p.player:getId() == id then + table.removeOne(ClientInstance.players, p) + break + end + end + ClientInstance:notifyUI("RemovePlayer", jsonData) +end + +freekill.client_callback["ArrangeSeats"] = function(jsonData) + local data = json.decode(jsonData) + local n = #ClientInstance.players + local players = {} + local function findPlayer(id) + for _, p in ipairs(ClientInstance.players) do + if p.player:getId() == id then return p end + end + return nil + end + + for i = 1, n do + table.insert(players, findPlayer(data[i])) + end + ClientInstance.players = players + + ClientInstance:notifyUI("ArrangeSeats", jsonData) end -- Create ClientInstance (used by Lua) diff --git a/lua/client/clientplayer.lua b/lua/client/clientplayer.lua index 8b137891..6d451770 100644 --- a/lua/client/clientplayer.lua +++ b/lua/client/clientplayer.lua @@ -1 +1,7 @@ +local ClientPlayer = Player:subclass("ClientPlayer") +function ClientPlayer:initialize(cp) + self.player = cp +end + +return ClientPlayer diff --git a/lua/core/engine.lua b/lua/core/engine.lua index 86ecb09a..761410c3 100644 --- a/lua/core/engine.lua +++ b/lua/core/engine.lua @@ -1,24 +1,103 @@ -local Sanguosha = class("Engine") +local Engine = class("Engine") -function Sanguosha:initialize() - self.skills = {} - self.generals = {} - self.cards = {} +function Engine:initialize() + -- Engine should be singleton + if Fk ~= nil then + error("Engine has been initialized") + return + end + + Fk = self + + self.packages = {} -- name --> Package + self.skills = {} -- name --> Skill + self.related_skills = {} -- skillName --> relatedName + self.generals = {} -- name --> General + self.lords = {} -- lordName[] + self.cards = {} -- Card[] + self.translations = {} -- srcText --> translated + + self:loadPackages() end -function Sanguosha:addSkill(skill) - table.insert(self.skills, skill) +-- Package pack +function Engine:loadPackage(pack) + assert(pack:isInstanceOf(Package)) + if self.packages[pack.name] ~= nil then + error(string.format("Duplicate package %s detected", pack.name)) + end + self.packages[pack.name] = pack + + -- add cards, generals and skills to Engine + if pack.type == Package.CardPack then + self:addCards(pack.cards) + elseif pack.type == Package.GeneralPack then + self:addGenerals(pack.generals) + end + self:addSkills(pack:getSkills()) end -function Sanguosha:addGeneral(general) - table.insert(self.generals, general) +function Engine:loadPackages() + assert(FileIO.isDir("packages")) + FileIO.cd("packages") + for _, dir in ipairs(FileIO.ls()) do + if FileIO.isDir(dir) then + self:loadPackage(require(dir)) + end + end + FileIO.cd("..") end -function Sanguosha:addCard(card) - table.insert(self.cards, cards) +function Engine:loadTranslationTable(t) + assert(type(t) == "table") + for k, v in pairs(t) do + self.translations[k] = v + end end -function Sanguosha:getGeneralsRandomly(num, generalPool, except, filter) +function Engine:addSkill(skill) + assert(skill.class:isSubclassOf(Skill)) + if self.skills[skill.name] ~= nil then + error(string.format("Duplicate skill %s detected", skill.name)) + end + self.skills[skill.name] = skill +end + +function Engine:addSkills(skills) + assert(type(skills) == "table") + for _, skill in ipairs(skills) do + self:addSkill(skill) + end +end + +function Engine:addGeneral(general) + assert(general:isInstanceOf(General)) + if self.generals[general.name] ~= nil then + error(string.format("Duplicate general %s detected", general.name)) + end + self.generals[general.name] = general +end + +function Engine:addGenerals(generals) + assert(type(generals) == "table") + for _, general in ipairs(generals) do + self:addGeneral(general) + end +end + +function Engine:addCard(card) + assert(card.class:isSubclassOf(Card)) + table.insert(self.cards, card) +end + +function Engine:addCards(cards) + assert(type(cards) == "table") + for _, card in ipairs(cards) do + self:addCard(card) + end +end + +function Engine:getGeneralsRandomly(num, generalPool, except, filter) if filter then assert(type(filter) == "function") end @@ -51,7 +130,7 @@ function Sanguosha:getGeneralsRandomly(num, generalPool, except, filter) return result end -function Sanguosha:getAllGenerals(except) +function Engine:getAllGenerals(except) local result = {} for _, general in ipairs(self.generals) do if not (except and table.contains(except, general)) then @@ -62,4 +141,4 @@ function Sanguosha:getAllGenerals(except) return result end -return Sanguosha +return Engine diff --git a/lua/core/general.lua b/lua/core/general.lua index 070bece8..496de06f 100644 --- a/lua/core/general.lua +++ b/lua/core/general.lua @@ -1,19 +1,29 @@ General = class("General") +-- enum Gender +General.Male = 0 +General.Female = 1 + function General:initialize(package, name, kingdom, hp, maxHp, gender, initialHp) self.package = package self.name = name self.kingdom = kingdom self.hp = hp - self.maxHp = maxHp - self.gender = gender + self.maxHp = maxHp or hp + self.gender = gender or General.Male self.initialHp = initialHp or maxHp - self.skills = {} + self.skills = {} -- Skill[] + -- skill belongs other general, e.g. "mashu" of pangde + self.other_skills = {} -- string[] end function General:addSkill(skill) - table.insert(self.skills, skill) + if (type(skill) == "string") then + table.insert(self.other_skills, skill) + elseif (skill.class and skill.class:isSubclassOf(Skill)) then + table.insert(self.skills, skill) + end end return General diff --git a/lua/core/package.lua b/lua/core/package.lua new file mode 100644 index 00000000..0d713634 --- /dev/null +++ b/lua/core/package.lua @@ -0,0 +1,40 @@ +local Package = class("Package") + +-- enum Type +Package.GeneralPack = 0 +Package.CardPack = 1 +Package.SpecialPack = 2 + +-- string name, Type type +function Package:initialize(name, _type) + assert(type(name) == "string") + assert(type(_type) == "nil" or type(_type) == "number") + self.name = name + self.type = _type or Package.GeneralPack + + self.generals = {} + -- skill not belongs to any generals, like "jixi" + self.extra_skills = {} + -- table: string --> string + self.related_skills = {} + self.cards = {} --> Card[] +end + +function Package:getSkills() + local ret = {table.unpack(self.related_skills)} + if self.type == Package.GeneralPack then + for _, g in ipairs(self.generals) do + for _, s in ipairs(g.skills) do + table.insert(ret, s) + end + end + end + return ret +end + +function Package:addGeneral(general) + assert(general.class and general:isInstanceOf(General)) + table.insert(self.generals, general) +end + +return Package diff --git a/lua/core/player.lua b/lua/core/player.lua index 5ae61626..65fc13b3 100644 --- a/lua/core/player.lua +++ b/lua/core/player.lua @@ -1,11 +1,19 @@ local Player = class("Player") function Player:initialize() - self.hp = nil - self.maxHp = nil - self.general = nil + self.hp = 0 + self.maxHp = 0 + self.kingdom = "qun" + self.role = "" + self.general = "" + self.handcard_num = 0 + self.seat = 0 + self.phase = Player.PhaseNone + self.faceup = true + self.chained = false self.dying = false self.dead = false + self.playerSkills = {} end diff --git a/lua/core/util.lua b/lua/core/util.lua index be9ee423..e9a76565 100644 --- a/lua/core/util.lua +++ b/lua/core/util.lua @@ -16,6 +16,13 @@ function table:contains(element) end end +function table:shuffle() + for i = #self, 2, -1 do + local j = math.random(i) + self[i], self[j] = self[j], self[i] + end +end + function table:insertTable(list) for _, e in ipairs(list) do table.insert(self, e) @@ -37,6 +44,41 @@ Sql = { end, } +FileIO = { + pwd = freekill.QmlBackend_pwd, + ls = function(filename) + if filename == nil then + return freekill.QmlBackend_ls(".") + else + return freekill.QmlBackend_ls(filename) + end + end, + cd = freekill.QmlBackend_cd, + exists = freekill.QmlBackend_exists, + isDir = freekill.QmlBackend_isDir +} + +Stack = class("Stack") +function Stack:initialize() + self.t = {} + self.p = 0 +end + +function Stack:push(e) + self.p = self.p + 1 + self.t[self.p] = e +end + +function Stack:isEmpty() + return self.p == 0 +end + +function Stack:pop() + if self.p == 0 then return nil end + self.p = self.p - 1 + return self.t[self.p + 1] +end + function table:removeOne(element) if #self == 0 or type(self[1]) ~= type(element) then return false end diff --git a/lua/freekill.lua b/lua/freekill.lua index f02a215d..ff88cf32 100644 --- a/lua/freekill.lua +++ b/lua/freekill.lua @@ -2,13 +2,14 @@ -- Load mods, init the engine, etc. package.path = package.path .. ";./lua/lib/?.lua" - .. ";./lua/core/?.lua" + .. ";./lua/?.lua" -- load libraries class = require "middleclass" json = require "json" require "sha256" -Util = require "util" +Util = require "core/util" +math.randomseed(os.time()) DebugMode = true @@ -19,10 +20,12 @@ function pt(t) end -- load core classes -Sanguosha = require "engine" -General = require "general" -Card = require "card" -Skill = require "skill" -Player = require "player" +Engine = require "core/engine" +Package = require "core/package" +General = require "core/general" +Card = require "core/card" +Skill = require "core/skill" +Player = require "core/player" -- load packages +Fk = Engine:new() diff --git a/lua/server/gamelogic.lua b/lua/server/gamelogic.lua index 9b4345c9..7d42098a 100644 --- a/lua/server/gamelogic.lua +++ b/lua/server/gamelogic.lua @@ -1,23 +1,55 @@ -function logic() - chooseGeneral() - initSkillList() - actionNormal() +local GameLogic = class("GameLogic") + +function GameLogic:initialize(room) + self.room = room + self.skill_table = {} -- TriggerEvent --> Skill[] + self.skills = {} -- skillName[] + self.event_stack = Stack:new() + + self.role_table = { + { "lord" }, + { "lord", "rebel" }, + { "lord", "rebel", "renegade" }, + { "lord", "loyalist", "rebel", "renegade" }, + { "lord", "loyalist", "rebel", "rebel", "renegade" }, + { "lord", "loyalist", "rebel", "rebel", "rebel", "renegade" }, + { "lord", "loyalist", "loyalist", "rebel", "rebel", "rebel", "renegade" }, + { "lord", "loyalist", "loyalist", "rebel", "rebel", "rebel", "rebel", "renegade" }, + } end -function chooseGeneral() - for _, p in ipairs(room:getPlayers()) do - local g = p:askForGeneral() - room:changeHero(p, g) +function GameLogic:run() + -- default logic + table.shuffle(self.room.players) + self:assignRoles() + self.room:adjustSeats() + + self:chooseGenerals() + self:startGame() +end + +function GameLogic:assignRoles() + local n = #self.room.players + local roles = self.role_table[n] + table.shuffle(roles) + + for i = 1, n do + local p = self.room.players[i] + p.role = roles[i] + if p.role == "lord" then + self.room:broadcastProperty(p, "role") + else + self.room:notifyProperty(p, p, "role") + end end end -function actionNormal() - local p = room:getLord() - while true do - room:setCurrent(p) - act(room:getCurrent) - p = p:getNextAlive() - end +function GameLogic:chooseGenerals() + end -function trigger() end +function GameLogic:startGame() + +end + +return GameLogic diff --git a/lua/server/room.lua b/lua/server/room.lua index 9790f082..643f6827 100644 --- a/lua/server/room.lua +++ b/lua/server/room.lua @@ -2,29 +2,68 @@ local Room = class("Room") function Room:initialize(_room) self.room = _room - self.players = {} + self.players = {} -- ServerPlayer[] self.gameFinished = false end -- When this function returns, the Room(C++) thread stopped. function Room:run() - print 'Room is running!' - -- First, create players(Lua) from ServerPlayer(C++) for _, p in freekill.qlist(self.room:getPlayers()) do local player = ServerPlayer:new(p) - print(player:getId()) - table.insert(self.players, p) + table.insert(self.players, player) + self.server.players[player:getId()] = player end - -- Second, assign role and adjust seats - -- Then let's choose general and start the game! + + self.logic = GameLogic:new(self) + self.logic:run() end -function Room:startGame() - while true do - if self.gameFinished then break end +function Room:broadcastProperty(player, property) + for _, p in ipairs(self.players) do + self:notifyProperty(p, player, property) end end +function Room:notifyProperty(p, player, property) + p:doNotify("PropertyUpdate", json.encode{ + player:getId(), + property, + player[property], + }) +end + +function Room:doBroadcastNotify(command, jsonData) + self.room:doBroadcastNotify(self.room:getPlayers(), command, jsonData) +end + +function Room:adjustSeats() + local players = {} + local p = 0 + + for i = 1, #self.players do + if self.players[i].role == "lord" then + p = i + break + end + end + for j = p, #self.players do + table.insert(players, self.players[j]) + end + for j = 1, p - 1 do + table.insert(players, self.players[j]) + end + + self.players = players + + local player_circle = {} + for i = 1, #self.players do + self.players[i].seat = i + table.insert(player_circle, self.players[i]:getId()) + end + + self:doBroadcastNotify("ArrangeSeats", json.encode(player_circle)) +end + function Room:gameOver() self.gameFinished = true -- dosomething diff --git a/lua/server/server.lua b/lua/server/server.lua index 7fe643ab..180a6660 100644 --- a/lua/server/server.lua +++ b/lua/server/server.lua @@ -1,7 +1,9 @@ Server = class('Server') -package.path = package.path .. ';./lua/server/?.lua' -Room = require "room" -ServerPlayer = require "serverplayer" + +-- load server classes +Room = require "server/room" +GameLogic = require "server/gamelogic" +ServerPlayer = require "server/serverplayer" freekill.server_callback = {} @@ -30,8 +32,8 @@ function Server:initialize() table.removeOne(self.rooms, room) end - self.rooms = {} - self.players = {} + self.rooms = {} -- id --> Room(Started) + self.players = {} -- id --> ServerPlayer end freekill.server_callback["UpdateAvatar"] = function(jsonData) @@ -41,7 +43,8 @@ freekill.server_callback["UpdateAvatar"] = function(jsonData) local sql = "UPDATE userinfo SET avatar='%s' WHERE id=%d;" Sql.exec(ServerInstance.db, string.format(sql, avatar, id)) local player = freekill.ServerInstance:findPlayer(id) - player:doNotify("UpdateAvatar", "[]") + player:setAvatar(avatar) + player:doNotify("UpdateAvatar", avatar) end freekill.server_callback["UpdatePassword"] = function(jsonData) diff --git a/lua/server/serverplayer.lua b/lua/server/serverplayer.lua index 125bd32c..16c6a198 100644 --- a/lua/server/serverplayer.lua +++ b/lua/server/serverplayer.lua @@ -9,4 +9,12 @@ function ServerPlayer:getId() return self.serverplayer:getId() end +function ServerPlayer:doNotify(command, jsonData) + self.serverplayer:doNotify(command, jsonData) +end + +function ServerPlayer:doRequest(command, jsonData, timeout) + self.serverplayer:doRequest(command, jsonData, timeout) +end + return ServerPlayer diff --git a/packages/standard/init.lua b/packages/standard/init.lua index 8d1c8b69..4624fe24 100644 --- a/packages/standard/init.lua +++ b/packages/standard/init.lua @@ -1 +1,161 @@ - +local extension = Package:new("standard") +extension.metadata = require "standard/metadata" + +Fk:loadTranslationTable{ + ["wei"] = "魏", + ["shu"] = "蜀", + ["wu"] = "吴", + ["qun"] = "群", +} + +local caocao = General:new(extension, "caocao", "wei", 4) +extension:addGeneral(caocao) +Fk:loadTranslationTable{ + ["caocao"] = "曹操", +} + +local simayi = General:new(extension, "simayi", "wei", 3) +extension:addGeneral(simayi) +Fk:loadTranslationTable{ + ["simayi"] = "司马懿", +} + +local xiahoudun = General:new(extension, "xiahoudun", "wei", 4) +extension:addGeneral(xiahoudun) +Fk:loadTranslationTable{ + ["xiahoudun"] = "夏侯惇", +} + +local zhangliao = General:new(extension, "zhangliao", "wei", 4) +extension:addGeneral(zhangliao) +Fk:loadTranslationTable{ + ["zhangliao"] = "张辽", +} + +local xuchu = General:new(extension, "xuchu", "wei", 4) +extension:addGeneral(xuchu) +Fk:loadTranslationTable{ + ["xuchu"] = "许褚", +} + +local guojia = General:new(extension, "guojia", "wei", 4) +extension:addGeneral(guojia) +Fk:loadTranslationTable{ + ["guojia"] = "郭嘉", +} + +local zhenji = General:new(extension, "zhenji", "wei", 3) +extension:addGeneral(zhenji) +Fk:loadTranslationTable{ + ["zhenji"] = "甄姬", +} + +local liubei = General:new(extension, "liubei", "shu", 4) +extension:addGeneral(liubei) +Fk:loadTranslationTable{ + ["liubei"] = "刘备", +} + +local guanyu = General:new(extension, "guanyu", "shu", 4) +extension:addGeneral(guanyu) +Fk:loadTranslationTable{ + ["guanyu"] = "关羽", +} + +local zhangfei = General:new(extension, "zhangfei", "shu", 4) +extension:addGeneral(zhangfei) +Fk:loadTranslationTable{ + ["zhangfei"] = "张飞", +} + +local zhugeliang = General:new(extension, "zhugeliang", "shu", 3) +extension:addGeneral(zhugeliang) +Fk:loadTranslationTable{ + ["zhugeliang"] = "诸葛亮", +} + +local zhaoyun = General:new(extension, "zhaoyun", "shu", 4) +extension:addGeneral(zhaoyun) +Fk:loadTranslationTable{ + ["zhaoyun"] = "赵云", +} + +local machao = General:new(extension, "machao", "shu", 4) +extension:addGeneral(machao) +Fk:loadTranslationTable{ + ["machao"] = "马超", +} + +local huangyueying = General:new(extension, "huangyueying", "shu", 3) +extension:addGeneral(huangyueying) +Fk:loadTranslationTable{ + ["huangyueying"] = "黄月英", +} + +local sunquan = General:new(extension, "sunquan", "wu", 4) +extension:addGeneral(sunquan) +Fk:loadTranslationTable{ + ["sunquan"] = "孙权", +} + +local ganning = General:new(extension, "ganning", "wu", 4) +extension:addGeneral(ganning) +Fk:loadTranslationTable{ + ["ganning"] = "甘宁", +} + +local lvmeng = General:new(extension, "lvmeng", "wu", 4) +extension:addGeneral(lvmeng) +Fk:loadTranslationTable{ + ["lvmeng"] = "吕蒙", +} + +local huanggai = General:new(extension, "huanggai", "wu", 4) +extension:addGeneral(huanggai) +Fk:loadTranslationTable{ + ["huanggai"] = "黄盖", +} + +local zhouyu = General:new(extension, "zhouyu", "wu", 3) +extension:addGeneral(zhouyu) +Fk:loadTranslationTable{ + ["zhouyu"] = "周瑜", +} + +local daqiao = General:new(extension, "daqiao", "wu", 3) +extension:addGeneral(daqiao) +Fk:loadTranslationTable{ + ["daqiao"] = "大乔", +} + +local luxun = General:new(extension, "luxun", "wu", 3) +extension:addGeneral(luxun) +Fk:loadTranslationTable{ + ["luxun"] = "陆逊", +} + +local sunshangxiang = General:new(extension, "sunshangxiang", "wu", 3) +extension:addGeneral(sunshangxiang) +Fk:loadTranslationTable{ + ["sunshangxiang"] = "孙尚香", +} + +local huatuo = General:new(extension, "huatuo", "qun", 3) +extension:addGeneral(huatuo) +Fk:loadTranslationTable{ + ["huatuo"] = "华佗", +} + +local lvbu = General:new(extension, "lvbu", "qun", 4) +extension:addGeneral(lvbu) +Fk:loadTranslationTable{ + ["lvbu"] = "吕布", +} + +local diaochan = General:new(extension, "diaochan", "qun", 3) +extension:addGeneral(diaochan) +Fk:loadTranslationTable{ + ["diaochan"] = "貂蝉", +} + +return extension diff --git a/packages/standard/metadata.json b/packages/standard/metadata.json deleted file mode 100644 index 8d1c8b69..00000000 --- a/packages/standard/metadata.json +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/standard/metadata.lua b/packages/standard/metadata.lua new file mode 100644 index 00000000..51cf16a8 --- /dev/null +++ b/packages/standard/metadata.lua @@ -0,0 +1,14 @@ +return { + name = "standard", + author = "official", + description = "", + collaborators = { + program = {}, + designer = {}, + cv = {}, + illustrator = {}, + }, + + dependencies = {}, + extra_files = {}, +} diff --git a/qml/Pages/Logic.js b/qml/Pages/Logic.js index 91f97342..0c0831c7 100644 --- a/qml/Pages/Logic.js +++ b/qml/Pages/Logic.js @@ -1,6 +1,6 @@ callbacks["UpdateAvatar"] = function(jsonData) { mainWindow.busy = false; - self.avatar = avatarName.text; + Self.avatar = jsonData; toast.show("Update avatar done."); } diff --git a/qml/Pages/Room.qml b/qml/Pages/Room.qml index 530aef9a..1e7156e6 100644 --- a/qml/Pages/Room.qml +++ b/qml/Pages/Room.qml @@ -7,7 +7,6 @@ import "RoomLogic.js" as Logic Item { id: roomScene - property var photoModel: [] property int playerNum: 0 property var dashboardModel @@ -23,6 +22,7 @@ Item { text: "quit" anchors.bottom: parent.bottom onClicked: { + ClientInstance.clearPlayers(); ClientInstance.notifyServer("QuitRoom", "[]"); } } @@ -60,6 +60,10 @@ Item { * +---------------------+ */ + ListModel { + id: photoModel + } + Item { id: roomArea width: roomScene.width @@ -69,20 +73,20 @@ Item { id: photos model: photoModel Photo { - general: modelData.general - screenName: modelData.screenName - role: modelData.role - kingdom: modelData.kingdom - netstate: modelData.netstate - maxHp: modelData.maxHp - hp: modelData.hp - seatNumber: modelData.seatNumber - isDead: modelData.isDead - dying: modelData.dying - faceturned: modelData.faceturned - chained: modelData.chained - drank: modelData.drank - isOwner: modelData.isOwner + general: _general + screenName: _screenName + role: _role + kingdom: _kingdom + netstate: _netstate + maxHp: _maxHp + hp: _hp + seatNumber: _seatNumber + isDead: _isDead + dying: _dying + faceturned: _faceturned + chained: _chained + drank: _drank + isOwner: _isOwner } } @@ -129,6 +133,7 @@ Item { toast.show("Sucesessfully entered room."); dashboardModel = { + id: Self.id, general: Self.avatar, screenName: Self.screenName, role: "unknown", @@ -149,24 +154,25 @@ Item { let i; for (i = 1; i < playerNum; i++) { - photoModel.push({ - general: "", - screenName: "", - role: "unknown", - kingdom: "qun", - netstate: "online", - maxHp: 0, - hp: 0, - seatNumber: i + 1, - isDead: false, - dying: false, - faceturned: false, - chained: false, - drank: false, - isOwner: false + photoModel.append({ + id: -1, + index: i - 1, // For animating seat swap + _general: "", + _screenName: "", + _role: "unknown", + _kingdom: "qun", + _netstate: "online", + _maxHp: 0, + _hp: 0, + _seatNumber: i + 1, + _isDead: false, + _dying: false, + _faceturned: false, + _chained: false, + _drank: false, + _isOwner: false }); } - photoModel = photoModel; // Force the Repeater reload Logic.arrangePhotos(); } diff --git a/qml/Pages/RoomElement/Photo.qml b/qml/Pages/RoomElement/Photo.qml index 171b72d4..58aa8700 100644 --- a/qml/Pages/RoomElement/Photo.qml +++ b/qml/Pages/RoomElement/Photo.qml @@ -24,6 +24,14 @@ Item { property bool drank: false property bool isOwner: false + Behavior on x { + NumberAnimation { duration: 600; easing.type: Easing.InOutQuad } + } + + Behavior on y { + NumberAnimation { duration: 600; easing.type: Easing.InOutQuad } + } + Image { id: back source: SkinBank.PHOTO_BACK_DIR + root.kingdom @@ -145,11 +153,42 @@ Item { anchors.rightMargin: -4 } - Text { + GlowText { id: seatNum - visible: false // TODO - property var seatChr: ["一", "二", "三", "四", "五", "六", "七"] + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: -32 + property var seatChr: ["一", "二", "三", "四", "五", "六", "七", "八"] font.family: "FZLiShu II-S06S" - text: seatChr[root.seatNumber - 1] + font.pixelSize: 32 + text: seatChr[seatNumber - 1] + + glow.color: "brown" + glow.spread: 0.2 + glow.radius: 8 + glow.samples: 12 + } + + SequentialAnimation { + id: trembleAnimation + running: false + PropertyAnimation { + target: root + property: "x" + to: root.x - 20 + easing.type: Easing.InQuad + duration: 100 + } + PropertyAnimation { + target: root + property: "x" + to: root.x + easing.type: Easing.OutQuad + duration: 100 + } + } + + function tremble() { + trembleAnimation.start() } } diff --git a/qml/Pages/RoomLogic.js b/qml/Pages/RoomLogic.js index 3309a9ad..8c41ba6d 100644 --- a/qml/Pages/RoomLogic.js +++ b/qml/Pages/RoomLogic.js @@ -42,65 +42,39 @@ function arrangePhotos() { if (!item) continue; - region = regions[seatIndex[i] - 1]; + region = regions[seatIndex[photoModel.get(i).index] - 1]; item.x = region.x; item.y = region.y; } } callbacks["AddPlayer"] = function(jsonData) { - // jsonData: string screenName, string avatar - for (let i = 0; i < photoModel.length; i++) { - if (photoModel[i].screenName === "") { + // jsonData: int id, string screenName, string avatar + for (let i = 0; i < photoModel.count; i++) { + let item = photoModel.get(i); + if (item.id === -1) { let data = JSON.parse(jsonData); - let name = data[0]; - let avatar = data[1]; - photoModel[i] = { - general: avatar, - screenName: name, - role: "unknown", - kingdom: "qun", - netstate: "online", - maxHp: 0, - hp: 0, - seatNumber: i + 1, - isDead: false, - dying: false, - faceturned: false, - chained: false, - drank: false, - isOwner: false - }; - photoModel = photoModel; - arrangePhotos(); + let uid = data[0]; + let name = data[1]; + let avatar = data[2]; + item.id = uid; + item._screenName = name; + item._general = avatar; + photos.itemAt(i).tremble(); return; } } } callbacks["RemovePlayer"] = function(jsonData) { - // jsonData: string screenName - let name = JSON.parse(jsonData)[0]; - for (let i = 0; i < photoModel.length; i++) { - if (photoModel[i].screenName === name) { - photoModel[i] = { - general: "", - screenName: "", - role: "unknown", - kingdom: "qun", - netstate: "online", - maxHp: 0, - hp: 0, - seatNumber: i + 1, - isDead: false, - dying: false, - faceturned: false, - chained: false, - drank: false, - isOwner: false - }; - photoModel = photoModel; - arrangePhotos(); + // jsonData: int uid + let uid = JSON.parse(jsonData)[0]; + for (let i = 0; i < photoModel.count; i++) { + let item = photoModel.get(i); + if (item.id === uid) { + item.id = -1; + item._screenName = ""; + item._general = ""; return; } } @@ -112,3 +86,51 @@ callbacks["RoomOwner"] = function(jsonData) { toast.show(J) } */ + +callbacks["PropertyUpdate"] = function(jsonData) { + // jsonData: int id, string property_name, value + let data = JSON.parse(jsonData); + let uid = data[0]; + let property_name = data[1]; + let value = data[2]; + + if (Self.id === uid) { + dashboardModel[property_name] = value; + roomScene.dashboardModelChanged(); + return; + } + + for (let i = 0; i < photoModel.count; i++) { + let item = photoModel.get(i); + if (item.id === uid) { + item["_" + property_name] = value; + return; + } + } +} + +callbacks["ArrangeSeats"] = function(jsonData) { + // jsonData: seat order + let order = JSON.parse(jsonData); + + for (let i = 0; i < photoModel.count; i++) { + let item = photoModel.get(i); + item._seatNumber = order.indexOf(item.id) + 1; + } + + dashboardModel.seatNumber = order.indexOf(Self.id) + 1; + roomScene.dashboardModelChanged(); + + // make Self to the first of list, then reorder photomodel + let selfIndex = order.indexOf(Self.id); + let after = order.splice(selfIndex); + after.push(...order); + let photoOrder = after.slice(1); + + for (let i = 0; i < photoModel.count; i++) { + let item = photoModel.get(i); + item.index = photoOrder.indexOf(item.id); + } + + arrangePhotos(); +} diff --git a/src/client/client.cpp b/src/client/client.cpp index d71152a9..881cb81b 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -49,3 +49,22 @@ void Client::notifyServer(const QString& command, const QString& jsonData) int type = Router::TYPE_NOTIFICATION | Router::SRC_CLIENT | Router::DEST_SERVER; router->notify(type, command, jsonData); } + +ClientPlayer *Client::addPlayer(int id, const QString &name, const QString &avatar) { + ClientPlayer *player = new ClientPlayer(id); + player->setScreenName(name); + player->setAvatar(avatar); + + players[id] = player; + return player; +} + +void Client::removePlayer(int id) { + ClientPlayer *p = players[id]; + p->deleteLater(); + players[id] = nullptr; +} + +void Client::clearPlayers() { + players.clear(); +} diff --git a/src/client/client.h b/src/client/client.h index 1161c07f..08fd6463 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -18,6 +18,10 @@ public: Q_INVOKABLE void callLua(const QString &command, const QString &jsonData); LuaFunction callback; + ClientPlayer *addPlayer(int id, const QString &name, const QString &avatar); + void removePlayer(int id); + Q_INVOKABLE void clearPlayers(); + signals: void error_message(const QString &msg); diff --git a/src/server/room.cpp b/src/server/room.cpp index 05d58931..61eb4a4c 100644 --- a/src/server/room.cpp +++ b/src/server/room.cpp @@ -4,10 +4,10 @@ Room::Room(Server* server) { - static int roomId = 0; - id = roomId; - roomId++; + id = server->nextRoomId; + server->nextRoomId++; this->server = server; + setParent(server); gameStarted = false; if (!isLobby()) { connect(this, &Room::playerAdded, server->lobby(), &Room::removePlayer); @@ -81,7 +81,7 @@ void Room::setOwner(ServerPlayer *owner) void Room::addPlayer(ServerPlayer *player) { - if (!player) return; + if (isFull() || !player) return; QJsonArray jsonData; @@ -126,7 +126,7 @@ void Room::removePlayer(ServerPlayer *player) // player->doNotify("QuitRoom", "[]"); QJsonArray jsonData; - jsonData << player->getScreenName(); + jsonData << player->getId(); doBroadcastNotify(getPlayers(), "RemovePlayer", QJsonDocument(jsonData).toJson()); if (isAbandoned()) { diff --git a/src/server/server.cpp b/src/server/server.cpp index 270ea3c4..1a496dc3 100644 --- a/src/server/server.cpp +++ b/src/server/server.cpp @@ -18,6 +18,7 @@ Server::Server(QObject* parent) this, &Server::processNewConnection); // create lobby + nextRoomId = 0; createRoom(nullptr, "Lobby", INT32_MAX); connect(lobby(), &Room::playerAdded, this, &Server::updateRoomList); connect(lobby(), &Room::playerRemoved, this, &Server::updateRoomList); @@ -188,15 +189,17 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString& name, co } else { // check if this username already login int id = result["id"].toArray()[0].toString().toInt(); - if (!players.value(id)) + if (!players.value(id)) { // check if password is the same passed = (passwordHash == arr[0].toString()); if (!passed) error_msg = "username or password error"; - else { + } else { // TODO: reconnect here error_msg = "others logged in with this name"; } } + } else { + error_msg = "invalid user name"; } if (passed) { diff --git a/src/server/server.h b/src/server/server.h index 108eedd8..3f8a72a4 100644 --- a/src/server/server.h +++ b/src/server/server.h @@ -3,9 +3,10 @@ class ServerSocket; class ClientSocket; -class Room; class ServerPlayer; +#include "room.h" + class Server : public QObject { Q_OBJECT @@ -49,6 +50,8 @@ private: ServerSocket *server; Room *m_lobby; QMap rooms; + int nextRoomId; + friend Room::Room(Server *server); QHash players; sqlite3 *db; diff --git a/src/swig/client.i b/src/swig/client.i index 783e1f62..695f39a2 100644 --- a/src/swig/client.i +++ b/src/swig/client.i @@ -3,6 +3,12 @@ class QmlBackend : public QObject { public: void emitNotifyUI(const QString &command, const QString &json_data); + + static void cd(const QString &path); + static QStringList ls(const QString &dir); + static QString pwd(); + static bool exists(const QString &file); + static bool isDir(const QString &file); }; extern QmlBackend *Backend; @@ -15,6 +21,9 @@ public: void notifyServer(const QString &command, const QString &json_data); LuaFunction callback; + + ClientPlayer *addPlayer(int id, const QString &name, const QString &avatar); + void removePlayer(int id); }; extern Client *ClientInstance; diff --git a/src/swig/naturalvar.i b/src/swig/naturalvar.i index 4d1edcc7..59e49070 100644 --- a/src/swig/naturalvar.i +++ b/src/swig/naturalvar.i @@ -42,3 +42,35 @@ SWIG_arg ++; %typemap(out) QString const & %{ lua_pushstring(L, $1.toUtf8()); SWIG_arg++; %} +// QStringList +%naturalvar QStringList; + +%typemap(in, checkfn = "lua_istable") QStringList +%{ +for (size_t i = 0; i < lua_rawlen(L, $input); ++i) { + lua_rawgeti(L, $input, i + 1); + const char *elem = luaL_checkstring(L, -1); + $1 << QString::fromUtf8(QByteArray(elem)); + lua_pop(L, 1); +} +%} + +%typemap(out) QStringList +%{ +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()); + lua_rawseti(L, -2, i + 1); +} + +SWIG_arg++; +%} + +%typemap(typecheck) QStringList +%{ + $1 = lua_istable(L, $input) ? 1 : 0; +%} + + diff --git a/src/ui/qmlbackend.cpp b/src/ui/qmlbackend.cpp index 0327548f..84342ec9 100644 --- a/src/ui/qmlbackend.cpp +++ b/src/ui/qmlbackend.cpp @@ -64,3 +64,23 @@ void QmlBackend::quitLobby() void QmlBackend::emitNotifyUI(const QString &command, const QString &jsonData) { emit notifyUI(command, jsonData); } + +void QmlBackend::cd(const QString &path) { + QDir::setCurrent(path); +} + +QStringList QmlBackend::ls(const QString &dir) { + return QDir(dir).entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); +} + +QString QmlBackend::pwd() { + return QDir::currentPath(); +} + +bool QmlBackend::exists(const QString &file) { + return QFile::exists(file); +} + +bool QmlBackend::isDir(const QString &file) { + return QFileInfo(file).isDir(); +} diff --git a/src/ui/qmlbackend.h b/src/ui/qmlbackend.h index 2ba72c3f..c90846cb 100644 --- a/src/ui/qmlbackend.h +++ b/src/ui/qmlbackend.h @@ -18,6 +18,13 @@ public: // lua --> qml void emitNotifyUI(const QString &command, const QString &jsonData); + // File used by both Lua and Qml + static Q_INVOKABLE void cd(const QString &path); + static Q_INVOKABLE QStringList ls(const QString &dir = ""); + static Q_INVOKABLE QString pwd(); + static Q_INVOKABLE bool exists(const QString &file); + static Q_INVOKABLE bool isDir(const QString &file); + signals: void notifyUI(const QString &command, const QString &jsonData);