Role & Seat (#6)

* standard generals

* assignrole

* arrangeSeat

* fix bugs
This commit is contained in:
Notify-ctrl 2022-03-28 22:24:30 +08:00 committed by GitHub
parent 3e4080f2ad
commit 58ea0ca80a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 801 additions and 154 deletions

View File

@ -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)

View File

@ -1 +1,7 @@
local ClientPlayer = Player:subclass("ClientPlayer")
function ClientPlayer:initialize(cp)
self.player = cp
end
return ClientPlayer

View File

@ -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

View File

@ -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

40
lua/core/package.lua Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -1 +0,0 @@

View File

@ -0,0 +1,14 @@
return {
name = "standard",
author = "official",
description = "",
collaborators = {
program = {},
designer = {},
cv = {},
illustrator = {},
},
dependencies = {},
extra_files = {},
}

View File

@ -1,6 +1,6 @@
callbacks["UpdateAvatar"] = function(jsonData) {
mainWindow.busy = false;
self.avatar = avatarName.text;
Self.avatar = jsonData;
toast.show("Update avatar done.");
}

View File

@ -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();
}

View File

@ -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()
}
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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);

View File

@ -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()) {

View File

@ -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) {

View File

@ -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<int, Room *> rooms;
int nextRoomId;
friend Room::Room(Server *server);
QHash<int, ServerPlayer *> players;
sqlite3 *db;

View File

@ -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;

View File

@ -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;
%}

View File

@ -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();
}

View File

@ -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);