* TriggerSkill

* update type comment

* events of phases

* lua annotation

* change style of enums

* clear lua stack

* multi lua_State at server side

* disable addPlayer when room is full or started

* logic:trigger

Co-authored-by: Notify-ctrl <notify-ctrl@qq.com>
This commit is contained in:
Notify-ctrl 2022-04-01 20:51:01 +08:00 committed by GitHub
parent daae2466ae
commit 4e25c032e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1123 additions and 353 deletions

View File

@ -1,36 +1,33 @@
---@class Card : Object
---@field package Package
---@field name string
---@field suit number # enum suit
---@field number number
---@field color number # enum color
---@field id number
---@field type number # enum type
---@field suit Suit
---@field number integer
---@field color Color
---@field id integer
---@field type CardType
local Card = class("Card")
-- enum Suit
fk.createEnum(Card, {
"Spade",
"Club",
"Heart",
"Diamond",
"NoSuit"
})
---@alias Suit integer
-- enum Color
fk.createEnum(Card, {
"Black",
"Red",
"NoColor"
})
Card.Spade = 1
Card.Club = 2
Card.Heart = 3
Card.Diamond = 4
Card.NoSuit = 5
-- enum Type
fk.createEnum(Card, {
"TypeSkill",
"TypeBasic",
"TypeTrick",
"TypeEquip"
})
---@alias Color integer
Card.Black = 1
Card.Red = 2
Card.NoColor = 3
---@alias CardType integer
Card.TypeSkill = 1
Card.TypeBasic = 2
Card.TypeTrick = 3
Card.TypeEquip = 4
function Card:initialize(name, suit, number, color)
self.name = name

19
lua/core/debug.lua Normal file
View File

@ -0,0 +1,19 @@
---@diagnostic disable: lowercase-global
inspect = require "inspect"
DebugMode = true
function PrintWhenMethodCall()
local info = debug.getinfo(2)
local name = info.name
local line = info.currentline
local namewhat = info.namewhat
local shortsrc = info.short_src
if (namewhat == "method") and
(shortsrc ~= "[C]") and
(not string.find(shortsrc, "/lib")) then
print(shortsrc .. ":" .. line .. ": " .. name)
end
end
--debug.sethook(PrintWhenMethodCall, "c")
function p(v) print(inspect(v)) end

View File

@ -1,11 +1,12 @@
---@class Engine : Object
---@field packages table
---@field skills table
---@field related_skills table
---@field generals table
---@field lords table
---@field cards table
---@field translations table
---@field packages table<string, Package>
---@field skills table<string, Skill>
---@field related_skills table<string, Skill[]>
---@field global_trigger TriggerSkill[]
---@field generals table<string, General>
---@field lords string[]
---@field cards Card[]
---@field translations table<string, string>
local Engine = class("Engine")
function Engine:initialize()
@ -19,7 +20,8 @@ function Engine:initialize()
self.packages = {} -- name --> Package
self.skills = {} -- name --> Skill
self.related_skills = {} -- skillName --> relatedName
self.related_skills = {} -- skillName --> relatedSkill[]
self.global_trigger = {}
self.generals = {} -- name --> General
self.lords = {} -- lordName[]
self.cards = {} -- Card[]
@ -61,7 +63,7 @@ function Engine:loadTranslationTable(t)
end
end
---@param skill any
---@param skill Skill
function Engine:addSkill(skill)
assert(skill.class:isSubclassOf(Skill))
if self.skills[skill.name] ~= nil then
@ -70,7 +72,7 @@ function Engine:addSkill(skill)
self.skills[skill.name] = skill
end
---@param skills any[]
---@param skills Skill[]
function Engine:addSkills(skills)
assert(type(skills) == "table")
for _, skill in ipairs(skills) do
@ -111,7 +113,7 @@ function Engine:addCards(cards)
end
end
---@param num number
---@param num integer
---@param generalPool General[]
---@param except string[]
---@param filter function
@ -123,7 +125,7 @@ function Engine:getGeneralsRandomly(num, generalPool, except, filter)
generalPool = generalPool or self.generals
except = except or {}
local availableGenerals = {}
for _, general in pairs(generalPool) do
if not table.contains(except, general.name) and not (filter and filter(general)) then

View File

