FreeKill/lua/server/serverplayer.lua

966 lines
26 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- SPDX-License-Identifier: GPL-3.0-or-later
---@class ServerPlayer : Player
---@field public serverplayer fk.ServerPlayer
---@field public room Room
---@field public next ServerPlayer
---@field public request_data string
---@field public client_reply string
---@field public default_reply string
---@field public reply_ready boolean
---@field public reply_cancel boolean
---@field public phases Phase[]
---@field public skipped_phases Phase[]
---@field public phase_state table[]
---@field public phase_index integer
---@field public role_shown boolean
---@field private _fake_skills Skill[]
---@field public prelighted_skills Skill[]
---@field private _timewaste_count integer
---@field public ai AI
---@field public ai_data any
local ServerPlayer = Player:subclass("ServerPlayer")
function ServerPlayer:initialize(_self)
Player.initialize(self)
self.serverplayer = _self -- 控制者
self._splayer = _self -- 真正在玩的玩家
self._observers = { _self } -- "旁观"中的玩家,然而不包括真正的旁观者
self.id = _self:getId()
self.room = nil
-- Below are for doBroadcastRequest
self.request_data = ""
self.client_reply = ""
self.default_reply = ""
self.reply_ready = false
self.reply_cancel = false
self.phases = {}
self.skipped_phases = {}
self._fake_skills = {}
self.prelighted_skills = {}
self._prelighted_skills = {}
self._timewaste_count = 0
self.ai = RandomAI:new(self)
end
---@param command string
---@param jsonData string
function ServerPlayer:doNotify(command, jsonData)
for _, p in ipairs(self._observers) do
p:doNotify(command, jsonData)
end
local room = self.room
for _, t in ipairs(room.observers) do
local id, p = table.unpack(t)
if id == self.id and room.room:hasObserver(p) then
p:doNotify(command, jsonData)
end
end
end
--- Send a request to client, and allow client to reply within *timeout* seconds.
---
--- *timeout* must not be negative. If nil, room.timeout is used.
---@param command string
---@param jsonData string
---@param timeout integer|nil
function ServerPlayer:doRequest(command, jsonData, timeout)
self.client_reply = ""
self.reply_ready = false
self.reply_cancel = false
if self.serverplayer:busy() then
self.room.request_queue[self.serverplayer] = self.room.request_queue[self.serverplayer] or {}
table.insert(self.room.request_queue[self.serverplayer], { self.id, command, jsonData, timeout })
return
end
self.room.request_self[self.serverplayer:getId()] = self.id
if not table.contains(self._observers, self.serverplayer) then
self.serverplayer:doNotify("StartChangeSelf", tostring(self.id))
end
timeout = timeout or self.room.timeout
self.serverplayer:setBusy(true)
self.ai_data = {
command = command,
jsonData = jsonData,
}
self.serverplayer:doRequest(command, jsonData, timeout)
end
local function _waitForReply(player, timeout)
local result
local start = os.getms()
local state = player.serverplayer:getState()
player.request_timeout = timeout
player.request_start = start
if state ~= fk.Player_Online then
if player.room.hasSurrendered then
return "__cancel"
end
if state ~= fk.Player_Robot then
player.room:checkNoHuman()
player.room:delay(500)
return "__cancel"
end
-- Let AI make reply. First handle request
-- coroutine.yield("__handleRequest", 0)
player.room:checkNoHuman()
player.ai:readRequestData()
local reply = player.ai:makeReply()
if reply == "" then reply = "__cancel" end
return reply
end
while true do
player.serverplayer:setThinking(true)
result = player.serverplayer:waitForReply(0)
if result ~= "__notready" then
player._timewaste_count = 0
player.serverplayer:setThinking(false)
return result
end
local rest = timeout * 1000 - (os.getms() - start) / 1000
if timeout and rest <= 0 then
if timeout >= 15 then
player._timewaste_count = player._timewaste_count + 1
end
player.serverplayer:setThinking(false)
if player._timewaste_count >= 3 then
player._timewaste_count = 0
player.serverplayer:emitKick()
end
return ""
end
if player.room.hasSurrendered then
player.serverplayer:setThinking(false)
return ""
end
coroutine.yield("__handleRequest", rest)
end
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 integer @ seconds to wait
---@return string @ JSON data
function ServerPlayer:waitForReply(timeout)
local result = _waitForReply(self, timeout)
local sid = self.serverplayer:getId()
local id = self.id
if self.room.request_self[sid] ~= id then
result = ""
end
self.request_data = ""
self.client_reply = result
if result == "__cancel" then
result = ""
self.reply_cancel = true
self.serverplayer:setBusy(false)
self.serverplayer:setThinking(false)
end
if result ~= "" then
self.reply_ready = true
self.serverplayer:setBusy(false)
self.serverplayer:setThinking(false)
end
local queue = self.room.request_queue[self.serverplayer]
if queue and #queue > 0 and not self.serverplayer:busy() then
local i, c, j, t = table.unpack(table.remove(queue, 1))
self.room:getPlayerById(i):doRequest(c, j, t)
end
return result
end
---@param player ServerPlayer
---@param observe bool
function ServerPlayer:marshal(player, observe)
local room = self.room
if not room.game_started then
-- If game does not starts, that mean we are entering room that
-- all players are choosing their generals.
-- Note that when we are in this function, the main thread must be
-- calling delay() or waiting for reply.
if self.role_shown then
room:notifyProperty(player, self, "role")
end
return
end
room:notifyProperty(player, self, "maxHp")
room:notifyProperty(player, self, "hp")
room:notifyProperty(player, self, "shield")
room:notifyProperty(player, self, "gender")
room:notifyProperty(player, self, "kingdom")
if self.dead then
room:notifyProperty(player, self, "dead")
room:notifyProperty(player, self, "role")
else
room:notifyProperty(player, self, "seat")
room:notifyProperty(player, self, "phase")
end
if not self.faceup then
room:notifyProperty(player, self, "faceup")
end
if self.chained then
room:notifyProperty(player, self, "chained")
end
local card_moves = {}
if #self.player_cards[Player.Hand] ~= 0 then
local info = {}
for _, i in ipairs(self.player_cards[Player.Hand]) do
table.insert(info, { cardId = i, fromArea = Card.DrawPile })
end
local move = {
moveInfo = info,
to = self.id,
toArea = Card.PlayerHand
}
table.insert(card_moves, move)
end
if #self.player_cards[Player.Equip] ~= 0 then
local info = {}
for _, i in ipairs(self.player_cards[Player.Equip]) do
table.insert(info, { cardId = i, fromArea = Card.DrawPile })
end
local move = {
moveInfo = info,
to = self.id,
toArea = Card.PlayerEquip
}
table.insert(card_moves, move)
end
if #self.player_cards[Player.Judge] ~= 0 then
local info = {}
for _, i in ipairs(self.player_cards[Player.Judge]) do
table.insert(info, { cardId = i, fromArea = Card.DrawPile })
end
local move = {
moveInfo = info,
to = self.id,
toArea = Card.PlayerJudge
}
table.insert(card_moves, move)
end
for k, v in pairs(self.special_cards) do
local info = {}
for _, i in ipairs(v) do
table.insert(info, { cardId = i, fromArea = Card.DrawPile })
end
local move = {
moveInfo = info,
to = self.id,
toArea = Card.PlayerSpecial,
specialName = k,
specialVisible = self == player,
}
table.insert(card_moves, move)
end
if #card_moves > 0 then
room:notifyMoveCards({ player }, card_moves, observe and self.seat == 1)
end
for k, v in pairs(self.mark) do
player:doNotify("SetPlayerMark", json.encode{self.id, k, v})
end
for _, s in ipairs(self.player_skills) do
player:doNotify("AddSkill", json.encode{self.id, s.name})
end
for k, v in pairs(self.cardUsedHistory) do
if v[1] > 0 then
player:doNotify("AddCardUseHistory", json.encode{k, v[1]})
end
end
for k, v in pairs(self.skillUsedHistory) do
if v[4] > 0 then
player:doNotify("SetSkillUseHistory", json.encode{self.id, k, v[1], 1})
player:doNotify("SetSkillUseHistory", json.encode{self.id, k, v[2], 2})
player:doNotify("SetSkillUseHistory", json.encode{self.id, k, v[3], 3})
player:doNotify("SetSkillUseHistory", json.encode{self.id, k, v[4], 4})
end
end
if self.role_shown then
room:notifyProperty(player, self, "role")
end
if #self.sealedSlots > 0 then
room:notifyProperty(player, self, "sealedSlots")
end
end
function ServerPlayer:reconnect()
local room = self.room
self.serverplayer:setState(fk.Player_Online)
self:doNotify("Setup", json.encode{
self.id,
self._splayer:getScreenName(),
self._splayer:getAvatar(),
})
self:doNotify("EnterLobby", "")
self:doNotify("EnterRoom", json.encode{
#room.players, room.timeout, room.settings,
})
self:doNotify("StartGame", "")
room:notifyProperty(self, self, "role")
-- send player data
for _, p in ipairs(room:getOtherPlayers(self, false, true)) do
self:doNotify("AddPlayer", json.encode{
p.id,
p._splayer:getScreenName(),
p._splayer:getAvatar(),
})
end
local player_circle = {}
for i = 1, #room.players do
table.insert(player_circle, room.players[i].id)
end
self:doNotify("ArrangeSeats", json.encode(player_circle))
for _, p in ipairs(room.players) do
room:notifyProperty(self, p, "general")
room:notifyProperty(self, p, "deputyGeneral")
p:marshal(self)
end
self:doNotify("UpdateDrawPile", #room.draw_pile)
self:doNotify("UpdateRoundNum", room:getTag("RoundCount") or 0)
room:broadcastProperty(self, "state")
end
function ServerPlayer:isAlive()
return self.dead == false
end
function ServerPlayer:turnOver()
if self.room.logic:trigger(fk.BeforeTurnOver, self) then
return
end
self.faceup = not self.faceup
self.room:broadcastProperty(self, "faceup")
self.room:sendLog{
type = "#TurnOver",
from = self.id,
arg = self.faceup and "face_up" or "face_down",
}
self.room.logic:trigger(fk.TurnedOver, self)
end
function ServerPlayer:showCards(cards)
cards = Card:getIdList(cards)
for _, id in ipairs(cards) do
Fk:filterCard(id, self)
end
local room = self.room
room:sendLog{
type = "#ShowCard",
from = self.id,
card = cards,
}
room:doBroadcastNotify("ShowCard", json.encode{
from = self.id,
cards = cards,
})
room:sendFootnote(cards, {
type = "##ShowCard",
from = self.id,
})
room.logic:trigger(fk.CardShown, self, { cardIds = cards })
end
local phase_name_table = {
[Player.Judge] = "phase_judge",
[Player.Draw] = "phase_draw",
[Player.Play] = "phase_play",
[Player.Discard] = "phase_discard",
}
---@param from_phase Phase
---@param to_phase Phase
function ServerPlayer:changePhase(from_phase, to_phase)
local room = self.room
local logic = room.logic
self.phase = Player.PhaseNone
local phase_change = {
from = from_phase,
to = to_phase
}
local skip = logic:trigger(fk.EventPhaseChanging, self, phase_change)
if skip and to_phase ~= Player.NotActive then
self.phase = from_phase
return true
end
self.phase = to_phase
room:broadcastProperty(self, "phase")
if #self.phases > 0 then
table.remove(self.phases, 1)
end
GameEvent(GameEvent.Phase, self, self.phase):exec()
return false
end
function ServerPlayer:gainAnExtraPhase(phase, delay)
local room = self.room
delay = (delay == nil) and true or delay
if delay then
local logic = room.logic
local turn = logic:getCurrentEvent():findParent(GameEvent.Phase, true)
if turn then
turn:addExitFunc(function() self:gainAnExtraPhase(phase, false) end)
return
end
end
local current = self.phase
self.phase = phase
room:broadcastProperty(self, "phase")
room:sendLog{
type = "#GainAnExtraPhase",
from = self.id,
arg = phase_name_table[phase],
}
GameEvent(GameEvent.Phase, self, self.phase):exec()
self.phase = current
room:broadcastProperty(self, "phase")
end
---@param phase_table Phase[]|nil
function ServerPlayer:play(phase_table)
phase_table = phase_table or {}
if #phase_table > 0 then
if not table.contains(phase_table, Player.NotActive) then
table.insert(phase_table, Player.NotActive)
end
else
phase_table = {
Player.RoundStart, Player.Start,
Player.Judge, Player.Draw, Player.Play, Player.Discard,
Player.Finish, Player.NotActive,
}
end
self.phases = phase_table
self.phase_state = {}
local phases = self.phases
local phase_state = self.phase_state
local room = self.room
for i = 1, #phases do
phase_state[i] = {
phase = phases[i],
skipped = self.skipped_phases[phases[i]] or false
}
end
for i = 1, #phases do
if self.dead then
self:changePhase(self.phase, Player.NotActive)
break
end
self.phase_index = i
local phase_change = {
from = self.phase,
to = phases[i]
}
local logic = self.room.logic
self.phase = Player.PhaseNone
local skip = phase_state[i].skipped
if not skip then
skip = logic:trigger(fk.EventPhaseChanging, self, phase_change)
end
phases[i] = phase_change.to
phase_state[i].phase = phases[i]
self.phase = phases[i]
room:broadcastProperty(self, "phase")
local cancel_skip = true
if phases[i] ~= Player.NotActive and (skip) then
cancel_skip = logic:trigger(fk.EventPhaseSkipping, self)
end
if (not skip) or (cancel_skip) then
GameEvent(GameEvent.Phase, self, self.phase):exec()
else
room:sendLog{
type = "#PhaseSkipped",
from = self.id,
arg = phase_name_table[self.phase],
}
end
end
end
---@param phase Phase
function ServerPlayer:skip(phase)
if not table.contains({
Player.Judge,
Player.Draw,
Player.Play,
Player.Discard
}, phase) then
return
end
self.skipped_phases[phase] = true
for _, t in ipairs(self.phase_state) do
if t.phase == phase then
t.skipped = true
end
end
end
function ServerPlayer:endPlayPhase()
self._play_phase_end = true
-- TODO: send log
end
function ServerPlayer:gainAnExtraTurn(delay)
local room = self.room
delay = (delay == nil) and true or delay
if delay then
local logic = room.logic
local turn = logic:getCurrentEvent():findParent(GameEvent.Turn, true)
if turn then
turn:addExitFunc(function() self:gainAnExtraTurn(false) end)
return
end
end
room:sendLog{
type = "#GainAnExtraTurn",
from = self.id
}
local current = room.current
room.current = self
GameEvent(GameEvent.Turn, self):exec()
room.current = current
end
function ServerPlayer:drawCards(num, skillName, fromPlace)
return self.room:drawCards(self, num, skillName, fromPlace)
end
---@param pile_name string
---@param card integer|Card
---@param visible boolean
---@param skillName string|nil
function ServerPlayer:addToPile(pile_name, card, visible, skillName)
local room = self.room
room:moveCardTo(card, Card.PlayerSpecial, self, fk.ReasonJustMove, skillName, pile_name, visible)
end
function ServerPlayer:bury()
self:setCardUseHistory("")
self:setSkillUseHistory("")
self:throwAllCards()
self:throwAllMarks()
self:clearPiles()
self:reset()
end
function ServerPlayer:throwAllCards(flag)
local cardIds = {}
flag = flag or "hej"
if string.find(flag, "h") then
table.insertTable(cardIds, self.player_cards[Player.Hand])
end
if string.find(flag, "e") then
table.insertTable(cardIds, self.player_cards[Player.Equip])
end
if string.find(flag, "j") then
table.insertTable(cardIds, self.player_cards[Player.Judge])
end
self.room:throwCard(cardIds, "", self)
end
function ServerPlayer:throwAllMarks()
for name, _ in pairs(self.mark) do
self.room:setPlayerMark(self, name, 0)
end
end
function ServerPlayer:clearPiles()
local cardIds = {}
for _, ids in pairs(self.special_cards) do
table.insertTable(cardIds, ids)
end
self.room:moveCardTo(cardIds, Card.DiscardPile, nil, fk.ReasonPutIntoDiscardPile, "", nil, true)
end
function ServerPlayer:addVirtualEquip(card)
Player.addVirtualEquip(self, card)
self.room:doBroadcastNotify("AddVirtualEquip", json.encode{
player = self.id,
name = card.name,
subcards = card.subcards,
})
end
function ServerPlayer:removeVirtualEquip(cid)
local ret = Player.removeVirtualEquip(self, cid)
self.room:doBroadcastNotify("RemoveVirtualEquip", json.encode{
player = self.id,
id = cid,
})
return ret
end
function ServerPlayer:addCardUseHistory(cardName, num)
Player.addCardUseHistory(self, cardName, num)
self:doNotify("AddCardUseHistory", json.encode{cardName, num})
end
function ServerPlayer:setCardUseHistory(cardName, num, scope)
Player.setCardUseHistory(self, cardName, num, scope)
self:doNotify("SetCardUseHistory", json.encode{cardName, num, scope})
end
function ServerPlayer:addSkillUseHistory(cardName, num)
Player.addSkillUseHistory(self, cardName, num)
self.room:doBroadcastNotify("AddSkillUseHistory", json.encode{self.id, cardName, num})
end
function ServerPlayer:setSkillUseHistory(cardName, num, scope)
Player.setSkillUseHistory(self, cardName, num, scope)
self.room:doBroadcastNotify("SetSkillUseHistory", json.encode{self.id, cardName, num, scope})
end
---@param chained boolean
function ServerPlayer:setChainState(chained)
local room = self.room
if room.logic:trigger(fk.BeforeChainStateChange, self) then
return
end
self.chained = chained
room:broadcastProperty(self, "chained")
room:sendLog{
type = "#ChainStateChange",
from = self.id,
arg = self.chained and "chained" or "un-chained"
}
room:delay(150)
room:broadcastPlaySound("./audio/system/chain")
room.logic:trigger(fk.ChainStateChanged, self)
end
function ServerPlayer:reset()
self.room:sendLog{
type = "#ChainStateChange",
from = self.id,
arg = "reset-general"
}
self:setChainState(false)
if not self.faceup then self:turnOver() end
end
--@param from ServerPlayer
---@param tos ServerPlayer[]
---@param skillName string
---@param initialCard Card|nil
---@return PindianStruct
function ServerPlayer:pindian(tos, skillName, initialCard)
local pindianData = { from = self, tos = tos, reason = skillName, fromCard = initialCard, results = {} }
self.room:pindian(pindianData)
return pindianData
end
--- 播放技能的语音。
---@param skill_name string @ 技能名
---@param index integer | nil @ 语音编号,默认为-1也就是随机播放
function ServerPlayer:broadcastSkillInvoke(skill_name, index)
index = index or -1
self.room:sendLogEvent("PlaySkillSound", {
name = skill_name,
i = index,
general = self.general,
deputy = self.deputyGeneral,
})
end
-- Hegemony func
---@param skill Skill
function ServerPlayer:addFakeSkill(skill)
assert(type(skill) == "string" or skill:isInstanceOf(Skill))
if type(skill) == "string" then
skill = Fk.skills[skill]
end
if table.contains(self._fake_skills, skill) then return end
table.insert(self._fake_skills, skill)
for _, s in ipairs(skill.related_skills) do
-- if s.main_skill == skill then -- TODO: need more detailed
table.insert(self._fake_skills, s)
-- end
end
-- TODO
self:doNotify("AddSkill", json.encode{ self.id, skill.name, true })
end
---@param skill Skill
function ServerPlayer:loseFakeSkill(skill)
assert(type(skill) == "string" or skill:isInstanceOf(Skill))
if type(skill) == "string" then
skill = Fk.skills[skill]
end
if not table.contains(self._fake_skills, skill) then return end
table.removeOne(self._fake_skills, skill)
for _, s in ipairs(skill.related_skills) do
table.removeOne(self._fake_skills, s)
end
-- TODO
self:doNotify("LoseSkill", json.encode{ self.id, skill.name, true })
end
function ServerPlayer:isFakeSkill(skill)
if type(skill) == "string" then skill = Fk.skills[skill] end
assert(skill:isInstanceOf(Skill))
return table.contains(self._fake_skills, skill)
end
---@param skill string | Skill
---@param isPrelight bool
function ServerPlayer:prelightSkill(skill, isPrelight)
if type(skill) == "string" then skill = Fk.skills[skill] end
assert(skill:isInstanceOf(Skill))
if not self._prelighted_skills[skill] and not self:hasSkill(skill) then
self._prelighted_skills[skill] = true
-- to attach skill to room
self:addSkill(skill)
self:loseSkill(skill)
end
if isPrelight then
-- self:addSkill(skill)
table.insert(self.prelighted_skills, skill)
for _, s in ipairs(skill.related_skills) do
table.insert(self.prelighted_skills, s)
end
else
-- self:loseSkill(skill)
table.removeOne(self.prelighted_skills, skill)
for _, s in ipairs(skill.related_skills) do
table.removeOne(self.prelighted_skills, s)
end
end
self:doNotify("PrelightSkill", json.encode{ skill.name, isPrelight })
end
---@param isDeputy bool
---@param no_trigger bool
function ServerPlayer:revealGeneral(isDeputy, no_trigger)
local room = self.room
local generalName
if isDeputy then
if self.deputyGeneral ~= "anjiang" then return end
generalName = self:getMark("__heg_deputy")
else
if self.general ~= "anjiang" then return end
generalName = self:getMark("__heg_general")
end
local general = Fk.generals[generalName] or Fk.generals["blank_shibing"]
for _, s in ipairs(general:getSkillNameList()) do
local skill = Fk.skills[s]
self:loseFakeSkill(skill)
end
local ret = true
if not ((isDeputy and self.general ~= "anjiang") or (not isDeputy and self.deputyGeneral ~= "anjiang")) then
local other = Fk.generals[self:getMark(isDeputy and "__heg_general" or "__heg_deputy")] or Fk.generals["blank_shibing"]
for _, sname in ipairs(other:getSkillNameList()) do
local s = Fk.skills[sname]
if s.frequency == Skill.Compulsory and s.relate_to_place ~= (isDeputy and "m" or "d") then
ret = false
break
end
end
end
if ret then
self:loseFakeSkill("reveal_skill")
end
local oldKingdom = self.kingdom
room:changeHero(self, generalName, false, isDeputy)
if oldKingdom ~= "wild" then
local kingdom = general.kingdom
self.kingdom = kingdom
if oldKingdom == "unknown" and #table.filter(room:getOtherPlayers(self, false, true),
function(p)
return p.kingdom == kingdom
end) >= #room.players // 2 then
self.kingdom = "wild"
end
room:broadcastProperty(self, "kingdom")
else
room:setPlayerProperty(self, "kingdom", "wild")
end
if self.gender == General.Agender then
self.gender = general.gender
end
room:sendLog{
type = "#RevealGeneral",
from = self.id,
arg = isDeputy and "deputyGeneral" or "mainGeneral",
arg2 = generalName,
}
if not no_trigger then
room.logic:trigger(fk.GeneralRevealed, self, generalName)
end
end
function ServerPlayer:revealBySkillName(skill_name)
local main = self.general == "anjiang"
local deputy = self.deputyGeneral == "anjiang"
if main then
if table.contains(Fk.generals[self:getMark("__heg_general")]
:getSkillNameList(), skill_name) then
self:revealGeneral(false)
return
end
end
if deputy then
if table.contains(Fk.generals[self:getMark("__heg_deputy")]
:getSkillNameList(), skill_name) then
self:revealGeneral(true)
return
end
end
end
function ServerPlayer:hideGeneral(isDeputy)
local room = self.room
local generalName = isDeputy and self.deputyGeneral or self.general
local mark = isDeputy and "__heg_deputy" or "__heg_general"
self:setMark(mark, generalName)
self:doNotify("SetPlayerMark", json.encode{ self.id, mark, generalName})
if isDeputy then
room:setDeputyGeneral(self, "anjiang")
room:broadcastProperty(self, "deputyGeneral")
else
room:setPlayerGeneral(self, "anjiang", false)
room:broadcastProperty(self, "general")
end
local general = Fk.generals[generalName]
local skills = general.skills
local place = isDeputy and "m" or "d"
for _, s in ipairs(skills) do
room:handleAddLoseSkills(self, "-" .. s.name, nil, false, true)
if s.relate_to_place ~= place then
if s.frequency == Skill.Compulsory then
self:addFakeSkill("reveal_skill")
end
self:addFakeSkill(s)
end
end
for _, sname in ipairs(general.other_skills) do
room:handleAddLoseSkills(self, "-" .. sname, nil, false, true)
local s = Fk.skills[sname]
if s.relate_to_place ~= place then
if s.frequency == Skill.Compulsory then
self:addFakeSkill("reveal_skill")
end
self:addFakeSkill(s)
end
end
room.logic:trigger(fk.GeneralHidden, room, generalName)
end
-- 神貂蝉
---@param p ServerPlayer
function ServerPlayer:control(p)
if self == p then
self.room:setPlayerMark(p, "@ControledBy", 0)
else
self.room:setPlayerMark(p, "@ControledBy", "seat#" .. self.seat)
end
p.serverplayer = self._splayer
end
-- 22
function ServerPlayer:addBuddy(other)
if type(other) == "number" then
other = self.room:getPlayerById(other)
end
Player.addBuddy(self, other)
self:doNotify("AddBuddy", json.encode{ other.id, other.player_cards[Player.Hand] })
end
function ServerPlayer:removeBuddy(other)
if type(other) == "number" then
other = self.room:getPlayerById(other)
end
Player.removeBuddy(self, other)
self:doNotify("RmBuddy", tostring(other.id))
end
return ServerPlayer