FreeKill/test/lua/lib/serverplayer.lua

613 lines
18 KiB
Lua
Raw Permalink Normal View History

-- 仿效 swig 中 class ServerPlayer 的接口制作,为了便于测试
local ServerPlayer = class("fk.ServerPlayer")
local io = fk.io
fk.Player_Invalid = 0
fk.Player_Online = 1
fk.Player_Trust = 2
fk.Player_Run = 3
fk.Player_Leave = 4
fk.Player_Robot = 5
fk.Player_Offline = 6
local function colorConvert(log)
log = log:gsub('<font color="#0598BC"><b>', string.char(27) .. "[34;1m")
log = log:gsub('<font color="#0C8F0C"><b>', string.char(27) .. "[32;1m")
log = log:gsub('<font color="#CC3131"><b>', string.char(27) .. "[31;1m")
log = log:gsub('<font color="red"><b>', string.char(27) .. "[31;1m")
log = log:gsub('<font color="black"><b>', string.char(27) .. "[0;1m")
log = log:gsub('<font color="#0598BC">', string.char(27) .. "[34m")
log = log:gsub('<font color="blue">', string.char(27) .. "[34m")
log = log:gsub('<font color="#0C8F0C">', string.char(27) .. "[32m")
log = log:gsub('<font color="green">', string.char(27) .. "[32m")
log = log:gsub('<font color="#CC3131">', string.char(27) .. "[31m")
log = log:gsub('<font color="red">', string.char(27) .. "[31m")
log = log:gsub('<font color="grey">', string.char(27) .. "[90m")
log = log:gsub("<b>", fk.BOLD)
log = log:gsub("</b></font>", fk.RST)
log = log:gsub("</font>", fk.RST)
log = log:gsub("<b>", fk.BOLD)
log = log:gsub("</b>", fk.RST)
log = log:gsub("<br>", "\n")
log = log:gsub("<br/>", "\n")
log = log:gsub("<br />", "\n")
return log
end
---@param msg LogMessage
local function parseMsg(msg, nocolor)
local self = Fk:currentRoom()
local data = msg
local function getPlayerStr(pid, color)
if nocolor then color = "white" end
if not pid then
return ""
end
local p = self:getPlayerById(pid)
local str = '<font color="%s"><b>%s</b></font>'
if p.general == "anjiang" and (p.deputyGeneral == "anjiang"
or not p.deputyGeneral) then
local ret = Fk:translate("seat#" .. p.seat)
return string.format(str, color, ret)
end
local ret = p.general
ret = Fk:translate(ret)
if p.deputyGeneral and p.deputyGeneral ~= "" then
ret = ret .. "/" .. Fk:translate(p.deputyGeneral)
end
ret = string.format(str, color, ret)
return ret
end
local from = getPlayerStr(data.from, "#0C8F0C")
local to = data.to or Util.DummyTable
local to_str = {}
for _, id in ipairs(to) do
table.insert(to_str, getPlayerStr(id, "#CC3131"))
end
to = table.concat(to_str, ", ")
local card = data.card or Util.DummyTable
local allUnknown = true
local unknownCount = 0
for _, id in ipairs(card) do
if id ~= -1 then
allUnknown = false
else
unknownCount = unknownCount + 1
end
end
if allUnknown then
card = ""
else
local card_str = {}
for _, id in ipairs(card) do
table.insert(card_str, Fk:getCardById(id, true):toLogString())
end
if unknownCount > 0 then
table.insert(card_str, Fk:translate("unknown_card")
.. unknownCount == 1 and "x" .. unknownCount or "")
end
card = table.concat(card_str, ", ")
end
local function parseArg(arg)
arg = arg or ""
arg = Fk:translate(arg)
arg = string.format('<font color="%s"><b>%s</b></font>', nocolor and "white" or "#0598BC", arg)
return arg
end
local arg = parseArg(data.arg)
local arg2 = parseArg(data.arg2)
local arg3 = parseArg(data.arg3)
local log = Fk:translate(data.type)
log = string.gsub(log, "%%from", from)
log = string.gsub(log, "%%to", to)
log = string.gsub(log, "%%card", card)
log = string.gsub(log, "%%arg2", arg2)
log = string.gsub(log, "%%arg3", arg3)
log = string.gsub(log, "%%arg", arg)
return colorConvert(log)
end
local function processPrompt(prompt)
local data = prompt:split(":")
local room = Fk:currentRoom()
local raw = Fk:translate(data[1]);
local src = tonumber(data[2]);
local dest = tonumber(data[3]);
if src then raw = raw:gsub("%%src", Fk:translate(room:getPlayerById(src).general)) end
if dest then raw = raw:gsub("%%dest", Fk:translate(room:getPlayerById(dest).general)) end
if data[5] then raw = raw:gsub("%%arg2", Fk:translate(data[5])) end
if data[4] then raw = raw:gsub("%%arg", Fk:translate(data[4])) end
return colorConvert(raw)
end
--- separated moves to many moves(one card per move)
---@param moves CardsMoveStruct[]
local function separateMoves(moves)
local ret = {} ---@type CardsMoveInfo[]
for _, move in ipairs(moves) do
for _, info in ipairs(move.moveInfo) do
table.insert(ret, {
ids = {info.cardId},
from = move.from,
to = move.to,
toArea = move.toArea,
fromArea = info.fromArea,
moveReason = move.moveReason,
specialName = move.specialName,
fromSpecialName = info.fromSpecialName,
proposer = move.proposer,
})
end
end
return ret
end
--- merge separated moves that information is the same
local function mergeMoves(moves)
local ret = {}
local temp = {}
for _, move in ipairs(moves) do
local info = string.format("%q,%q,%q,%q,%s,%s,%q",
move.from, move.to, move.fromArea, move.toArea,
move.specialName, move.fromSpecialName, move.proposer)
if temp[info] == nil then
temp[info] = {
ids = {},
from = move.from,
to = move.to,
fromArea = move.fromArea,
toArea = move.toArea,
moveReason = move.moveReason,
specialName = move.specialName,
fromSpecialName = move.fromSpecialName,
proposer = move.proposer,
}
end
table.insert(temp[info].ids, move.ids[1])
end
for _, v in pairs(temp) do
table.insert(ret, v)
end
return ret
end
local function sendMoveCardLog(move)
local client = Fk:currentRoom() ---@class Client
if #move.ids == 0 then return end
local hidden = table.contains(move.ids, -1)
local msgtype
if move.toArea == Card.PlayerHand then
if move.fromArea == Card.PlayerSpecial then
print(parseMsg({
type = "$GetCardsFromPile",
from = move.to,
arg = move.fromSpecialName,
arg2 = #move.ids,
card = move.ids,
}))
elseif move.fromArea == Card.DrawPile then
print(parseMsg({
type = "$DrawCards",
from = move.to,
card = move.ids,
arg = #move.ids,
}))
elseif move.fromArea == Card.Processing then
print(parseMsg({
type = "$GotCardBack",
from = move.to,
card = move.ids,
arg = #move.ids,
}))
elseif move.fromArea == Card.DiscardPile then
print(parseMsg({
type = "$RecycleCard",
from = move.to,
card = move.ids,
arg = #move.ids,
}))
elseif move.from then
print(parseMsg({
type = "$MoveCards",
from = move.from,
to = { move.to },
arg = #move.ids,
card = move.ids,
}))
else
print(parseMsg({
type = "$PreyCardsFromPile",
from = move.to,
card = move.ids,
arg = #move.ids,
}))
end
elseif move.toArea == Card.PlayerEquip then
print(parseMsg({
type = "$InstallEquip",
from = move.to,
card = move.ids,
}))
elseif move.toArea == Card.PlayerJudge then
if move.from ~= move.to and move.fromArea == Card.PlayerJudge then
print(parseMsg({
type = "$LightningMove",
from = move.from,
to = { move.to },
card = move.ids,
}))
elseif move.from then
print(parseMsg({
type = "$PasteCard",
from = move.from,
to = { move.to },
card = move.ids,
}))
end
elseif move.toArea == Card.PlayerSpecial then
print(parseMsg({
type = "$AddToPile",
arg = move.specialName,
arg2 = #move.ids,
from = move.to,
card = move.ids,
}))
elseif move.fromArea == Card.PlayerEquip then
print(parseMsg({
type = "$UninstallEquip",
from = move.from,
card = move.ids,
}))
-- elseif move.toArea == Card.Processing then
-- nop
elseif move.from and move.toArea == Card.DrawPile then
msgtype = hidden and "$PutCard" or "$PutKnownCard"
print(parseMsg({
type = msgtype,
from = move.from,
card = move.ids,
arg = #move.ids,
}))
elseif move.toArea == Card.DiscardPile then
if move.moveReason == fk.ReasonDiscard then
if move.proposer and move.proposer ~= move.from then
print(parseMsg({
type = "$DiscardOther",
from = move.from,
to = {move.proposer},
card = move.ids,
arg = #move.ids,
}))
else
print(parseMsg({
type = "$DiscardCards",
from = move.from,
card = move.ids,
arg = #move.ids,
}))
end
elseif move.moveReason == fk.ReasonPutIntoDiscardPile then
print(parseMsg({
type = "$PutToDiscard",
card = move.ids,
arg = #move.ids,
}))
end
-- elseif move.toArea == Card.Void then
-- nop
end
end
function ServerPlayer:initialize(id)
self.id = id
self.screenName = "player" .. id
self.state = fk.Player_Online
self.died = false
self._busy = false
self._thinking = false
end
function ServerPlayer:getId() return self.id end
function ServerPlayer:setId(id) self.id = id end
function ServerPlayer:getScreenName() return self.screenName end
function ServerPlayer:getAvatar() return "zhouyu" end
function ServerPlayer:getState() return self.state end
function ServerPlayer:setState(state) self.state = state end
function ServerPlayer:isDied() return self.died end
function ServerPlayer:setDied(died) self.died = died end
local function tr(str)
return string.format("%s(%s)", Fk:translate(str), str)
end
local function trcid(cid)
local card = Fk:getCardById(cid)
return colorConvert(card:toLogString()) .. "(" .. cid .. ")"
end
local function help_rp_yn()
print(fk.GRAY .." (reply格式n或N表示取消其余确定如reply n)" .. fk.RST)
end
local function help_rp_choices()
print(fk.GRAY .." (reply格式直接输入文本有多个则用空格分隔reply kill)" .. fk.RST)
end
local request_processors = {
["AskForGeneral"] = function(j)
local data = json.decode(j)
io.write(string.format("请选择 %d 名武将: ", data[2]))
for _, g in ipairs(data[1]) do
io.write(tr(g) .. " ")
end
io.write("\n")
help_rp_choices()
end,
["PlayCard"] = function()
print("出牌阶段,请进行操作")
end,
["AskForSkillInvoke"] = function(j)
local data = json.decode(j)
local skill = data[1]
local prompt = data[2]
if prompt then prompt = processPrompt(prompt)
else prompt = string.format("你是否发动技能 %s", tr(skill)) end
print(prompt)
help_rp_yn()
end,
["AskForUseCard"] = function(j)
local cardname, pattern, prompt, _, extra_data, disabledSkillNames =
table.unpack(json.decode(j))
if prompt then prompt = processPrompt(prompt)
else prompt = string.format("请使用卡牌 %s", tr(cardname)) end
print(prompt)
end,
["AskForUseActiveSkill"] = function(j)
local skill_name, prompt, cancelable, extra_data =
table.unpack(json.decode(j))
if prompt then prompt = processPrompt(prompt)
else prompt = string.format("请使用技能 %s", tr(skill_name)) end
print(prompt)
end,
}
function ServerPlayer:doRequest(cmd, j)
if self.id == 1 then
io.write(fk.YELLOW .. "[!] " .. fk.RST)
if request_processors[cmd] then
request_processors[cmd](j)
else
print(cmd, j)
end
end
end
local cmd_help = function()
io.write(""
.. fk.BLUE .." <回车>".. fk.CARET .."重复执行上一条命令\n"
.. fk.BLUE .." help/h".. fk.CARET .."查看这条帮助\n"
.. fk.BLUE .." dbg".. fk.CARET .."使用Debugger\n"
.. fk.BLUE .." reply/rp".. fk.CARET .."发送答复需手搓JSON除非有特殊提示注意无合法性检测\n"
.. fk.GRAY .." -------------------------------\n" .. fk.RST
.. fk.BLUE .." room/r".. fk.CARET .."查看房间概况\n"
.. fk.BLUE .." player/p".. fk.CARET .."查看玩家概况参数为玩家id默认1\n"
.. fk.BLUE .." skill/s".. fk.CARET .."查看技能描述\n"
)
end
local reply_processors = {
["AskForGeneral"] = function(args)
return json.encode(args)
end,
["AskForSkillInvoke"] = function(args)
local ret = args[1]
if ret == 'n' or ret == 'N' then
return ''
end
return '1'
end,
}
local cmd_reply = function(args)
if #args == 0 then return "" end
local room = Fk:currentRoom()
local player = room:getPlayerById(1)
local command = player.ai_data.command
if reply_processors[command] then
return reply_processors[command](args)
else
return args[1]
end
end
local function getRoleStr(str)
if str == "lord" then
return fk.RED .. fk.BOLD .. "" .. fk.RST
elseif str == "loyalist" then
return fk.YELLOW .. fk.BOLD .. "" .. fk.RST
elseif str == "rebel" then
return fk.GREEN .. fk.BOLD .. "" .. fk.RST
elseif str == "renegade" then
return fk.BLUE .. fk.BOLD .. "" .. fk.RST
end
end
local function writeCardList(cidlist)
for _, id in ipairs(cidlist) do
io.write(trcid(id))
io.write(" ")
end
end
local cmd_room = function()
local room = Fk:currentRoom()
if not room.players[3].shield then return end
printf("第%d轮 牌堆剩%d张", room.tag['RoundCount'], #room.draw_pile)
print("\n玩家列表:")
for _, p in ipairs(room.players) do
io.write(string.format("%s%d ID=%d %s %s %d|%d/%d %d牌",
p.id == 1 and "*" or "", p.seat, p.id, getRoleStr(p.role),
(p.dead and fk.GRAY or fk.GREEN) .. Fk:translate(p.general) .. fk.RST,
p.shield, p.hp, p.maxHp, #p.player_cards[Player.Hand]))
if #p.player_cards[Player.Equip] > 0 then io.write(" 有装备") end
if #p.player_cards[Player.Judge] > 0 then io.write(" 有判定") end
io.write("\n")
end
--[[
print("\n摸牌堆:")
writeCardList(room.draw_pile)
print("\n弃牌堆:")
writeCardList(room.discard_pile)
print("\nVoid牌堆")
writeCardList(room.void)
--]]
end
local cmd_player = function(args)
local room = Fk:currentRoom()
if #args == 0 then table.insert(args, "1") end
for _, sid in ipairs(args) do
local p = room:getPlayerById(tonumber(sid))
io.write(fk.BOLD .. tostring(p) .. fk.RST .. " " .. getRoleStr(p.role))
if p.general and p.general ~= "" then
io.write(" " .. tr(p.general))
else
io.write("\n"); return
end
if p.deputyGeneral and p.deputyGeneral ~= "" then
io.write("/" .. tr(p.deputyGeneral))
end
io.write(string.format(" HP: %d|%d/%d", p.shield, p.hp, p.maxHp))
if p.dead then io.write(" 已死亡") end
io.write("\n")
if #p.player_cards[Player.Hand] > 0 then
io.write(string.format("共%d张手牌: ", #p.player_cards[Player.Hand]))
writeCardList(p.player_cards[Player.Hand])
io.write("\n")
else
print("没有手牌")
end
if #p.player_cards[Player.Equip] > 0 then
io.write("装备区内的牌: ")
writeCardList(p.player_cards[Player.Equip])
io.write("\n")
end
if #p.player_cards[Player.Judge] > 0 then
io.write("判定区内的牌: ")
writeCardList(p.player_cards[Player.Judge])
io.write("\n")
end
io.write("技能:")
for _, s in ipairs(p.player_skills) do
if s.visible then io.write(tr(s.name) .. " ") end
end
io.write("\n")
end
end
local cmd_skill = function(args)
for _, s in ipairs(args) do
print(fk.BOLD .. tr(s) .. fk.RST)
print(colorConvert(Fk:getDescription(s)))
end
end
local cmd_card = function(args)
for _, s in ipairs(args) do
local cid = tonumber(s)
if not cid then return end
local c = Fk:getCardById(cid, true)
if not c then return end
print(fk.BOLD .. trcid(cid) .. fk.RST)
print(colorConvert(Fk:getDescription(c.name)))
end
end
local command_table = {
help = cmd_help, h = cmd_help,
dbg = function() dbg() end,
reply = cmd_reply, rp = cmd_reply,
room = cmd_room, r = cmd_room,
player = cmd_player, p = cmd_player,
skill = cmd_skill, s = cmd_skill,
card = cmd_card, c = cmd_card,
}
local last_cmd = "help"
function ServerPlayer:waitForReply()
-- dbg() 时的便利变量
local room = RoomInstance
local logic = room.logic
local player = room:getPlayerById(self.id)
if self.id == 1 then
while true do
io.write(string.char(27) .. "[95m(FkTest) " .. fk.RST)
io.flush()
local line = io.read()
if line == nil then break end -- Ctrl-D
local args = line:split(" ")
for i = #args, 1, -1 do
if args[i] == "" then table.remove(args, i) end
end
local command = table.remove(args, 1)
if command == nil then
command = last_cmd
else
last_cmd = command
end
local f = command_table[command]
if f then
local ret = f(args)
if ret then return ret end
elseif command then
print(fk.RED .. "unknown command '" .. command .. "'" .. fk.RST)
end
end
end
return ""
end
function ServerPlayer:doNotify(cmd, j)
if self.id ~= 1 then
return
end
if cmd == "GameLog" then
print(parseMsg(json.decode(j)))
elseif cmd == "MoveCards" then
local raw_moves = json.decode(j)
local separated = separateMoves(raw_moves)
local merged = mergeMoves(separated)
for _, move in ipairs(merged) do
sendMoveCardLog(move)
end
elseif cmd == "GameOver" then
print(cmd, j)
end
end
function ServerPlayer:busy() return self._busy end
function ServerPlayer:setBusy(b) self._busy = b end
function ServerPlayer:thinking() return self._thinking end
function ServerPlayer:setThinking(t) self._thinking = t end
function ServerPlayer:emitKick()
self.state = fk.Player_Offline
end
function ServerPlayer:getGameData()
return {[0]=0,0,0,0,at=function(t,k)return t[k]end}
end
return ServerPlayer