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:
notify 2022-12-20 12:51:54 +08:00 committed by GitHub
parent 22235ee6ec
commit 0029949a40
17 changed files with 622 additions and 76 deletions

View File

@ -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"

View File

@ -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] 阵亡,无伤害来源",
}

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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")

View File

@ -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)

View File

@ -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"] = "麒麟弓",

View File

@ -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,

View File

@ -255,4 +255,8 @@ RowLayout {
for (let i = 0; i < skillButtons.count; i++)
skillButtons.itemAt(i).enabled = false;
}
function tremble() {
selfPhoto.tremble();
}
}

View 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", "[]");
}
}
}
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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");

View File

@ -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);