@ -2,20 +2,19 @@
---@field package Package
---@field name string
---@field kingdom string
---@field hp number
---@field maxHp number
---@field gender number
---@field skills table
---@field other_skills table
---@field hp integer
---@field maxHp integer
---@field gender Gender
---@field skills Skill[]
---@field other_skills string[]
General = class("General")
-- enum Gender
fk.createEnum(General, {
"Male",
"Female"
})
---@alias Gender integer
function General:initialize(package, name, kingdom, hp, maxHp, gender, initialHp)
General.Male = 1
General.Female = 2
function General:initialize(package, name, kingdom, hp, maxHp, gender)
self.package = package
self.name = name
self.kingdom = kingdom
@ -23,12 +22,11 @@ function General:initialize(package, name, kingdom, hp, maxHp, gender, initialHp
self.maxHp = maxHp or hp
self.gender = gender or General.Male
self.skills = {} -- Skill[]
-- skill belongs other general, e.g. "mashu" of pangde
self.other_skills = {} -- string[]
self.skills = {} -- skills first added to this general
self.other_skills = {} -- skill belongs other general, e.g. "mashu" of pangde
end
---@param skill any
---@param skill Skill
function General:addSkill(skill)
if (type(skill) == "string") then
table.insert(self.other_skills, skill)

View File

@ -1,18 +1,17 @@
---@class Package : Object
---@field name string
---@field type number
---@field generals table
---@field extra_skills table
---@field related_skills table
---@field cards table
---@field type PackageType
---@field generals General[]
---@field extra_skills Skill[]
---@field related_skills table<string, string>
---@field cards Card[]
local Package = class("Package")
-- enum Type
fk.createEnum(Package, {
"GeneralPack",
"CardPack",
"SpecialPack"
})
---@alias PackageType integer
Package.GeneralPack = 1
Package.CardPack = 2
Package.SpecialPack = 3
function Package:initialize(name, _type)
assert(type(name) == "string")
@ -21,11 +20,9 @@ function Package:initialize(name, _type)
self.type = _type or Package.GeneralPack
self.generals = {}
-- skill not belongs to any generals, like "jixi"
self.extra_skills = {}
-- table: string --> string
self.extra_skills = {} -- skill not belongs to any generals, like "jixi"
self.related_skills = {}
self.cards = {} --> Card[]
self.cards = {}
end
---@return table skills

View File

@ -1,6 +1,32 @@
---@class Player : Object
---@field hp integer
---@field maxHp integer
---@field kingdom string
---@field role string
---@field general string
---@field handcard_num integer
---@field seat integer
---@field phase Phase
---@field faceup boolean
---@field chained boolean
---@field dying boolean
---@field dead boolean
---@field state string
---@field player_skills Skill[]
local Player = class("Player")
---@alias Phase integer
Player.RoundStart = 1
Player.Start = 2
Player.Judge = 3
Player.Draw = 4
Player.Play = 5
Player.Discard = 6
Player.Finish = 7
Player.NotActive = 8
Player.PhaseNone = 9
function Player:initialize()
self.hp = 0
self.maxHp = 0
@ -15,8 +41,8 @@ function Player:initialize()
self.dying = false
self.dead = false
self.state = ""
self.playerSkills = {}
self.player_skills = {}
end
---@param general General

View File

@ -1,30 +1,22 @@
---@class Skill
---@class Skill : Object
---@field name string
---@field frequency Frequency
---@field visible boolean
local Skill = class("Skill")
fk.createEnum(Skill, {
"Common",
"Frequent",
"Compulsory",
"Awaken",
"Limit",
})
---@alias Frequency integer
function Skill:initialize(name, skillType)
Skill.Frequent = 1
Skill.NotFrequent = 2
Skill.Compulsory = 3
Skill.Limited = 4
Skill.Wake = 5
function Skill:initialize(name, frequency)
-- TODO: visible, lord, etc
self.name = name
self.description = ":" .. name
self.skillType = skillType
end
local TriggerSkill = class("TriggerSkill", Skill)
function TriggerSkill:initialize(spec)
Skill.initialize(self, spec.name, spec.skillType)
self.isRefreshAt = spec.isRefreshAt
self.isTriggerable = spec.isTriggerable
self.targetFilter = spec.targetFilter
self.cardFilter = spec.cardFilter
self.beforeTrigger = spec.beforeTrigger
self.onTrigger = spec.onTrigger
self.frequency = frequency
self.visible = true
end
return Skill

View File

View File

View File

View File

View File

View File

View File

@ -0,0 +1,67 @@
---@class TriggerSkill : Skill
---@field global boolean
---@field events Event[]
---@field refresh_events Event[]
---@field priority_table table<Event, number>
local TriggerSkill = Skill:subclass("TriggerSkill")
function TriggerSkill:initialize(name, frequency)
Skill.initialize(self, name, frequency)
self.global = false
self.events = {}
self.refresh_events = {}
self.priority_table = {} -- GameEvent --> priority
end
-- Default functions
---Determine whether a skill can refresh at this moment
---@param event Event # TriggerEvent
---@param target ServerPlayer # Player who triggered this event
---@param player ServerPlayer # Player who is operating
---@param data any # useful data of the event
---@return nil
function TriggerSkill:canRefresh(event, target, player, data) return false end
---Refresh the skill (e.g. clear marks)
---@param event Event # TriggerEvent
---@param target ServerPlayer # Player who triggered this event
---@param player ServerPlayer # Player who is operating
---@param data any # useful data of the event
function TriggerSkill:refresh(event, target, player, data) end
---Determine whether a skill can trigger at this moment
---@param event Event # TriggerEvent
---@param target ServerPlayer # Player who triggered this event
---@param player ServerPlayer # Player who is operating
---@param data any # useful data of the event
---@return boolean
function TriggerSkill:triggerable(event, target, player, data)
return target and (target == player)
and (self.global or (target:isAlive() and target:hasSkill(self)))
end
---Trigger this skill
---@param event Event # TriggerEvent
---@param target ServerPlayer # Player who triggered this event
---@param player ServerPlayer # Player who is operating
---@param data any # useful data of the event
---@return boolean # returns true if trigger is broken
function TriggerSkill:trigger(event, target, player, data)
print(string.format("%s triggered: event=%d", self.name, event))
--if player.room:askForSkillInvoke(self.name) then
-- return self:use(event, target, player, data)
--end
return false
end
---Use this skill
---@param event Event # TriggerEvent
---@param target ServerPlayer # Player who triggered this event
---@param player ServerPlayer # Player who is operating
---@param data any # useful data of the event
---@return boolean
function TriggerSkill:use(event, target, player, data) end
return TriggerSkill

View File

View File

@ -29,6 +29,26 @@ function table:insertTable(list)
end
end
function table:indexOf(value, from)
from = from or 1
for i = from, #self do
if self[i] == value then return i end
end
return -1
end
function table:removeOne(element)
if #self == 0 or type(self[1]) ~= type(element) then return false end
for i = 1, #self do
if self[i] == element then
table.remove(self, i)
return true
end
end
return false
end
---@class Sql
Sql = {
---@param filename string
@ -95,23 +115,15 @@ function Stack:pop()
return self.t[self.p + 1]
end
function table:removeOne(element)
if #self == 0 or type(self[1]) ~= type(element) then return false end
for i = 1, #self do
if self[i] == element then
table.remove(self, i)
return true
end
end
return false
end
--- convert a table to enum, e.g. core/card.lua
---@param table table # class to store the enum
--- useful function to create enums
---
--- only use it in a terminal
---@param table string
---@param enum string[]
function fk.createEnum(table, enum)
function CreateEnum(table, enum)
local enum_format = "%s.%s = %d"
for i, v in ipairs(enum) do
table[v] = i
print(string.format(enum_format, table, v, i))
end
end

View File

@ -1,3 +1,13 @@
-- load types for extension
SkillCard = require "core.card_type.skill"
BasicCard = require "core.card_type.basic"
TrickCard = require "core.card_type.trick"
EquipCard = require "core.card_type.equip"
dofile "lua/server/event.lua"
TriggerSkill = require "core.skill_type.trigger"
---@param spec table
---@return BasicCard
function fk.CreateBasicCard(spec)
@ -36,3 +46,58 @@ function fk.CreateEquipCard(spec)
local card = EquipCard:new(spec.name, spec.suit, spec.number)
return card
end
---@param spec table
---@return TriggerSkill
function fk.CreateTriggerSkill(spec)
assert(type(spec.name) == "string")
--assert(type(spec.on_trigger) == "function")
if spec.frequency then assert(type(spec.frequency) == "number") end
local frequency = spec.frequency or Skill.NotFrequent
local skill = TriggerSkill:new(spec.name, frequency)
if type(spec.events) == "number" then
table.insert(skill.events, spec.events)
elseif type(spec.events) == "table" then
table.insertTable(skill.events, spec.events)
end
if type(spec.refresh_events) == "number" then
table.insert(skill.refresh_events, spec.refresh_events)
elseif type(spec.refresh_events) == "table" then
table.insertTable(skill.refresh_events, spec.refresh_events)
end
if type(spec.global) == "boolean" then skill.global = spec.global end
if spec.on_trigger then skill.trigger = spec.on_trigger end
if spec.can_trigger then
skill.triggerable = spec.can_trigger
end
if spec.can_refresh then
skill.canRefresh = spec.can_refresh
end
if not spec.priority then
if frequency == Skill.Wake then
spec.priority = 3
elseif frequency == Skill.Compulsory then
spec.priority = 2
else
spec.priority = 1
end
end
if type(spec.priority) == "number" then
for _, event in ipairs(spec.events) do
skill.priority_table[event] = spec.priority
end
elseif type(spec.priority) == "table" then
for event, priority in pairs(spec.priority) do
skill.priority_table[event] = priority
end
end
return skill
end

View File

@ -11,26 +11,15 @@ json = require "json"
dofile "lua/lib/sha256.lua"
dofile "lua/core/util.lua"
dofile "lua/core/debug.lua"
math.randomseed(os.time())
DebugMode = true
function pt(t)
for k, v in pairs(t) do
print(k, v)
end
end
-- load core classes
Engine = require "core.engine"
Package = require "core.package"
General = require "core.general"
Card = require "core.card"
SkillCard = require "core.card_type.skill"
BasicCard = require "core.card_type.basic"
TrickCard = require "core.card_type.trick"
EquipCard = require "core.card_type.equip"
Skill = require "core.skill"
Player = require "core.player"

337
lua/lib/inspect.lua Normal file
View File

@ -0,0 +1,337 @@
local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local math = _tl_compat and _tl_compat.math or math; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
local inspect = {Options = {}, }
inspect._VERSION = 'inspect.lua 3.1.0'
inspect._URL = 'http://github.com/kikito/inspect.lua'
inspect._DESCRIPTION = 'human-readable representations of tables'
inspect._LICENSE = [[
MIT LICENSE
Copyright (c) 2022 Enrique García Cota
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
inspect.KEY = setmetatable({}, { __tostring = function() return 'inspect.KEY' end })
inspect.METATABLE = setmetatable({}, { __tostring = function() return 'inspect.METATABLE' end })
local tostring = tostring
local rep = string.rep
local match = string.match
local char = string.char
local gsub = string.gsub
local fmt = string.format
local function rawpairs(t)
return next, t, nil
end
local function smartQuote(str)
if match(str, '"') and not match(str, "'") then
return "'" .. str .. "'"
end
return '"' .. gsub(str, '"', '\\"') .. '"'
end
local shortControlCharEscapes = {
["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n",
["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v", ["\127"] = "\\127",
}
local longControlCharEscapes = { ["\127"] = "\127" }
for i = 0, 31 do
local ch = char(i)
if not shortControlCharEscapes[ch] then
shortControlCharEscapes[ch] = "\\" .. i
longControlCharEscapes[ch] = fmt("\\%03d", i)
end
end
local function escape(str)
return (gsub(gsub(gsub(str, "\\", "\\\\"),
"(%c)%f[0-9]", longControlCharEscapes),
"%c", shortControlCharEscapes))
end
local function isIdentifier(str)
return type(str) == "string" and not not str:match("^[_%a][_%a%d]*$")
end
local flr = math.floor
local function isSequenceKey(k, sequenceLength)
return type(k) == "number" and
flr(k) == k and
1 <= (k) and
k <= sequenceLength
end
local defaultTypeOrders = {
['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4,
['function'] = 5, ['userdata'] = 6, ['thread'] = 7,
}
local function sortKeys(a, b)
local ta, tb = type(a), type(b)
if ta == tb and (ta == 'string' or ta == 'number') then
return (a) < (b)
end
local dta = defaultTypeOrders[ta] or 100
local dtb = defaultTypeOrders[tb] or 100
return dta == dtb and ta < tb or dta < dtb
end
local function getKeys(t)
local seqLen = 1
while rawget(t, seqLen) ~= nil do
seqLen = seqLen + 1
end
seqLen = seqLen - 1
local keys, keysLen = {}, 0
for k in rawpairs(t) do
if not isSequenceKey(k, seqLen) then
keysLen = keysLen + 1
keys[keysLen] = k
end
end
table.sort(keys, sortKeys)
return keys, keysLen, seqLen
end
local function countCycles(x, cycles)
if type(x) == "table" then
if cycles[x] then
cycles[x] = cycles[x] + 1
else
cycles[x] = 1
for k, v in rawpairs(x) do
countCycles(k, cycles)
countCycles(v, cycles)
end
countCycles(getmetatable(x), cycles)
end
end
end
local function makePath(path, a, b)
local newPath = {}
local len = #path
for i = 1, len do newPath[i] = path[i] end
newPath[len + 1] = a
newPath[len + 2] = b
return newPath
end
local function processRecursive(process,
item,
path,
visited)
if item == nil then return nil end
if visited[item] then return visited[item] end
local processed = process(item, path)
if type(processed) == "table" then
local processedCopy = {}
visited[item] = processedCopy
local processedKey
for k, v in rawpairs(processed) do
processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited)
if processedKey ~= nil then
processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited)
end
end
local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited)
if type(mt) ~= 'table' then mt = nil end
setmetatable(processedCopy, mt)
processed = processedCopy
end
return processed
end
local function puts(buf, str)
buf.n = buf.n + 1
buf[buf.n] = str
end
local Inspector = {}
local Inspector_mt = { __index = Inspector }
local function tabify(inspector)
puts(inspector.buf, inspector.newline .. rep(inspector.indent, inspector.level))
end
function Inspector:getId(v)
local id = self.ids[v]
local ids = self.ids
if not id then
local tv = type(v)
id = (ids[tv] or 0) + 1
ids[v], ids[tv] = id, id
end
return tostring(id)
end
function Inspector:putValue(v)
local buf = self.buf
local tv = type(v)
if tv == 'string' then
puts(buf, smartQuote(escape(v)))
elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or
tv == 'cdata' or tv == 'ctype' then
puts(buf, tostring(v))
elseif tv == 'table' and not self.ids[v] then
local t = v
if t == inspect.KEY or t == inspect.METATABLE then
puts(buf, tostring(t))
elseif self.level >= self.depth then
puts(buf, '{...}')
else
if self.cycles[t] > 1 then puts(buf, fmt('<%d>', self:getId(t))) end
local keys, keysLen, seqLen = getKeys(t)
puts(buf, '{')
self.level = self.level + 1
for i = 1, seqLen + keysLen do
if i > 1 then puts(buf, ',') end
if i <= seqLen then
puts(buf, ' ')
self:putValue(t[i])
else
local k = keys[i - seqLen]
tabify(self)
if isIdentifier(k) then
puts(buf, k)
else
puts(buf, "[")
self:putValue(k)
puts(buf, "]")
end
puts(buf, ' = ')
self:putValue(t[k])
end
end
local mt = getmetatable(t)
if type(mt) == 'table' then
if seqLen + keysLen > 0 then puts(buf, ',') end
tabify(self)
puts(buf, '<metatable> = ')
self:putValue(mt)
end
self.level = self.level - 1
if keysLen > 0 or type(mt) == 'table' then
tabify(self)
elseif seqLen > 0 then
puts(buf, ' ')
end
puts(buf, '}')
end
else
puts(buf, fmt('<%s %d>', tv, self:getId(v)))
end
end
function inspect.inspect(root, options)
options = options or {}
local depth = options.depth or (math.huge)
local newline = options.newline or '\n'
local indent = options.indent or ' '
local process = options.process
if process then
root = processRecursive(process, root, {}, {})
end
local cycles = {}
countCycles(root, cycles)
local inspector = setmetatable({
buf = { n = 0 },
ids = {},
cycles = cycles,
depth = depth,
level = 0,
newline = newline,
indent = indent,
}, Inspector_mt)
inspector:putValue(root)
return table.concat(inspector.buf)
end
setmetatable(inspect, {
__call = function(_, root, options)
return inspect.inspect(root, options)
end,
})
return inspect

View File

@ -1,16 +1,11 @@
local EVENTS = {
"GameStart",
---@alias Event integer
"PhaseChanging",
"PhaseStart",
"PhaseProceeding",
"PhaseEnd",
"PreCardUse",
"AfterCardUseDeclared",
"AfterCardTargetDeclared",
"CardUsing",
"CardUseFinished",
}
GameEvent = Util:createEnum(EVENTS)
fk.NonTrigger = 1
fk.GameStart = 2
fk.TurnStart = 3
fk.EventPhaseStart = 4
fk.EventPhaseProceeding = 5
fk.EventPhaseEnd = 6
fk.EventPhaseChanging = 7
fk.EventPhaseSkipping = 8
fk.NumOfEvents = 9

View File

@ -1,14 +1,16 @@
---@class GameLogic: Object
---@field room Room
---@field skill_table table
---@filed skills table
---@field skill_table table<Event, TriggerSkill[]>
---@field refresh_skill_table table<Event, TriggerSkill[]>
---@field skills string[]
---@field event_stack Stack
---@field role_table table
---@field role_table string[][]
local GameLogic = class("GameLogic")
function GameLogic:initialize(room)
self.room = room
self.skill_table = {} -- TriggerEvent --> Skill[]
self.skill_table = {} -- TriggerEvent --> TriggerSkill[]
self.refresh_skill_table = {}
self.skills = {} -- skillName[]
self.event_stack = Stack:new()
@ -62,6 +64,7 @@ function GameLogic:chooseGenerals()
local lord = room:getLord()
local lord_general = nil
if lord ~= nil then
room.current = lord
local generals = Fk:getGeneralsRandomly(3)
for i = 1, #generals do
generals[i] = generals[i].name
@ -99,7 +102,7 @@ end
function GameLogic:prepareForStart()
local room = self.room
local players = room.players
room.alive_players = players
room.alive_players = {table.unpack(players)}
for i = 1, #players - 1 do
players[i].next = players[i + 1]
end
@ -127,11 +130,141 @@ function GameLogic:prepareForStart()
-- TODO: prepare drawPile
-- TODO: init cards in drawPile
-- TODO: init trigger table for self
self:addTriggerSkill(GameRule)
for _, trig in ipairs(Fk.global_trigger) do
self:addTriggerSkill(trig)
end
end
function GameLogic:action()
self:trigger(fk.GameStart)
local room = self.room
while true do
self:trigger(fk.TurnStart, room.current)
if room.game_finished then break end
room.current = room.current:getNextAlive()
end
end
---@param skill TriggerSkill
function GameLogic:addTriggerSkill(skill)
if skill == nil or table.contains(self.skills, skill.name) then
return
end
table.insert(self.skills, skill.name)
for _, event in ipairs(skill.refresh_events) do
if self.refresh_skill_table[event] == nil then
self.refresh_skill_table[event] = {}
end
table.insert(self.refresh_skill_table[event], skill)
end
for _, event in ipairs(skill.events) do
if self.skill_table[event] == nil then
self.skill_table[event] = {}
end
table.insert(self.skill_table[event], skill)
end
if skill.visible then
if (Fk.related_skills[skill.name] == nil) then return end
for _, s in ipairs(Fk.related_skills[skill.name]) do
if (s.class == TriggerSkill) then
self:addTriggerSkill(s)
end
end
end
end
---@param event Event
---@param target ServerPlayer
---@param data any
function GameLogic:trigger(event, target, data)
local room = self.room
local broken = false
local skills = self.skill_table[event] or {}
local skills_to_refresh = self.refresh_skill_table[event] or {}
local player = target
self.event_stack:push({event, target, data})
if target == nil then
for _, skill in ipairs(skills_to_refresh) do
if skill:canRefresh(event, target, player, data) then
skill:refresh(event, target, player, data)
end
end
for _, skill in ipairs(skills) do
if skill:triggerable(event, target, player, data) then
broken = skill:trigger(event, target, player, data)
if broken then break end
end
end
self.event_stack:pop()
return broken
end
repeat do
-- refresh skills. This should not be broken
for _, skill in ipairs(skills_to_refresh) do
if skill:canRefresh(event, target, player, data) then
skill:refresh(event, target, player, data)
end
end
player = player.next
end until player == target
---@param a TriggerSkill
---@param b TriggerSkill
local compare_func = function (a, b)
return a.priority_table[event] > b.priority_table[event]
end
table.sort(skills, compare_func)
repeat do
local triggerable_skills = {} ---@type table<number, TriggerSkill[]>
local priority_table = {} ---@type number[]
for _, skill in ipairs(skills) do
if skill:triggerable(event, target, player, data) then
local priority = skill.priority_table[event]
if triggerable_skills[priority] == nil then
triggerable_skills[priority] = {}
end
table.insert(triggerable_skills[priority], skill)
if not table.contains(priority_table, priority) then
table.insert(priority_table, priority)
end
end
end
for _, priority in ipairs(priority_table) do
local triggerables = triggerable_skills[priority]
local skill_names = {} ---@type string[]
for _, skill in ipairs(triggerables) do
table.insert(skill_names, skill.name)
end
while #skill_names > 0 do
local skill_name = room:askForChoice(player, skill_names)
local skill = triggerables[table.indexOf(skill_names, skill_name)]
broken = skill:trigger(event, target, player, data)
if broken then break end
table.removeOne(skill_names, skill_name)
table.removeOne(triggerables, skill)
end
end
if broken then break end
player = player.next
end until player == target
self.event_stack:pop()
return broken
end
return GameLogic

68
lua/server/lobby.lua Normal file
View File

@ -0,0 +1,68 @@
---@class Lobby : Object
---@field lobby fk.Room
Lobby = class("Lobby")
fk.lobby_callback = {}
local db = fk.ServerInstance:getDatabase()
function Lobby:initialize(_lobby)
self.lobby = _lobby
self.lobby.callback = function(_self, command, jsonData)
local cb = fk.lobby_callback[command]
if (type(cb) == "function") then
cb(jsonData)
else
print("Lobby error: Unknown command " .. command);
end
end
end
fk.lobby_callback["UpdateAvatar"] = function(jsonData)
-- jsonData: [ int uid, string newavatar ]
local data = json.decode(jsonData)
local id, avatar = data[1], data[2]
local sql = "UPDATE userinfo SET avatar='%s' WHERE id=%d;"
Sql.exec(db, string.format(sql, avatar, id))
local player = fk.ServerInstance:findPlayer(id)
player:setAvatar(avatar)
player:doNotify("UpdateAvatar", avatar)
end
fk.lobby_callback["UpdatePassword"] = function(jsonData)
-- jsonData: [ int uid, string oldpassword, int newpassword ]
local data = json.decode(jsonData)
local id, old, new = data[1], data[2], data[3]
local sql_find = "SELECT password FROM userinfo WHERE id=%d;"
local sql_update = "UPDATE userinfo SET password='%s' WHERE id=%d;"
local passed = false
local result = Sql.exec_select(db, string.format(sql_find, id))
passed = (result["password"][1] == sha256(old))
if passed then
Sql.exec(db, string.format(sql_update, sha256(new), id))
end
local player = fk.ServerInstance:findPlayer(tonumber(id))
player:doNotify("UpdatePassword", passed and "1" or "0")
end
fk.lobby_callback["CreateRoom"] = function(jsonData)
-- jsonData: [ int uid, string name, int capacity ]
local data = json.decode(jsonData)
local owner = fk.ServerInstance:findPlayer(tonumber(data[1]))
local roomName = data[2]
local capacity = data[3]
fk.ServerInstance:createRoom(owner, roomName, capacity)
end
fk.lobby_callback["EnterRoom"] = function(jsonData)
-- jsonData: [ int uid, int roomId ]
local data = json.decode(jsonData)
local player = fk.ServerInstance:findPlayer(tonumber(data[1]))
local room = fk.ServerInstance:findRoom(tonumber(data[2]))
room:addPlayer(player)
end
function CreateRoom(_room)
LobbyInstance = Lobby:new(_room)
end

View File

@ -1,17 +1,37 @@
---@class Room : Object
---@field room fk.Room
---@field server Server
---@field players table
---@field alive_players table
---@field players ServerPlayer[]
---@field alive_players ServerPlayer[]
---@field current ServerPlayer
---@field game_finished boolean
---@field timeout number
---@field timeout integer
local Room = class("Room")
-- load classes used by the game
GameLogic = require "server.gamelogic"
ServerPlayer = require "server.serverplayer"
fk.room_callback = {}
---@param _room fk.Room
function Room:initialize(_room)
self.room = _room
self.server = nil
self.players = {} -- ServerPlayer[]
self.room.callback = function(_self, command, jsonData)
local cb = fk.room_callback[command]
if (type(cb) == "function") then
cb(jsonData)
else
print("Lobby error: Unknown command " .. command);
end
end
self.room.startGame = function(_self)
self:run()
end
self.players = {}
self.alive_players = {}
self.current = nil
self.game_finished = false
self.timeout = _room:getTimeout()
end
@ -23,7 +43,6 @@ function Room:run()
player.state = p:getStateString()
player.room = self
table.insert(self.players, player)
self.server.players[player:getId()] = player
end
self.logic = GameLogic:new(self)
@ -189,4 +208,50 @@ function Room:gameOver()
self.room:gameOver()
end
return Room
---@param id integer
function Room:findPlayerById(id)
for _, p in ipairs(self.players) do
if p:getId() == id then
return p
end
end
return nil
end
---@param player ServerPlayer
---@param choices string[]
function Room:askForChoice(player, choices)
return choices[1]
end
fk.room_callback["QuitRoom"] = function(jsonData)
-- jsonData: [ int uid ]
local data = json.decode(jsonData)
local player = fk.ServerInstance:findPlayer(tonumber(data[1]))
local room = player:getRoom()
if not room:isLobby() then
room:removePlayer(player)
end
end
fk.room_callback["PlayerStateChanged"] = function(jsonData)
-- jsonData: [ int uid, string stateString ]
-- note: this function is not called by Router.
-- note: when this function is called, the room must be started
local data = json.decode(jsonData)
local id = data[1]
local stateString = data[2]
RoomInstance:findPlayerById(id).state = stateString
end
fk.room_callback["DoLuaScript"] = function(jsonData)
-- jsonData: [ int uid, string luaScript ]
-- warning: only use this in debugging mode.
if not DebugMode then return end
local data = json.decode(jsonData)
assert(load(data[2]))()
end
function CreateRoom(_room)
RoomInstance = Room:new(_room)
end

View File

@ -1,118 +0,0 @@
---@class Server : Object
---@field server fk.Server
---@field db fk.SQLite3
---@field rooms table
---@field players table
Server = class('Server')
-- load server classes
Room = require "server.room"
GameLogic = require "server.gamelogic"
ServerPlayer = require "server.serverplayer"
fk.server_callback = {}
function Server:initialize()
self.server = fk.ServerInstance
self.db = fk.ServerInstance:getDatabase()
self.server.callback = function(_self, command, jsonData)
local cb = fk.server_callback[command]
if (type(cb) == "function") then
cb(jsonData)
else
print("Server error: Unknown command " .. command);
end
end
self.server.startRoom = function(_self, _room)
local room = Room:new(_room)
room.server = self
table.insert(self.rooms, room)
room:run()
-- If room.run returns, the game is over and lua room
-- should be destoried now.
-- This behavior does not affect C++ Room.
table.removeOne(self.rooms, room)
end
self.rooms = {} -- id --> Room(Started)
self.players = {} -- id --> ServerPlayer
end
fk.server_callback["UpdateAvatar"] = function(jsonData)
-- jsonData: [ int uid, string newavatar ]
local data = json.decode(jsonData)
local id, avatar = data[1], data[2]
local sql = "UPDATE userinfo SET avatar='%s' WHERE id=%d;"
Sql.exec(ServerInstance.db, string.format(sql, avatar, id))
local player = fk.ServerInstance:findPlayer(id)
player:setAvatar(avatar)
player:doNotify("UpdateAvatar", avatar)
end
fk.server_callback["UpdatePassword"] = function(jsonData)
-- jsonData: [ int uid, string oldpassword, int newpassword ]
local data = json.decode(jsonData)
local id, old, new = data[1], data[2], data[3]
local sql_find = "SELECT password FROM userinfo WHERE id=%d;"
local sql_update = "UPDATE userinfo SET password='%s' WHERE id=%d;"
local db = ServerInstance.db
local passed = false
local result = Sql.exec_select(db, string.format(sql_find, id))
passed = (result["password"][1] == sha256(old))
if passed then
Sql.exec(db, string.format(sql_update, sha256(new), id))
end
local player = fk.ServerInstance:findPlayer(tonumber(id))
player:doNotify("UpdatePassword", passed and "1" or "0")
end
fk.server_callback["CreateRoom"] = function(jsonData)
-- jsonData: [ int uid, string name, int capacity ]
local data = json.decode(jsonData)
local owner = fk.ServerInstance:findPlayer(tonumber(data[1]))
local roomName = data[2]
local capacity = data[3]
fk.ServerInstance:createRoom(owner, roomName, capacity)
end
fk.server_callback["EnterRoom"] = function(jsonData)
-- jsonData: [ int uid, int roomId ]
local data = json.decode(jsonData)
local player = fk.ServerInstance:findPlayer(tonumber(data[1]))
local room = fk.ServerInstance:findRoom(tonumber(data[2]))
room:addPlayer(player)
end
fk.server_callback["QuitRoom"] = function(jsonData)
-- jsonData: [ int uid ]
local data = json.decode(jsonData)
local player = fk.ServerInstance:findPlayer(tonumber(data[1]))
local room = player:getRoom()
if not room:isLobby() then
room:removePlayer(player)
end
end
fk.server_callback["DoLuaScript"] = function(jsonData)
-- jsonData: [ int uid, string luaScript ]
-- warning: only use this in debugging mode.
if not DebugMode then return end
local data = json.decode(jsonData)
assert(load(data[2]))()
end
fk.server_callback["PlayerStateChanged"] = function(jsonData)
-- jsonData: [ int uid, string stateString ]
-- note: this function is not called by Router.
local data = json.decode(jsonData)
local id = data[1]
local stateString = data[2]
ServerInstance.players[id].state = stateString
end
ServerInstance = Server:new()

View File

@ -22,7 +22,7 @@ function ServerPlayer:initialize(_self)
self.reply_ready = false
end
---@return number
---@return integer
function ServerPlayer:getId()
return self.serverplayer:getId()
end
@ -38,7 +38,7 @@ end
--- *timeout* must not be negative. If nil, room.timeout is used.
---@param command string
---@param jsonData string
---@param timeout number
---@param timeout integer
function ServerPlayer:doRequest(command, jsonData, timeout)
timeout = timeout or self.room.timeout
self.client_reply = ""
@ -49,7 +49,7 @@ end
--- Wait for at most *timeout* seconds for reply from client.
---
--- If *timeout* is negative or **nil**, the function will wait forever until get reply.
---@param timeout number # seconds to wait
---@param timeout integer # seconds to wait
---@return string reply # JSON data
function ServerPlayer:waitForReply(timeout)
local result = ""
@ -64,4 +64,25 @@ function ServerPlayer:waitForReply(timeout)
return result
end
---@param skill Skill
function ServerPlayer:hasSkill(skill)
return table.contains(self.player_skills, skill)
end
function ServerPlayer:isAlive()
return self.dead == false
end
function ServerPlayer:getNextAlive()
if #self.room.alive_players == 0 then
return self
end
local ret = self.next
while ret.dead do
ret = ret.next
end
return ret
end
return ServerPlayer

View File

@ -11,7 +11,7 @@ fk = {}
SPlayerList = {}
--- * get microsecond from Epoch
---@return number microsecond
---@return integer microsecond
function fk:GetMicroSecond()end
--- construct a QList<ServerPlayer *>.

View File

@ -5,13 +5,22 @@
--- middleclass
class = {}
---@param class class
---@return boolean
function class:isSubclassOf(class) end
---@class Object
---@field class class
Object = {}
---@generic T
---@param self T
function Object:initialize(...) end
function Object.new(...)end
---@generic T
---@param self T
---@return T
function Object:new(...)end
---@param name string
function Object:subclass(name)end

View File

@ -3,10 +3,10 @@
---@class fk.Player
FPlayer = {}
---@return number id
---@return integer id
function FPlayer:getId()end
---@param id number
---@param id integer
function FPlayer:setId(id)end
---@return string name
@ -47,13 +47,13 @@ function FServerPlayer:speak(msg)end
--- *timeout* must not be negative or **nil**.
---@param command string
---@param jsonData string
---@param timeout number
---@param timeout integer
function FServerPlayer:doRequest(command,jsonData,timeout)end
--- Wait for at most *timeout* seconds for reply from client.
---
--- If *timeout* is negative or **nil**, the function will wait forever until get reply.
---@param timeout number # seconds to wait
---@param timeout integer # seconds to wait
---@return string reply # JSON data
---@overload fun()
function FServerPlayer:waitForReply(timeout)end

View File

@ -1,6 +1,6 @@
---@meta
---@return number length
---@return integer length
function SPlayerList:length()end
---@param e fk.ServerPlayer
@ -10,7 +10,7 @@ function SPlayerList:append(e)end
---@return boolean
function SPlayerList:contains(e)end
---@param index number
---@param index integer
---@return fk.ServerPlayer | nil
function SPlayerList:at(index)end

View File

@ -12,14 +12,17 @@ FRoom = {}
---@param owner fk.ServerPlayer
---@param name string
---@param capacity number
---@param capacity integer
function FServer:createRoom(owner,name,capacity)end
---@param id number
---@param id integer
---@return fk.Room room
function FServer:findRoom(id)end
---@param id number
---@return fk.Room room
function FServer:lobby()end
---@param id integer
---@return fk.ServerPlayer player
function FServer:findPlayer(id)end

View File

@ -0,0 +1,13 @@
GameRule = fk.CreateTriggerSkill{
name = "game_rule",
events = {
fk.GameStart, fk.TurnStart,
fk.EventPhaseProceeding, fk.EventPhaseEnd, fk.EventPhaseChanging,
},
priority = 0,
can_trigger = function(self, event, target, player, data)
return (target == player) or (target == nil)
end,
}

View File

@ -1,5 +1,6 @@
local extension = Package:new("standard")
extension.metadata = require "packages.standard.metadata"
dofile "packages/standard/game_rule.lua"
Fk:loadTranslationTable{
["wei"] = "",

View File

@ -8,6 +8,7 @@ callbacks["NetworkDelayTest"] = function(jsonData) {
}
callbacks["ErrorMsg"] = function(jsonData) {
console.log("ERROR: " + jsonData);
toast.show(jsonData, 5000);
mainWindow.busy = false;
}

View File

@ -21,14 +21,43 @@ bool DoLuaScript(lua_State *L, const char *script)
luaL_loadfile(L, script);
int error = lua_pcall(L, 0, LUA_MULTRET, -2);
if (error) {
const char *error_msg = lua_tostring(L, -1);
qDebug() << error_msg;
lua_pop(L, 2);
return false;
}
lua_pop(L, 1);
return true;
}
// For Lua debugging
void Dumpstack(lua_State *L)
{
int top = lua_gettop(L);
for (int i = 1; i <= top; i++) {
printf("%d\t%s\t", i, luaL_typename(L, i));
switch (lua_type(L, i)) {
case LUA_TNUMBER:
printf("%g\n",lua_tonumber(L, i));
break;
case LUA_TSTRING:
printf("%s\n",lua_tostring(L, i));
break;
case LUA_TBOOLEAN:
printf("%s\n", (lua_toboolean(L, i) ? "true" : "false"));
break;
case LUA_TNIL:
printf("%s\n", "nil");
break;
default:
printf("%p\n",lua_topointer(L, i));
break;
}
}
}
sqlite3 *OpenDatabase(const QString &filename)
{
sqlite3 *ret;

View File

@ -155,12 +155,11 @@ void Router::handlePacket(const QByteArray& rawPacket)
if (type & DEST_CLIENT) {
ClientInstance->callLua(command, jsonData);
} else {
ServerPlayer *player = qobject_cast<ServerPlayer *>(parent());
// Add the uid of sender to jsonData
QJsonArray arr = QJsonDocument::fromJson(jsonData.toUtf8()).array();
arr.prepend(
qobject_cast<ServerPlayer *>(parent())->getId()
);
ServerInstance->callLua(command, QJsonDocument(arr).toJson());
arr.prepend(player->getId());
player->getRoom()->callLua(command, QJsonDocument(arr).toJson());
}
}
else if (type & TYPE_REQUEST) {

View File

@ -1,6 +1,7 @@
#include "room.h"
#include "serverplayer.h"
#include "server.h"
#include "util.h"
Room::Room(Server* server)
{
@ -15,12 +16,22 @@ Room::Room(Server* server)
connect(this, &Room::playerAdded, server->lobby(), &Room::removePlayer);
connect(this, &Room::playerRemoved, server->lobby(), &Room::addPlayer);
}
L = CreateLuaState();
DoLuaScript(L, "lua/freekill.lua");
if (isLobby()) {
DoLuaScript(L, "lua/server/lobby.lua");
} else {
DoLuaScript(L, "lua/server/room.lua");
}
initLua();
}
Room::~Room()
{
// TODO
disconnect();
lua_close(L);
}
Server *Room::getServer() const
@ -83,7 +94,15 @@ void Room::setOwner(ServerPlayer *owner)
void Room::addPlayer(ServerPlayer *player)
{
if (isFull() || !player) return;
if (!player) return;
if (isFull() || gameStarted) {
player->doNotify("ErrorMsg", "Room is full or already started!");
if (runned_players.contains(player->getId())) {
player->doNotify("ErrorMsg", "Running away is shameful.");
}
return;
}
QJsonArray jsonData;
@ -120,7 +139,7 @@ void Room::addPlayer(ServerPlayer *player)
player->doNotify("RoomOwner", QJsonDocument(jsonData).toJson());
}
if (isFull())
if (isFull() && !gameStarted)
start();
}
emit playerAdded(player);
@ -133,10 +152,18 @@ void Room::removePlayer(ServerPlayer *player)
if (isLobby()) return;
// player->doNotify("QuitRoom", "[]");
QJsonArray jsonData;
jsonData << player->getId();
doBroadcastNotify(getPlayers(), "RemovePlayer", QJsonDocument(jsonData).toJson());
if (gameStarted) {
QJsonArray jsonData;
jsonData << player->getId();
doBroadcastNotify(getPlayers(), "PlayerRunned", QJsonDocument(jsonData).toJson());
runned_players << player->getId();
// TODO: create a robot for runned player.
} else {
QJsonArray jsonData;
jsonData << player->getId();
doBroadcastNotify(getPlayers(), "RemovePlayer", QJsonDocument(jsonData).toJson());
}
if (isAbandoned()) {
emit abandoned();
@ -198,6 +225,7 @@ void Room::doBroadcastNotify(const QList<ServerPlayer *> targets,
void Room::gameOver()
{
gameStarted = false;
runned_players.clear();
// clean offline players
foreach (ServerPlayer *p, players) {
if (p->getState() == Player::Offline) {
@ -209,6 +237,5 @@ void Room::gameOver()
void Room::run()
{
gameStarted = true;
getServer()->roomStart(this);
roomStart();
}

View File

@ -48,6 +48,15 @@ public:
void gameOver();
lua_State *getLuaState() const;
void initLua();
void callLua(const QString &command, const QString &jsonData);
LuaFunction callback;
void roomStart();
LuaFunction startGame;
signals:
void abandoned();
@ -66,9 +75,12 @@ private:
ServerPlayer *owner; // who created this room?
QList<ServerPlayer *> players;
QList<int> runned_players;
bool gameStarted;
int timeout;
lua_State *L;
};
#endif // _ROOM_H

View File

@ -12,6 +12,7 @@ Server::Server(QObject* parent)
: QObject(parent)
{
ServerInstance = this;
db = OpenDatabase();
server = new ServerSocket();
server->setParent(this);
connect(server, &ServerSocket::new_connection,
@ -22,19 +23,12 @@ Server::Server(QObject* parent)
createRoom(nullptr, "Lobby", INT32_MAX);
connect(lobby(), &Room::playerAdded, this, &Server::updateRoomList);
connect(lobby(), &Room::playerRemoved, this, &Server::updateRoomList);
db = OpenDatabase();
L = CreateLuaState();
DoLuaScript(L, "lua/freekill.lua");
DoLuaScript(L, "lua/server/server.lua");
}
Server::~Server()
{
ServerInstance = nullptr;
m_lobby->deleteLater();
lua_close(L);
sqlite3_close(db);
}
@ -257,9 +251,9 @@ void Server::onUserDisconnected()
void Server::onUserStateChanged()
{
Player *player = qobject_cast<Player *>(sender());
ServerPlayer *player = qobject_cast<ServerPlayer *>(sender());
QJsonArray arr;
arr << player->getId();
arr << player->getStateString();
callLua("PlayerStateChanged", QJsonDocument(arr).toJson());
player->getRoom()->callLua("PlayerStateChanged", QJsonDocument(arr).toJson());
}

View File

@ -27,12 +27,6 @@ public:
sqlite3 *getDatabase();
void callLua(const QString &command, const QString &jsonData);
LuaFunction callback;
void roomStart(Room *room);
LuaFunction startRoom;
signals:
void roomCreated(Room *room);
void playerAdded(ServerPlayer *player);
@ -55,7 +49,6 @@ private:
QHash<int, ServerPlayer *> players;
sqlite3 *db;
lua_State *L;
void handleNameAndPassword(ClientSocket *client, const QString &name, const QString &password);
};

View File

@ -43,9 +43,12 @@ void Client::callLua(const QString& command, const QString& json_data)
lua_pushstring(L, json_data.toUtf8());
int error = lua_pcall(L, 3, 0, -5);
if (error) {
const char *error_msg = lua_tostring(L, -1);
qDebug() << error_msg;
lua_pop(L, 2);
}
lua_pop(L, 1);
}
%}

View File

@ -2,57 +2,14 @@
%nodefaultdtor Server;
class Server : public QObject {
public:
Room *lobby() const;
void createRoom(ServerPlayer *owner, const QString &name, int capacity);
Room *findRoom(int id) const;
ServerPlayer *findPlayer(int id) const;
sqlite3 *getDatabase();
LuaFunction callback;
LuaFunction startRoom;
};
%{
void Server::callLua(const QString& command, const QString& json_data)
{
Q_ASSERT(callback);
lua_getglobal(L, "debug");
lua_getfield(L, -1, "traceback");
lua_replace(L, -2);
lua_rawgeti(L, LUA_REGISTRYINDEX, callback);
SWIG_NewPointerObj(L, this, SWIGTYPE_p_Server, 0);
lua_pushstring(L, command.toUtf8());
lua_pushstring(L, json_data.toUtf8());
int error = lua_pcall(L, 3, 0, -5);
if (error) {
const char *error_msg = lua_tostring(L, -1);
qDebug() << error_msg;
}
}
void Server::roomStart(Room *room) {
Q_ASSERT(startRoom);
lua_getglobal(L, "debug");
lua_getfield(L, -1, "traceback");
lua_replace(L, -2);
lua_rawgeti(L, LUA_REGISTRYINDEX, startRoom);
SWIG_NewPointerObj(L, this, SWIGTYPE_p_Server, 0);
SWIG_NewPointerObj(L, room, SWIGTYPE_p_Room, 0);
int error = lua_pcall(L, 2, 0, -4);
if (error) {
const char *error_msg = lua_tostring(L, -1);
qDebug() << error_msg;
}
}
%}
extern Server *ServerInstance;
%nodefaultctor Room;
@ -93,5 +50,69 @@ public:
);
void gameOver();
LuaFunction callback;
LuaFunction startGame;
};
%{
void Room::initLua()
{
lua_getglobal(L, "debug");
lua_getfield(L, -1, "traceback");
lua_replace(L, -2);
lua_getglobal(L, "CreateRoom");
SWIG_NewPointerObj(L, this, SWIGTYPE_p_Room, 0);
int error = lua_pcall(L, 1, 0, -2);
lua_pop(L, 1);
if (error) {
const char *error_msg = lua_tostring(L, -1);
qDebug() << error_msg;
}
}
void Room::callLua(const QString& command, const QString& json_data)
{
Q_ASSERT(callback);
lua_getglobal(L, "debug");
lua_getfield(L, -1, "traceback");
lua_replace(L, -2);
lua_rawgeti(L, LUA_REGISTRYINDEX, callback);
SWIG_NewPointerObj(L, this, SWIGTYPE_p_Room, 0);
lua_pushstring(L, command.toUtf8());
lua_pushstring(L, json_data.toUtf8());
int error = lua_pcall(L, 3, 0, -5);
if (error) {
const char *error_msg = lua_tostring(L, -1);
qDebug() << error_msg;
lua_pop(L, 2);
}
lua_pop(L, 1);
}
void Room::roomStart() {
Q_ASSERT(startGame);
lua_getglobal(L, "debug");
lua_getfield(L, -1, "traceback");
lua_replace(L, -2);
lua_rawgeti(L, LUA_REGISTRYINDEX, startGame);
SWIG_NewPointerObj(L, this, SWIGTYPE_p_Room, 0);
int error = lua_pcall(L, 1, 0, -3);
if (error) {
const char *error_msg = lua_tostring(L, -1);
qDebug() << error_msg;
lua_pop(L, 2);
}
lua_pop(L, 1);
}
%}