-- 仿效 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('', string.char(27) .. "[34;1m") log = log:gsub('', string.char(27) .. "[32;1m") log = log:gsub('', string.char(27) .. "[31;1m") log = log:gsub('', string.char(27) .. "[31;1m") log = log:gsub('', string.char(27) .. "[0;1m") log = log:gsub('', string.char(27) .. "[34m") log = log:gsub('', string.char(27) .. "[34m") log = log:gsub('', string.char(27) .. "[32m") log = log:gsub('', string.char(27) .. "[32m") log = log:gsub('', string.char(27) .. "[31m") log = log:gsub('', string.char(27) .. "[31m") log = log:gsub('', string.char(27) .. "[90m") log = log:gsub("", fk.BOLD) log = log:gsub("", fk.RST) log = log:gsub("", fk.RST) log = log:gsub("", fk.BOLD) log = log:gsub("", fk.RST) log = log:gsub("
", "\n") log = log:gsub("
", "\n") log = log:gsub("
", "\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 = '%s' 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('%s', 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