mirror of
https://github.com/Qsgs-Fans/FreeKill.git
synced 2024-11-16 11:42:45 +08:00
Basiccard (#33)
* update nullification * Slash * kill player * correct players to alive_players * add log for changehp and dying * usecard log & indicator * setemotion, logevent * fix distanceTo * shutdown server when console start * game over * complete slash * change format of flist.txt to avoid '\r\n' * fix \r\n * peach, zhiheng * ask for peach
This commit is contained in:
parent
22235ee6ec
commit
0029949a40
|
@ -143,6 +143,7 @@ fk.client_callback["Setup"] = function(jsonData)
|
|||
end
|
||||
|
||||
fk.client_callback["EnterRoom"] = function(jsonData)
|
||||
Self = ClientPlayer:new(fk.Self)
|
||||
ClientInstance.players = {Self}
|
||||
ClientInstance.alive_players = {Self}
|
||||
ClientInstance.discard_pile = {}
|
||||
|
@ -188,6 +189,12 @@ fk.client_callback["ArrangeSeats"] = function(jsonData)
|
|||
p.seat = i
|
||||
table.insert(players, p)
|
||||
end
|
||||
|
||||
for i = 1, #players - 1 do
|
||||
players[i].next = players[i + 1]
|
||||
end
|
||||
players[#players].next = players[1]
|
||||
|
||||
ClientInstance.players = players
|
||||
|
||||
ClientInstance:notifyUI("ArrangeSeats", jsonData)
|
||||
|
@ -355,6 +362,27 @@ fk.client_callback["GameLog"] = function(jsonData)
|
|||
ClientInstance:appendLog(data)
|
||||
end
|
||||
|
||||
fk.client_callback["LogEvent"] = function(jsonData)
|
||||
local data = json.decode(jsonData)
|
||||
if data.type == "Death" then
|
||||
table.removeOne(
|
||||
ClientInstance.alive_players,
|
||||
ClientInstance:getPlayerById(data.to)
|
||||
)
|
||||
end
|
||||
ClientInstance:notifyUI("LogEvent", jsonData)
|
||||
end
|
||||
|
||||
fk.client_callback["AddCardUseHistory"] = function(jsonData)
|
||||
local data = json.decode(jsonData)
|
||||
Self:addCardUseHistory(data[1], data[2])
|
||||
end
|
||||
|
||||
fk.client_callback["ResetCardUseHistory"] = function(jsonData)
|
||||
if jsonData == "" then jsonData = nil end
|
||||
Self:resetCardUseHistory(jsonData)
|
||||
end
|
||||
|
||||
-- Create ClientInstance (used by Lua)
|
||||
ClientInstance = Client:new()
|
||||
dofile "lua/client/client_util.lua"
|
||||
|
|
|
@ -77,6 +77,12 @@ function GetCards(pack_name)
|
|||
return json.encode(ret)
|
||||
end
|
||||
|
||||
function DistanceTo(from, to)
|
||||
local a = ClientInstance:getPlayerById(from)
|
||||
local b = ClientInstance:getPlayerById(to)
|
||||
return a:distanceTo(b)
|
||||
end
|
||||
|
||||
---@param card string | integer
|
||||
---@param player integer
|
||||
function CanUseCard(card, player)
|
||||
|
@ -96,6 +102,9 @@ end
|
|||
---@param selected integer[] @ ids of selected targets
|
||||
---@param selected_cards integer[] @ ids of selected cards
|
||||
function CanUseCardToTarget(card, to_select, selected)
|
||||
if ClientInstance:getPlayerById(to_select).dead then
|
||||
return "false"
|
||||
end
|
||||
local c ---@type Card
|
||||
local selected_cards
|
||||
if type(card) == "number" then
|
||||
|
@ -264,6 +273,23 @@ Fk:loadTranslationTable{
|
|||
["Sort Cards"] = "牌序",
|
||||
["Chat"] = "聊天",
|
||||
["Log"] = "战报",
|
||||
|
||||
["$GameOver"] = "游戏结束",
|
||||
["$Winner"] = "%1 获胜",
|
||||
["Back To Lobby"] = "返回大厅",
|
||||
}
|
||||
|
||||
-- Game concepts
|
||||
Fk:loadTranslationTable{
|
||||
["lord"] = "主公",
|
||||
["loyalist"] = "忠臣",
|
||||
["rebel"] = "反贼",
|
||||
["renegade"] = "内奸",
|
||||
["lord+loyalist"] = "主忠",
|
||||
|
||||
["normal_damage"] = "无属性",
|
||||
["fire_damage"] = "火属性",
|
||||
["thunder_damage"] = "雷属性",
|
||||
}
|
||||
|
||||
-- related to sendLog
|
||||
|
@ -296,6 +322,9 @@ Fk:loadTranslationTable{
|
|||
-- useCard
|
||||
["#UseCard"] = "%from 使用了牌 %card",
|
||||
["#UseCardToTargets"] = "%from 使用了牌 %card,目标是 %to",
|
||||
["#CardUseCollaborator"] = "%from 在此次 %card 中的子目标是 %to",
|
||||
["#UseCardToCard"] = "%from 使用了牌 %card,目标是 %arg",
|
||||
["#ResponsePlayCard"] = "%from 打出了牌 %card",
|
||||
|
||||
-- judge
|
||||
["#InitialJudge"] = "%from 的判定牌为 %card",
|
||||
|
@ -306,4 +335,16 @@ Fk:loadTranslationTable{
|
|||
["#TurnOver"] = "%from 将武将牌翻面,现在是 %arg",
|
||||
["face_up"] = "正面朝上",
|
||||
["face_down"] = "背面朝上",
|
||||
|
||||
-- damage, heal and lose HP
|
||||
["#Damage"] = "%to 对 %from 造成了 %arg 点 %arg2 伤害",
|
||||
["#DamageWithNoFrom"] = "%from 受到了 %arg 点 %arg2 伤害",
|
||||
["#LoseHP"] = "%from 失去了 %arg 点体力",
|
||||
["#HealHP"] = "%from 回复了 %arg 点体力",
|
||||
["#ShowHPAndMaxHP"] = "%from 现在的体力值为 %arg,体力上限为 %arg2",
|
||||
|
||||
-- dying and death
|
||||
["#EnterDying"] = "%from 进入了濒死阶段",
|
||||
["#KillPlayer"] = "%from [%arg] 阵亡,凶手是 %to",
|
||||
["#KillPlayerWithNoKiller"] = "%from [%arg] 阵亡,无伤害来源",
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
---@field general string
|
||||
---@field handcard_num integer
|
||||
---@field seat integer
|
||||
---@field next Player
|
||||
---@field phase Phase
|
||||
---@field faceup boolean
|
||||
---@field chained boolean
|
||||
|
@ -50,6 +51,7 @@ function Player:initialize()
|
|||
self.role = ""
|
||||
self.general = ""
|
||||
self.seat = 0
|
||||
self.next = nil
|
||||
self.phase = Player.PhaseNone
|
||||
self.faceup = true
|
||||
self.chained = false
|
||||
|
@ -240,7 +242,15 @@ end
|
|||
|
||||
---@param other Player
|
||||
function Player:distanceTo(other)
|
||||
local right = math.abs(self.seat - other.seat)
|
||||
assert(other:isInstanceOf(Player))
|
||||
local right = 0
|
||||
local temp = self
|
||||
while temp ~= other do
|
||||
if not temp.dead then
|
||||
right = right + 1
|
||||
end
|
||||
temp = temp.next
|
||||
end
|
||||
local left = #Fk:currentRoom().alive_players - right
|
||||
local ret = math.min(left, right)
|
||||
-- TODO: corrent distance here using skills
|
||||
|
@ -260,11 +270,18 @@ function Player:addCardUseHistory(cardName, num)
|
|||
end
|
||||
|
||||
function Player:resetCardUseHistory(cardName)
|
||||
if not cardName then
|
||||
self.cardUsedHistory = {}
|
||||
end
|
||||
if self.cardUsedHistory[cardName] then
|
||||
self.cardUsedHistory[cardName] = 0
|
||||
end
|
||||
end
|
||||
|
||||
function Player:usedTimes(cardName)
|
||||
return self.cardUsedHistory[cardName] or 0
|
||||
end
|
||||
|
||||
function Player:isKongcheng()
|
||||
return #self:getCardIds(Player.Hand) == 0
|
||||
end
|
||||
|
@ -277,6 +294,10 @@ function Player:isAllNude()
|
|||
return #self:getCardIds() == 0
|
||||
end
|
||||
|
||||
function Player:isWounded()
|
||||
return self.hp < self.maxHp
|
||||
end
|
||||
|
||||
---@param skill string | Skill
|
||||
---@return Skill
|
||||
local function getActualSkill(skill)
|
||||
|
|
|
@ -71,4 +71,12 @@ fk.CardEffecting = 56
|
|||
fk.CardEffectFinished = 57
|
||||
fk.CardEffectCancelledOut = 58
|
||||
|
||||
fk.NumOfEvents = 59
|
||||
fk.AskForPeaches = 59
|
||||
fk.AskForPeachesDone = 60
|
||||
fk.Death = 61
|
||||
fk.BuryVictim = 62
|
||||
fk.BeforeGameOverJudge = 63
|
||||
fk.GameOverJudge = 64
|
||||
fk.GameFinished = 65
|
||||
|
||||
fk.NumOfEvents = 66
|
||||
|
|
|
@ -74,7 +74,7 @@ function GameLogic:chooseGenerals()
|
|||
room:broadcastProperty(lord, "general")
|
||||
end
|
||||
|
||||
local nonlord = room:getOtherPlayers(lord)
|
||||
local nonlord = room:getOtherPlayers(lord, true)
|
||||
local generals = Fk:getGeneralsRandomly(#nonlord * 3, Fk.generals, {lord_general})
|
||||
table.shuffle(generals)
|
||||
for _, p in ipairs(nonlord) do
|
||||
|
@ -150,7 +150,7 @@ function GameLogic:action()
|
|||
self:trigger(fk.GameStart)
|
||||
local room = self.room
|
||||
|
||||
for _, p in ipairs(room.players) do
|
||||
for _, p in ipairs(room.alive_players) do
|
||||
self:trigger(fk.DrawInitialCards, p, { num = 4 })
|
||||
end
|
||||
|
||||
|
|
|
@ -49,7 +49,14 @@ function Room:initialize(_room)
|
|||
|
||||
self.room.startGame = function(_self)
|
||||
Room.initialize(self, _room) -- clear old data
|
||||
self:run()
|
||||
local co_func = function()
|
||||
self:run()
|
||||
end
|
||||
local co = coroutine.create(co_func)
|
||||
while not self.game_finished do
|
||||
coroutine.resume(co)
|
||||
end
|
||||
fk.qInfo("Game Finished.")
|
||||
end
|
||||
|
||||
self.players = {}
|
||||
|
@ -126,19 +133,18 @@ function Room:deadPlayerFilter(playerIds)
|
|||
return newPlayerIds
|
||||
end
|
||||
|
||||
---@param sortBySeat boolean
|
||||
---@return ServerPlayer[]
|
||||
function Room:getAlivePlayers(sortBySeat)
|
||||
sortBySeat = sortBySeat or true
|
||||
|
||||
local alivePlayers = {}
|
||||
for _, player in ipairs(self.players) do
|
||||
if player:isAlive() then
|
||||
table.insert(alivePlayers, player)
|
||||
function Room:getAlivePlayers()
|
||||
local current = self.current
|
||||
local temp = current.next
|
||||
local ret = {current}
|
||||
while temp ~= current do
|
||||
if not temp.dead then
|
||||
table.insert(ret, temp)
|
||||
end
|
||||
temp = temp.next
|
||||
end
|
||||
|
||||
return alivePlayers
|
||||
return ret
|
||||
end
|
||||
|
||||
---@param player ServerPlayer
|
||||
|
@ -169,8 +175,13 @@ end
|
|||
|
||||
---@param expect ServerPlayer
|
||||
---@return ServerPlayer[]
|
||||
function Room:getOtherPlayers(expect)
|
||||
local ret = {table.unpack(self.players)}
|
||||
function Room:getOtherPlayers(expect, include_dead)
|
||||
local ret
|
||||
if include_dead then
|
||||
ret = {table.unpack(self.players)}
|
||||
else
|
||||
ret = {table.unpack(self.alive_players)}
|
||||
end
|
||||
table.removeOne(ret, expect)
|
||||
return ret
|
||||
end
|
||||
|
@ -391,6 +402,25 @@ function Room:sendLog(log)
|
|||
self:doBroadcastNotify("GameLog", json.encode(log))
|
||||
end
|
||||
|
||||
function Room:doAnimate(type, data, players)
|
||||
players = players or self.players
|
||||
data.type = type
|
||||
self:doBroadcastNotify("Animate", json.encode(data), players)
|
||||
end
|
||||
|
||||
function Room:setEmotion(player, name)
|
||||
self:doAnimate("Emotion", {
|
||||
player = player.id,
|
||||
emotion = name
|
||||
})
|
||||
end
|
||||
|
||||
function Room:sendLogEvent(type, data, players)
|
||||
players = players or self.players
|
||||
data.type = type
|
||||
self:doBroadcastNotify("LogEvent", json.encode(data), players)
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- interactive functions
|
||||
------------------------------------------------------------------------
|
||||
|
@ -465,14 +495,7 @@ function Room:askForDiscard(player, minNum, maxNum, includeEquip, skillName)
|
|||
end
|
||||
end
|
||||
|
||||
self:moveCards({
|
||||
ids = toDiscard,
|
||||
from = player.id,
|
||||
toArea = Card.DiscardPile,
|
||||
moveReason = fk.ReasonDiscard,
|
||||
proposer = player.id,
|
||||
skillName = skillName
|
||||
})
|
||||
self:throwCard(toDiscard, skillName, player, player)
|
||||
end
|
||||
|
||||
---@param player ServerPlayer
|
||||
|
@ -644,7 +667,7 @@ function Room:askForNullification(players, card_name, prompt, cancelable, extra_
|
|||
extra_data = extra_data or {}
|
||||
prompt = prompt or "#AskForUseCard"
|
||||
|
||||
self:notifyMoveFocus(self.players, card_name)
|
||||
self:notifyMoveFocus(self.alive_players, card_name)
|
||||
self:doBroadcastNotify("WaitForNullification", "")
|
||||
|
||||
local data = {card_name, prompt, cancelable, extra_data}
|
||||
|
@ -776,13 +799,57 @@ end
|
|||
---@param cardUseEvent CardUseStruct
|
||||
---@return boolean
|
||||
function Room:useCard(cardUseEvent)
|
||||
local from = cardUseEvent.customFrom or cardUseEvent.from
|
||||
self:moveCards({
|
||||
ids = { cardUseEvent.cardId },
|
||||
from = cardUseEvent.customFrom or cardUseEvent.from,
|
||||
from = from,
|
||||
toArea = Card.Processing,
|
||||
moveReason = fk.ReasonUse,
|
||||
})
|
||||
|
||||
|
||||
self:setEmotion(self:getPlayerById(from), Fk:getCardById(cardUseEvent.cardId).name)
|
||||
self:doAnimate("Indicate", {
|
||||
from = from,
|
||||
to = cardUseEvent.tos or {},
|
||||
})
|
||||
if cardUseEvent.tos then
|
||||
local to = {}
|
||||
for _, t in ipairs(cardUseEvent.tos) do
|
||||
table.insert(to, t[1])
|
||||
end
|
||||
self:sendLog{
|
||||
type = "#UseCardToTargets",
|
||||
from = from,
|
||||
to = to,
|
||||
card = {cardUseEvent.cardId},
|
||||
}
|
||||
for _, t in ipairs(cardUseEvent.tos) do
|
||||
if t[2] then
|
||||
local temp = {table.unpack(t)}
|
||||
table.remove(temp, 1)
|
||||
self:sendLog{
|
||||
type = "#CardUseCollaborator",
|
||||
from = t[1],
|
||||
to = temp,
|
||||
card = {cardUseEvent.cardId},
|
||||
}
|
||||
end
|
||||
end
|
||||
elseif cardUseEvent.toCardId then
|
||||
self:sendLog{
|
||||
type = "#UseCardToCard",
|
||||
from = from,
|
||||
card = {cardUseEvent.cardId},
|
||||
arg = Fk:getCardById(cardUseEvent.toCardId).name,
|
||||
}
|
||||
else
|
||||
self:sendLog{
|
||||
type = "#UseCard",
|
||||
from = from,
|
||||
card = {cardUseEvent.cardId},
|
||||
}
|
||||
end
|
||||
|
||||
if Fk:getCardById(cardUseEvent.cardId).skill then
|
||||
Fk:getCardById(cardUseEvent.cardId).skill:onUse(self, cardUseEvent)
|
||||
end
|
||||
|
@ -992,7 +1059,7 @@ function Room:doCardEffect(cardEffectEvent)
|
|||
end
|
||||
elseif Fk:getCardById(cardEffectEvent.cardId).type == Card.TypeTrick then
|
||||
local players = {}
|
||||
for _, p in ipairs(self.players) do
|
||||
for _, p in ipairs(self.alive_players) do
|
||||
local cards = p.player_cards[Player.Hand]
|
||||
for _, cid in ipairs(cards) do
|
||||
if Fk:getCardById(cid).name == "nullification" then
|
||||
|
@ -1071,7 +1138,7 @@ function Room:moveCards(...)
|
|||
return false
|
||||
end
|
||||
|
||||
self:notifyMoveCards(self.players, cardsMoveStructs)
|
||||
self:notifyMoveCards(nil, cardsMoveStructs)
|
||||
|
||||
for _, data in ipairs(cardsMoveStructs) do
|
||||
if #data.moveInfo > 0 then
|
||||
|
@ -1227,6 +1294,54 @@ function Room:changeHp(player, num, reason, skillName, damageStruct)
|
|||
|
||||
assert(not (data.reason == "recover" and data.num < 0))
|
||||
player.hp = math.min(player.hp + data.num, player.maxHp)
|
||||
self:broadcastProperty(player, "hp")
|
||||
|
||||
if reason == "damage" then
|
||||
local damage_nature_table = {
|
||||
[fk.NormalDamage] = "normal_damage",
|
||||
[fk.FireDamage] = "fire_damage",
|
||||
[fk.ThunderDamage] = "thunder_damage",
|
||||
}
|
||||
if damageStruct.from then
|
||||
self:sendLog{
|
||||
type = "#Damage",
|
||||
to = {damageStruct.from},
|
||||
from = player.id,
|
||||
arg = 0 - num,
|
||||
arg2 = damage_nature_table[damageStruct.damageType],
|
||||
}
|
||||
else
|
||||
self:sendLog{
|
||||
type = "#DamageWithNoFrom",
|
||||
from = player.id,
|
||||
arg = 0 - num,
|
||||
arg2 = damage_nature_table[damageStruct.damageType],
|
||||
}
|
||||
end
|
||||
self:sendLogEvent("Damage", {
|
||||
to = player.id,
|
||||
damageType = damage_nature_table[damageStruct.damageType],
|
||||
})
|
||||
elseif reason == "loseHp" then
|
||||
self:sendLog{
|
||||
type = "#LoseHP",
|
||||
from = player.id,
|
||||
arg = 0 - num,
|
||||
}
|
||||
elseif reason == "recover" then
|
||||
self:sendLog{
|
||||
type = "#HealHP",
|
||||
from = player.id,
|
||||
arg = num,
|
||||
}
|
||||
end
|
||||
|
||||
self:sendLog{
|
||||
type = "#ShowHPAndMaxHP",
|
||||
from = player.id,
|
||||
arg = player.hp,
|
||||
arg2 = player.maxHp,
|
||||
}
|
||||
|
||||
self.logic:trigger(fk.HpChanged, player, data)
|
||||
|
||||
|
@ -1281,6 +1396,7 @@ function Room:changeMaxHp(player, num)
|
|||
end
|
||||
|
||||
player.maxHp = math.max(player.maxHp + num, 0)
|
||||
self:broadcastProperty(player, "maxHp")
|
||||
local diff = player.hp - player.maxHp
|
||||
if diff > 0 then
|
||||
if not self:changeHp(player, -diff) then
|
||||
|
@ -1328,7 +1444,7 @@ function Room:damage(damageStruct)
|
|||
|
||||
if not self:changeHp(victim, -damageStruct.damage, "damage", damageStruct.skillName, damageStruct) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
stages = {
|
||||
[fk.Damage] = damageStruct.from,
|
||||
|
@ -1368,35 +1484,58 @@ end
|
|||
function Room:enterDying(dyingStruct)
|
||||
local dyingPlayer = self:getPlayerById(dyingStruct.who)
|
||||
dyingPlayer.dying = true
|
||||
self:broadcastProperty(dyingPlayer, "dying")
|
||||
self:sendLog{
|
||||
type = "#EnterDying",
|
||||
from = dyingPlayer.id,
|
||||
}
|
||||
self.logic:trigger(fk.EnterDying, dyingPlayer, dyingStruct)
|
||||
|
||||
if dyingPlayer.hp < 1 then
|
||||
local alivePlayers = self:getAlivePlayers()
|
||||
for _, player in ipairs(alivePlayers) do
|
||||
self.logic:trigger(fk.Dying, player, dyingStruct)
|
||||
|
||||
if player.hp > 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if dyingPlayer.hp < 1 then
|
||||
---@type DeathStruct
|
||||
local deathData = {
|
||||
who = dyingPlayer.id,
|
||||
damage = dyingStruct.damage,
|
||||
}
|
||||
self:killPlayer(deathData)
|
||||
end
|
||||
self.logic:trigger(fk.Dying, dyingPlayer, dyingStruct)
|
||||
self.logic:trigger(fk.AskForPeaches, dyingPlayer, dyingStruct)
|
||||
self.logic:trigger(fk.AskForPeachesDone, dyingPlayer, dyingStruct)
|
||||
end
|
||||
|
||||
if not dyingPlayer.dead then
|
||||
dyingPlayer.dying = false
|
||||
self:broadcastProperty(dyingPlayer, "dying")
|
||||
end
|
||||
self.logic:trigger(fk.AfterDying, dyingPlayer, dyingStruct)
|
||||
end
|
||||
|
||||
---@param deathStruct DeathStruct
|
||||
function Room:killPlayer(deathStruct)
|
||||
print(self:getPlayerById(deathStruct.who).general .. " is dead")
|
||||
self:gameOver()
|
||||
local victim = self:getPlayerById(deathStruct.who)
|
||||
victim.dead = true
|
||||
table.removeOne(self.alive_players, victim)
|
||||
|
||||
local logic = self.logic
|
||||
logic:trigger(fk.BeforeGameOverJudge, victim, deathStruct)
|
||||
|
||||
local killer = deathStruct.damage and deathStruct.damage.from or nil
|
||||
if killer then
|
||||
self:sendLog{
|
||||
type = "#KillPlayer",
|
||||
to = {killer},
|
||||
from = victim.id,
|
||||
arg = victim.role,
|
||||
}
|
||||
else
|
||||
self:sendLog{
|
||||
type = "#KillPlayerWithNoKiller",
|
||||
from = victim.id,
|
||||
arg = victim.role,
|
||||
}
|
||||
end
|
||||
self:sendLogEvent("Death", {to = victim.id})
|
||||
|
||||
self:broadcastProperty(victim, "role")
|
||||
self:broadcastProperty(victim, "dead")
|
||||
|
||||
logic:trigger(fk.GameOverJudge, victim, deathStruct)
|
||||
logic:trigger(fk.Death, victim, deathStruct)
|
||||
logic:trigger(fk.BuryVictim, victim, deathStruct)
|
||||
end
|
||||
|
||||
-- lose/acquire skill actions
|
||||
|
@ -1502,6 +1641,26 @@ function Room:judge(data)
|
|||
end
|
||||
end
|
||||
|
||||
---@param card_ids integer[]
|
||||
---@param skillName string
|
||||
---@param who ServerPlayer
|
||||
---@param thrower ServerPlayer
|
||||
function Room:throwCard(card_ids, skillName, who, thrower)
|
||||
if type(card_ids) == "number" then
|
||||
card_ids = {card_ids}
|
||||
end
|
||||
skillName = skillName or ""
|
||||
thrower = thrower or who
|
||||
self:moveCards({
|
||||
ids = card_ids,
|
||||
from = who.id,
|
||||
toArea = Card.DiscardPile,
|
||||
moveReason = fk.ReasonDiscard,
|
||||
proposer = thrower.id,
|
||||
skillName = skillName
|
||||
})
|
||||
end
|
||||
|
||||
-- other helpers
|
||||
|
||||
function Room:adjustSeats()
|
||||
|
@ -1545,10 +1704,16 @@ function Room:shuffleDrawPile()
|
|||
table.shuffle(self.draw_pile)
|
||||
end
|
||||
|
||||
function Room:gameOver()
|
||||
function Room:gameOver(winner)
|
||||
self.game_finished = true
|
||||
-- dosomething
|
||||
|
||||
for _, p in ipairs(self.players) do
|
||||
self:broadcastProperty(p, "role")
|
||||
end
|
||||
self:doBroadcastNotify("GameOver", winner)
|
||||
|
||||
self.room:gameOver()
|
||||
coroutine.yield()
|
||||
end
|
||||
|
||||
function CreateRoom(_room)
|
||||
|
|
|
@ -19,8 +19,6 @@ function ServerPlayer:initialize(_self)
|
|||
self.state = _self:getStateString()
|
||||
self.room = nil
|
||||
|
||||
self.next = nil
|
||||
|
||||
-- Below are for doBroadcastRequest
|
||||
self.request_data = ""
|
||||
self.client_reply = ""
|
||||
|
@ -208,4 +206,44 @@ function ServerPlayer:play(phase_table)
|
|||
end
|
||||
end
|
||||
|
||||
function ServerPlayer:drawCards(num, skillName, fromPlace)
|
||||
return self.room:drawCards(self, num, skillName, fromPlace)
|
||||
end
|
||||
|
||||
function ServerPlayer:bury()
|
||||
-- self:clearFlags()
|
||||
-- self:clearHistory()
|
||||
self:throwAllCards()
|
||||
-- self:throwAllMarks()
|
||||
-- self:clearPiles()
|
||||
|
||||
-- self.room:clearPlayerCardLimitation(self, false)
|
||||
end
|
||||
|
||||
function ServerPlayer:throwAllCards(flag)
|
||||
local room = self.room
|
||||
flag = flag or "hej"
|
||||
if string.find(flag, "h") then
|
||||
room:throwCard(self.player_cards[Player.Hand], "", self)
|
||||
end
|
||||
|
||||
if string.find(flag, "e") then
|
||||
room:throwCard(self.player_cards[Player.Equip], "", self)
|
||||
end
|
||||
|
||||
if string.find(flag, "j") then
|
||||
room:throwCard(self.player_cards[Player.Judge], "", self)
|
||||
end
|
||||
end
|
||||
|
||||
function ServerPlayer:addCardUseHistory(cardName, num)
|
||||
Player.addCardUseHistory(self, cardName, num)
|
||||
self:doNotify("AddCardUseHistory", json.encode{cardName, num})
|
||||
end
|
||||
|
||||
function ServerPlayer:resetCardUseHistory(cardName)
|
||||
Player.resetCardUseHistory(self, cardName)
|
||||
self:doNotify("ResetCardUseHistory", cardName or "")
|
||||
end
|
||||
|
||||
return ServerPlayer
|
||||
|
|
|
@ -1,8 +1,48 @@
|
|||
---@param victim ServerPlayer
|
||||
local function getWinner(victim)
|
||||
local room = victim.room
|
||||
local winner = ""
|
||||
local alive = room.alive_players
|
||||
|
||||
if victim.role == "lord" then
|
||||
if #alive == 1 and alive[1].role == "renegade" then
|
||||
winner = "renegede"
|
||||
else
|
||||
winner = "rebel"
|
||||
end
|
||||
elseif victim.role ~= "loyalist" then
|
||||
local lord_win = true
|
||||
for _, p in ipairs(alive) do
|
||||
if p.role == "rebel" or p.role == "renegade" then
|
||||
lord_win = false
|
||||
break
|
||||
end
|
||||
end
|
||||
if lord_win then
|
||||
winner = "lord+loyalist"
|
||||
end
|
||||
end
|
||||
|
||||
return winner
|
||||
end
|
||||
|
||||
---@param killer ServerPlayer
|
||||
local function rewardAndPunish(killer, victim)
|
||||
if killer.dead then return end
|
||||
if victim.role == "rebel" then
|
||||
killer:drawCards(3, "kill")
|
||||
elseif victim.role == "loyalist" and killer.role == "lord" then
|
||||
killer:throwAllCards("he")
|
||||
end
|
||||
end
|
||||
|
||||
GameRule = fk.CreateTriggerSkill{
|
||||
name = "game_rule",
|
||||
events = {
|
||||
fk.GameStart, fk.DrawInitialCards, fk.TurnStart,
|
||||
fk.EventPhaseProceeding, fk.EventPhaseEnd, fk.EventPhaseChanging,
|
||||
fk.AskForPeaches, fk.AskForPeachesDone,
|
||||
fk.GameOverJudge, fk.BuryVictim,
|
||||
},
|
||||
priority = 0,
|
||||
|
||||
|
@ -40,7 +80,7 @@ GameRule = fk.CreateTriggerSkill{
|
|||
table.insert(move_to_notify.moveInfo,
|
||||
{ cardId = id, fromArea = Card.DrawPile })
|
||||
end
|
||||
room:notifyMoveCards(room.players, {move_to_notify})
|
||||
room:notifyMoveCards(nil, {move_to_notify})
|
||||
|
||||
for _, id in ipairs(cardIds) do
|
||||
room:setCardArea(id, Card.PlayerHand, player.id)
|
||||
|
@ -129,12 +169,52 @@ GameRule = fk.CreateTriggerSkill{
|
|||
end,
|
||||
[fk.EventPhaseEnd] = function()
|
||||
if player.phase == Player.Play then
|
||||
-- TODO: clear history
|
||||
player:resetCardUseHistory()
|
||||
end
|
||||
end,
|
||||
[fk.EventPhaseChanging] = function()
|
||||
-- TODO: copy but dont copy all
|
||||
end,
|
||||
[fk.AskForPeaches] = function()
|
||||
local savers = room:getAlivePlayers()
|
||||
for _, p in ipairs(savers) do
|
||||
if player.hp > 0 or player.dead then break end
|
||||
while player.hp < 1 do
|
||||
local peach_use = room:askForUseCard(p, "peach")
|
||||
if not peach_use then break end
|
||||
peach_use.tos = { {player.id} }
|
||||
room:useCard(peach_use)
|
||||
end
|
||||
end
|
||||
end,
|
||||
[fk.AskForPeachesDone] = function()
|
||||
if player.hp < 1 then
|
||||
---@type DeathStruct
|
||||
local deathData = {
|
||||
who = player.id,
|
||||
damage = data.damage,
|
||||
}
|
||||
room:killPlayer(deathData)
|
||||
end
|
||||
end,
|
||||
[fk.GameOverJudge] = function()
|
||||
local winner = getWinner(player)
|
||||
if winner ~= "" then
|
||||
room:gameOver(winner)
|
||||
return true
|
||||
end
|
||||
end,
|
||||
[fk.BuryVictim] = function()
|
||||
player:bury()
|
||||
if room.tag["SkipNormalDeathProcess"] then
|
||||
return false
|
||||
end
|
||||
local damage = data.damage
|
||||
if damage and damage.from then
|
||||
local killer = room:getPlayerById(damage.from)
|
||||
rewardAndPunish(killer, player);
|
||||
end
|
||||
end,
|
||||
default = function()
|
||||
print("game_rule: Event=" .. event)
|
||||
room:askForSkillInvoke(player, "rule")
|
||||
|
|
|
@ -101,7 +101,9 @@ local zhiheng = fk.CreateActiveSkill{
|
|||
return #selected == 0 and #selected_cards > 0
|
||||
end,
|
||||
on_effect = function(self, room, effect)
|
||||
room:drawCards(room:getPlayerById(effect.from), #effect.cards, "zhiheng")
|
||||
local from = room:getPlayerById(effect.from)
|
||||
room:throwCard(effect.cards, self.name, from)
|
||||
room:drawCards(from, #effect.cards, self.name)
|
||||
end
|
||||
}
|
||||
local sunquan = General:new(extension, "sunquan", "wu", 4)
|
||||
|
|
|
@ -7,26 +7,31 @@ Fk:loadTranslationTable{
|
|||
|
||||
local slashSkill = fk.CreateActiveSkill{
|
||||
name = "slash_skill",
|
||||
can_use = function(self, player)
|
||||
-- TODO: tmd skill
|
||||
return player:usedTimes("slash") < 1
|
||||
end,
|
||||
target_filter = function(self, to_select, selected)
|
||||
if #selected == 0 then
|
||||
local player = Fk:currentRoom():getPlayerById(to_select)
|
||||
return Self ~= player
|
||||
return Self ~= player and Self:inMyAttackRange(player)
|
||||
end
|
||||
end,
|
||||
feasible = function(self, selected)
|
||||
-- TODO: tmd
|
||||
return #selected == 1
|
||||
end,
|
||||
on_effect = function(self, room, effect)
|
||||
local to = effect.to
|
||||
local from = effect.from
|
||||
local cid = room:askForCardChosen(
|
||||
room:getPlayerById(from),
|
||||
room:getPlayerById(to),
|
||||
"hej",
|
||||
"snatch"
|
||||
)
|
||||
|
||||
room:obtainCard(from, cid)
|
||||
|
||||
room:damage({
|
||||
from = from,
|
||||
to = to,
|
||||
damage = 1,
|
||||
damageType = fk.NormalDamage,
|
||||
skillName = self.name
|
||||
})
|
||||
end
|
||||
}
|
||||
local slash = fk.CreateBasicCard{
|
||||
|
@ -115,10 +120,28 @@ extension:addCards({
|
|||
jink:clone(Card.Diamond, 11),
|
||||
})
|
||||
|
||||
local peachSkill = fk.CreateActiveSkill{
|
||||
name = "peach_skill",
|
||||
can_use = function(self, player)
|
||||
return player:isWounded()
|
||||
end,
|
||||
on_effect = function(self, room, effect)
|
||||
local to = effect.to
|
||||
local from = effect.from
|
||||
|
||||
room:recover{
|
||||
who = to,
|
||||
num = 1,
|
||||
recoverBy = from,
|
||||
skillName = self.name
|
||||
}
|
||||
end
|
||||
}
|
||||
local peach = fk.CreateBasicCard{
|
||||
name = "peach",
|
||||
suit = Card.Heart,
|
||||
number = 3,
|
||||
skill = peachSkill,
|
||||
}
|
||||
Fk:loadTranslationTable{
|
||||
["peach"] = "桃",
|
||||
|
@ -261,6 +284,9 @@ extension:addCards({
|
|||
|
||||
local nullificationSkill = fk.CreateActiveSkill{
|
||||
name = "nullification_skill",
|
||||
can_use = function()
|
||||
return false
|
||||
end,
|
||||
on_effect = function(self, room, effect)
|
||||
if effect.responseToEvent then
|
||||
effect.responseToEvent.isCancellOut = true
|
||||
|
@ -374,6 +400,7 @@ local crossbow = fk.CreateWeapon{
|
|||
name = "crossbow",
|
||||
suit = Card.Club,
|
||||
number = 1,
|
||||
attack_range = 1,
|
||||
}
|
||||
Fk:loadTranslationTable{
|
||||
["crossbow"] = "诸葛连弩",
|
||||
|
@ -388,6 +415,7 @@ local qingGang = fk.CreateWeapon{
|
|||
name = "qinggang_sword",
|
||||
suit = Card.Spade,
|
||||
number = 6,
|
||||
attack_range = 2,
|
||||
}
|
||||
Fk:loadTranslationTable{
|
||||
["qinggang_sword"] = "青釭剑",
|
||||
|
@ -401,6 +429,7 @@ local iceSword = fk.CreateWeapon{
|
|||
name = "ice_sword",
|
||||
suit = Card.Spade,
|
||||
number = 2,
|
||||
attack_range = 2,
|
||||
}
|
||||
Fk:loadTranslationTable{
|
||||
["ice_sword"] = "寒冰剑",
|
||||
|
@ -414,6 +443,7 @@ local doubleSwords = fk.CreateWeapon{
|
|||
name = "double_swords",
|
||||
suit = Card.Spade,
|
||||
number = 2,
|
||||
attack_range = 2,
|
||||
}
|
||||
Fk:loadTranslationTable{
|
||||
["double_swords"] = "雌雄双股剑",
|
||||
|
@ -427,6 +457,7 @@ local blade = fk.CreateWeapon{
|
|||
name = "blade",
|
||||
suit = Card.Spade,
|
||||
number = 5,
|
||||
attack_range = 3,
|
||||
}
|
||||
Fk:loadTranslationTable{
|
||||
["blade"] = "青龙偃月刀",
|
||||
|
@ -440,6 +471,7 @@ local spear = fk.CreateWeapon{
|
|||
name = "spear",
|
||||
suit = Card.Spade,
|
||||
number = 12,
|
||||
attack_range = 3,
|
||||
}
|
||||
Fk:loadTranslationTable{
|
||||
["spear"] = "丈八蛇矛",
|
||||
|
@ -453,6 +485,7 @@ local axe = fk.CreateWeapon{
|
|||
name = "axe",
|
||||
suit = Card.Diamond,
|
||||
number = 5,
|
||||
attack_range = 3,
|
||||
}
|
||||
Fk:loadTranslationTable{
|
||||
["axe"] = "贯石斧",
|
||||
|
@ -466,6 +499,7 @@ local halberd = fk.CreateWeapon{
|
|||
name = "halberd",
|
||||
suit = Card.Diamond,
|
||||
number = 12,
|
||||
attack_range = 4,
|
||||
}
|
||||
Fk:loadTranslationTable{
|
||||
["halberd"] = "方天画戟",
|
||||
|
@ -479,6 +513,7 @@ local kylinBow = fk.CreateWeapon{
|
|||
name = "kylin_bow",
|
||||
suit = Card.Heart,
|
||||
number = 5,
|
||||
attack_range = 5,
|
||||
}
|
||||
Fk:loadTranslationTable{
|
||||
["kylin_bow"] = "麒麟弓",
|
||||
|
|
|
@ -4,6 +4,8 @@ import QtQuick.Layouts
|
|||
import "Common"
|
||||
import "RoomElement"
|
||||
import "RoomLogic.js" as Logic
|
||||
import "skin-bank.js" as SkinBank
|
||||
|
||||
|
||||
Item {
|
||||
id: roomScene
|
||||
|
@ -159,7 +161,7 @@ Item {
|
|||
maxHp: model.maxHp
|
||||
hp: model.hp
|
||||
seatNumber: model.seatNumber
|
||||
isDead: model.isDead
|
||||
dead: model.dead
|
||||
dying: model.dying
|
||||
faceup: model.faceup
|
||||
chained: model.chained
|
||||
|
@ -224,7 +226,7 @@ Item {
|
|||
self.maxHp: dashboardModel.maxHp
|
||||
self.hp: dashboardModel.hp
|
||||
self.seatNumber: dashboardModel.seatNumber
|
||||
self.isDead: dashboardModel.isDead
|
||||
self.dead: dashboardModel.dead
|
||||
self.dying: dashboardModel.dying
|
||||
self.faceup: dashboardModel.faceup
|
||||
self.chained: dashboardModel.chained
|
||||
|
@ -459,6 +461,15 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "D"
|
||||
property bool show_distance: false
|
||||
onActivated: {
|
||||
show_distance = !show_distance;
|
||||
showDistance(show_distance);
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Esc"
|
||||
onActivated: {
|
||||
|
@ -491,6 +502,18 @@ Item {
|
|||
log.append(msg);
|
||||
}
|
||||
|
||||
function showDistance(show) {
|
||||
for (let i = 0; i < photoModel.count; i++) {
|
||||
let item = photos.itemAt(i);
|
||||
if (show) {
|
||||
let dis = Backend.callLuaFunction("DistanceTo",[Self.id, item.playerid]);
|
||||
item.distance = parseInt(dis);
|
||||
} else {
|
||||
item.distance = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
toast.show(Backend.translate("$EnterRoom"));
|
||||
|
||||
|
@ -504,7 +527,7 @@ Item {
|
|||
maxHp: 0,
|
||||
hp: 0,
|
||||
seatNumber: 1,
|
||||
isDead: false,
|
||||
dead: false,
|
||||
dying: false,
|
||||
faceup: true,
|
||||
chained: false,
|
||||
|
@ -527,7 +550,7 @@ Item {
|
|||
maxHp: 0,
|
||||
hp: 0,
|
||||
seatNumber: i + 1,
|
||||
isDead: false,
|
||||
dead: false,
|
||||
dying: false,
|
||||
faceup: true,
|
||||
chained: false,
|
||||
|
|
|
@ -255,4 +255,8 @@ RowLayout {
|
|||
for (let i = 0; i < skillButtons.count; i++)
|
||||
skillButtons.itemAt(i).enabled = false;
|
||||
}
|
||||
|
||||
function tremble() {
|
||||
selfPhoto.tremble();
|
||||
}
|
||||
}
|
||||
|
|
32
qml/Pages/RoomElement/GameOverBox.qml
Normal file
32
qml/Pages/RoomElement/GameOverBox.qml
Normal file
|
@ -0,0 +1,32 @@
|
|||
import QtQuick
|
||||
import ".."
|
||||
|
||||
GraphicsBox {
|
||||
property string winner: ""
|
||||
|
||||
id: root
|
||||
title.text: Backend.translate("$GameOver")
|
||||
width: Math.max(140, body.width + 20)
|
||||
height: body.height + title.height + 20
|
||||
|
||||
Column {
|
||||
id: body
|
||||
x: 10
|
||||
y: title.height + 5
|
||||
spacing: 10
|
||||
|
||||
Text {
|
||||
text: Backend.translate("$Winner").arg(Backend.translate(winner))
|
||||
color: "#E4D5A0"
|
||||
}
|
||||
|
||||
MetroButton {
|
||||
text: Backend.translate("Back To Lobby")
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
onClicked: {
|
||||
ClientInstance.notifyServer("QuitRoom", "[]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,12 +19,13 @@ Item {
|
|||
property int maxHp: 0
|
||||
property int hp: 0
|
||||
property int seatNumber: 1
|
||||
property bool isDead: false
|
||||
property bool dead: false
|
||||
property bool dying: false
|
||||
property bool faceup: true
|
||||
property bool chained: false
|
||||
property bool drank: false
|
||||
property bool isOwner: false
|
||||
property int distance: 0
|
||||
property string status: "normal"
|
||||
|
||||
property alias handcardArea: handcardAreaItem
|
||||
|
@ -164,7 +165,7 @@ Item {
|
|||
anchors.fill: photoMask
|
||||
source: generalImage
|
||||
saturation: 0
|
||||
visible: root.isDead
|
||||
visible: root.dead
|
||||
}
|
||||
|
||||
Image {
|
||||
|
@ -212,8 +213,8 @@ Item {
|
|||
|
||||
Image {
|
||||
// id: saveme
|
||||
visible: root.isDead || root.dying
|
||||
source: SkinBank.DEATH_DIR + (root.isDead ? root.role : "saveme")
|
||||
visible: root.dead || root.dying
|
||||
source: SkinBank.DEATH_DIR + (root.dead ? root.role : "saveme")
|
||||
anchors.centerIn: photoMask
|
||||
}
|
||||
|
||||
|
@ -410,6 +411,17 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: "white"
|
||||
height: 20
|
||||
width: 20
|
||||
visible: distance != 0
|
||||
Text {
|
||||
text: distance
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
onGeneralChanged: {
|
||||
if (!roomScene.isStarted) return;
|
||||
generalName.text = Backend.translate(general);
|
||||
|
|
|
@ -170,6 +170,14 @@ function moveCards(moves) {
|
|||
}
|
||||
|
||||
function setEmotion(id, emotion) {
|
||||
let path = (SkinBank.PIXANIM_DIR + emotion).replace("file://", "");
|
||||
if (!Backend.exists(path)) {
|
||||
return;
|
||||
}
|
||||
if (!Backend.isDir(path)) {
|
||||
// TODO: set picture emotion
|
||||
return;
|
||||
}
|
||||
let component = Qt.createComponent("RoomElement/PixmapAnimation.qml");
|
||||
if (component.status !== Component.Ready)
|
||||
return;
|
||||
|
@ -183,7 +191,8 @@ function setEmotion(id, emotion) {
|
|||
}
|
||||
}
|
||||
|
||||
let animation = component.createObject(photo, {source: emotion, anchors: {centerIn: photo}});
|
||||
let animation = component.createObject(photo, {source: emotion});
|
||||
animation.anchors.centerIn = photo;
|
||||
animation.finished.connect(() => animation.destroy());
|
||||
animation.start();
|
||||
}
|
||||
|
@ -644,3 +653,47 @@ callbacks["AskForResponseCard"] = function(jsonData) {
|
|||
callbacks["WaitForNullification"] = function() {
|
||||
roomScene.state = "notactive";
|
||||
}
|
||||
|
||||
callbacks["Animate"] = function(jsonData) {
|
||||
// jsonData: [Object object]
|
||||
let data = JSON.parse(jsonData);
|
||||
switch (data.type) {
|
||||
case "Indicate":
|
||||
data.to.forEach(item => {
|
||||
doIndicate(data.from, [item[0]]);
|
||||
if (item[1]) {
|
||||
doIndicate(item[0], item.slice(1));
|
||||
}
|
||||
})
|
||||
break;
|
||||
case "Emotion":
|
||||
setEmotion(data.player, data.emotion);
|
||||
break;
|
||||
case "LightBox":
|
||||
break;
|
||||
case "SuperLightBox":
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
callbacks["LogEvent"] = function(jsonData) {
|
||||
// jsonData: [Object object]
|
||||
let data = JSON.parse(jsonData);
|
||||
switch (data.type) {
|
||||
case "Damage":
|
||||
let item = getPhotoOrDashboard(data.to);
|
||||
setEmotion(data.to, "damage");
|
||||
item.tremble();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
callbacks["GameOver"] = function(jsonData) {
|
||||
roomScene.state = "notactive";
|
||||
roomScene.popupBox.source = "RoomElement/GameOverBox.qml";
|
||||
let box = roomScene.popupBox.item;
|
||||
box.winner = jsonData;
|
||||
}
|
||||
|
|
|
@ -159,8 +159,9 @@ static void writeFileMD5(QFile &dest, const QString &fname) {
|
|||
}
|
||||
|
||||
auto data = f.readAll();
|
||||
data.replace(QByteArray("\r\n"), QByteArray("\n"));
|
||||
auto hash = QCryptographicHash::hash(data, QCryptographicHash::Md5).toHex();
|
||||
dest.write(fname.toUtf8() + '=' + hash + '\n');
|
||||
dest.write(fname.toUtf8() + '=' + hash + ';');
|
||||
}
|
||||
|
||||
static void writeDirMD5(QFile &dest, const QString &dir, const QString &filter) {
|
||||
|
@ -186,6 +187,7 @@ QString calcFileMD5() {
|
|||
qFatal("Cannot open flist.txt. Quitting.");
|
||||
}
|
||||
|
||||
writeDirMD5(flist, "packages", "*.lua");
|
||||
writeDirMD5(flist, "lua", "*.lua");
|
||||
writeDirMD5(flist, "qml", "*.qml");
|
||||
writeDirMD5(flist, "qml", "*.js");
|
||||
|
|
|
@ -98,7 +98,9 @@ QString ServerPlayer::waitForReply(int timeout)
|
|||
{
|
||||
QString ret;
|
||||
if (getState() != Player::Online) {
|
||||
#ifndef QT_DEBUG
|
||||
QThread::sleep(1);
|
||||
#endif
|
||||
ret = "__cancel";
|
||||
} else {
|
||||
ret = router->waitForReply(timeout);
|
||||
|
|
Loading…
Reference in New Issue
Block a user