71
docs/dev/hegemony.rst
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
.. SPDX-License-Identifier: GFDL-1.3-or-later
|
||||||
|
|
||||||
|
国战实现相关碎碎念
|
||||||
|
=================
|
||||||
|
|
||||||
|
国战流程与身份版基本相同,就有一些有区别:
|
||||||
|
|
||||||
|
- 双将,并且是同势力双将
|
||||||
|
- 开局所有人暗将状态
|
||||||
|
- 胜利判断和奖惩不同
|
||||||
|
- 亮将
|
||||||
|
- 预亮技能
|
||||||
|
|
||||||
|
下面来一个个分析一下。胜利和奖惩已经在22写过了,不分析。
|
||||||
|
|
||||||
|
双将与同势力选将
|
||||||
|
-----------
|
||||||
|
|
||||||
|
双将好说,已经做完了。主要是同势力选将,可能需要单独写个处理函数吧。还有抽武将的时候也要避免出现没人同势力的情况啊。
|
||||||
|
|
||||||
|
简而言之就是UI加个同势力选项;同时,随机生成武将环节中如果可选人数小于X+1(X为游戏内势力数,一般为4),那么就抽X+1张,然后去掉多余的。
|
||||||
|
|
||||||
|
暗将与亮将
|
||||||
|
----
|
||||||
|
|
||||||
|
首先不要亮出武将,即别broadcast general,或者把general先设为暗将。
|
||||||
|
|
||||||
|
每个玩家选完武将后可以用两个标记保存着自己实际的选将。而实际上会用暗将来代替选好的武将。
|
||||||
|
|
||||||
|
那设置体力上限也得重写了,总之就是GameLogic中选将和准备开始都需要重写呢。
|
||||||
|
|
||||||
|
暗将是没有任何技能的。但是亮将后,就可以用changeHero修改武将牌,然后就真的获得了该获得的所有技能了。
|
||||||
|
|
||||||
|
预亮
|
||||||
|
----
|
||||||
|
|
||||||
|
实现国战模式最大的难点。
|
||||||
|
|
||||||
|
预亮技能可以激活武将的某个技能,这样游戏会对该技能进行询问。
|
||||||
|
|
||||||
|
既然是询问,那么能预亮的技能只会是触发技咯,或者附带有隐藏触发技的技能。
|
||||||
|
|
||||||
|
那么,能预亮的技能从哪来?暗将可是个白板,没有任何技能的。
|
||||||
|
|
||||||
|
答案是游戏开始前公布武将(也就是公布暗将)环节的时候,用doNotify告诉每个玩家都获得了哪些技能。事实上呢,(在服务端这边)他们根本没有这些技能呢。但这样又有个问题,有些技能是在客户端有判断的,这意味着暗将的马术可能可以发挥作用。
|
||||||
|
|
||||||
|
解决办法是写个global的失效技令暗将的所有未预亮技能失效。然后在亮将后,先哟doNotify让玩家认为自己失去所有技能,再用changeHero变将,changeHero函数中有替换技能的代码。
|
||||||
|
|
||||||
|
那么预亮技能怎么处理呢?预亮和游戏流程没啥关系,纯属玩家想预亮就预亮想取消就取消。既然与游戏流程无关,那么这块就得用到异步IO了,pushRequest解决。
|
||||||
|
|
||||||
|
然后技能只要被预亮了,那么服务端就知道有这个技能了,先挂给Room。然后也给Player加上技能。此时只在服务端加技能!不对,不能加技能啊,把技能贴给logic就行了啊。那我既然要询问的话肯定得满足hasSkill啊。怎么办呢?果然还是要加给玩家啊,但又怕状态技捣乱,那就让状态技失效呗。
|
||||||
|
|
||||||
|
这样一来基本解决了预亮的游戏逻辑问题。接下来有一点要操心的是UI。首先对于有状态技的,找个地方塞个亮将按钮。手刹直接是在技能区加这个按钮了,那正好可以做个亮将技能啊。
|
||||||
|
|
||||||
|
然后就是UI了。这个得走cpp了谁让Lua服务端没有这种处理函数呢?(也根本不应该有,Lua和处理消息根本不在一个线程好吧)先不管这些,总之在暗将状态下,对于服务端notify到的触发型技能,给个预亮按钮。就换个按钮样式而已。主动和视为因为不影响烧条就照旧处理。就触发技弄成预亮键。视为技拼触发技的情况也弄成预亮hhh。
|
||||||
|
|
||||||
|
变回暗将
|
||||||
|
-----
|
||||||
|
|
||||||
|
点名批评邹氏
|
||||||
|
|
||||||
|
changeHero成暗将然后notify获得即可。两人都暗了的话还要变势力。
|
||||||
|
|
||||||
|
明将就是先notify失去再changeHero。
|
||||||
|
|
||||||
|
调虎离山
|
||||||
|
-----
|
||||||
|
|
||||||
|
将目标设为死人即可(删除)
|
||||||
|
|
||||||
|
真正实现的话,可能还是就按照牌面描述来吧,但是要对距离计算和上家/下家做修改。前者可能还能用距离技顶一下,后者非改源码不可。
|
|
@ -16,3 +16,4 @@ Dev文档
|
||||||
scenario.rst
|
scenario.rst
|
||||||
todo.rst
|
todo.rst
|
||||||
ui.rst
|
ui.rst
|
||||||
|
hegemony.rst
|
||||||
|
|
BIN
image/button/skill/prelight.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
image/button/skill/prelight/disabled.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
image/button/skill/prelight/normal.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
image/button/skill/prelight/pressed.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
image/button/skill/unprelight.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
image/card/general/unknown-magatama.png
Normal file
After Width: | Height: | Size: 145 B |
BIN
image/card/general/unknown.png
Normal file
After Width: | Height: | Size: 145 B |
BIN
image/photo/back/unknown.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
image/photo/back/wild.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
@ -113,10 +113,20 @@ function Client:appendLog(msg)
|
||||||
if not pid then
|
if not pid then
|
||||||
return ""
|
return ""
|
||||||
end
|
end
|
||||||
local ret = self:getPlayerById(pid)
|
local p = self:getPlayerById(pid)
|
||||||
ret = ret.general
|
local str <const> = '<font color="%s"><b>%s</b></font>'
|
||||||
|
if p.general == "anjiang" and (p.deputyGeneral == "anjiang"
|
||||||
|
or not p.deputyGeneral) then
|
||||||
|
local ret = Fk:translate("seat#" .. p.seat)
|
||||||
|
return string.format(str, color, ret)
|
||||||
|
end
|
||||||
|
|
||||||
|
local ret = p.general
|
||||||
ret = Fk:translate(ret)
|
ret = Fk:translate(ret)
|
||||||
ret = string.format('<font color="' .. color .. '"><b>%s</b></font>', ret)
|
if p.deputyGeneral then
|
||||||
|
ret = ret .. "/" .. Fk:translate(p.deputyGeneral)
|
||||||
|
end
|
||||||
|
ret = string.format(str, color, ret)
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -490,10 +500,13 @@ end
|
||||||
fk.client_callback["LoseSkill"] = function(jsonData)
|
fk.client_callback["LoseSkill"] = function(jsonData)
|
||||||
-- jsonData: [ int player_id, string skill_name ]
|
-- jsonData: [ int player_id, string skill_name ]
|
||||||
local data = json.decode(jsonData)
|
local data = json.decode(jsonData)
|
||||||
local id, skill_name = data[1], data[2]
|
local id, skill_name, prelight = data[1], data[2], data[3]
|
||||||
local target = ClientInstance:getPlayerById(id)
|
local target = ClientInstance:getPlayerById(id)
|
||||||
local skill = Fk.skills[skill_name]
|
local skill = Fk.skills[skill_name]
|
||||||
target:loseSkill(skill)
|
if not prelight then
|
||||||
|
target:loseSkill(skill)
|
||||||
|
end
|
||||||
|
|
||||||
if skill.visible then
|
if skill.visible then
|
||||||
ClientInstance:notifyUI("LoseSkill", jsonData)
|
ClientInstance:notifyUI("LoseSkill", jsonData)
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,6 +16,8 @@ function GetGeneralData(name)
|
||||||
hp = general.hp,
|
hp = general.hp,
|
||||||
maxHp = general.maxHp,
|
maxHp = general.maxHp,
|
||||||
shield = general.shield,
|
shield = general.shield,
|
||||||
|
hidden = general.hidden,
|
||||||
|
total_hidden = general.total_hidden,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -108,7 +110,9 @@ end
|
||||||
function GetGenerals(pack_name)
|
function GetGenerals(pack_name)
|
||||||
local ret = {}
|
local ret = {}
|
||||||
for _, g in ipairs(Fk.packages[pack_name].generals) do
|
for _, g in ipairs(Fk.packages[pack_name].generals) do
|
||||||
table.insert(ret, g.name)
|
if not g.total_hidden then
|
||||||
|
table.insert(ret, g.name)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
return json.encode(ret)
|
return json.encode(ret)
|
||||||
end
|
end
|
||||||
|
|
|
@ -150,6 +150,19 @@ FreeKill使用的是libgit2的C API,与此同时使用Git完成拓展包的下
|
||||||
["#AskForDiscard"] = "请弃置 %arg 张牌,最少 %arg2 张",
|
["#AskForDiscard"] = "请弃置 %arg 张牌,最少 %arg2 张",
|
||||||
["#AskForCard"] = "请选择 %arg 张牌,最少 %arg2 张",
|
["#AskForCard"] = "请选择 %arg 张牌,最少 %arg2 张",
|
||||||
["#askForPindian"] = "请选择一张手牌作为拼点牌",
|
["#askForPindian"] = "请选择一张手牌作为拼点牌",
|
||||||
|
["#StartPindianReason"] = "%from 由于 %arg 而发起拼点",
|
||||||
|
|
||||||
|
["#RevealGeneral"] = "%from 亮出 %arg %arg2",
|
||||||
|
["mainGeneral"] = "主将",
|
||||||
|
["deputyGeneral"] = "副将",
|
||||||
|
["seat#1"] = "一号位",
|
||||||
|
["seat#2"] = "二号位",
|
||||||
|
["seat#3"] = "三号位",
|
||||||
|
["seat#4"] = "四号位",
|
||||||
|
["seat#5"] = "五号位",
|
||||||
|
["seat#6"] = "六号位",
|
||||||
|
["seat#7"] = "七号位",
|
||||||
|
["seat#8"] = "八号位",
|
||||||
|
|
||||||
["Trust"] = "托管",
|
["Trust"] = "托管",
|
||||||
["Sort Cards"] = "牌序",
|
["Sort Cards"] = "牌序",
|
||||||
|
|
|
@ -295,7 +295,8 @@ function Engine:getGeneralsRandomly(num, generalPool, except, filter)
|
||||||
local availableGenerals = {}
|
local availableGenerals = {}
|
||||||
for _, general in pairs(generalPool) do
|
for _, general in pairs(generalPool) do
|
||||||
if not table.contains(except, general.name) and not (filter and filter(general)) then
|
if not table.contains(except, general.name) and not (filter and filter(general)) then
|
||||||
if #table.filter(availableGenerals, function(g)
|
if (not general.hidden and not general.total_hidden) and
|
||||||
|
#table.filter(availableGenerals, function(g)
|
||||||
return g.trueName == general.trueName
|
return g.trueName == general.trueName
|
||||||
end) == 0 then
|
end) == 0 then
|
||||||
table.insert(availableGenerals, general)
|
table.insert(availableGenerals, general)
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
---@field public other_skills string[] @ 武将身上属于其他武将的技能,通过字符串调用
|
---@field public other_skills string[] @ 武将身上属于其他武将的技能,通过字符串调用
|
||||||
---@field public related_skills Skill[] @ 武将相关的不属于其他武将的技能,例如邓艾的急袭
|
---@field public related_skills Skill[] @ 武将相关的不属于其他武将的技能,例如邓艾的急袭
|
||||||
---@field public related_other_skills string [] @ 武将相关的属于其他武将的技能,例如孙策的英姿
|
---@field public related_other_skills string [] @ 武将相关的属于其他武将的技能,例如孙策的英姿
|
||||||
|
---@field public hidden boolean
|
||||||
|
---@field public total_hidden boolean
|
||||||
General = class("General")
|
General = class("General")
|
||||||
|
|
||||||
---@alias Gender integer
|
---@alias Gender integer
|
||||||
|
@ -78,4 +80,13 @@ function General:addRelatedSkill(skill)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function General:getSkillNameList(include_lord)
|
||||||
|
local ret = table.map(self.skills, Util.NameMapper)
|
||||||
|
table.insertTable(ret, self.other_skills)
|
||||||
|
|
||||||
|
if not include_lord then
|
||||||
|
end
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
|
||||||
return General
|
return General
|
||||||
|
|
|
@ -118,6 +118,17 @@ function Player:setGeneral(general, setHp, addSkills)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Player:getGeneralMaxHp()
|
||||||
|
local general = Fk.generals[self:getMark("__heg_general") or self.general]
|
||||||
|
local deputy = Fk.generals[self:getMark("__heg_deputy") or self.deputy]
|
||||||
|
|
||||||
|
if not deputy then
|
||||||
|
return general.maxHp
|
||||||
|
else
|
||||||
|
return (general.maxHp + deputy.maxHp) // 2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
--- 查询角色是否存在flag。
|
--- 查询角色是否存在flag。
|
||||||
---@param flag string @ 一种标记
|
---@param flag string @ 一种标记
|
||||||
function Player:hasFlag(flag)
|
function Player:hasFlag(flag)
|
||||||
|
|
|
@ -69,11 +69,9 @@ function Skill:isEquipmentSkill()
|
||||||
return self.attached_equip and type(self.attached_equip) == 'string' and self.attached_equip ~= ""
|
return self.attached_equip and type(self.attached_equip) == 'string' and self.attached_equip ~= ""
|
||||||
end
|
end
|
||||||
|
|
||||||
--- 确认技能是否对特定玩家生效。
|
--- 判断技能是不是对于某玩家而言失效了。
|
||||||
---
|
---
|
||||||
--- 据说你一般用不到这个,只要你把on_cost(代价)和on_use(效果)区分得当,
|
--- 它影响的是hasSkill,但也可以单独拿出来判断。
|
||||||
---
|
|
||||||
--- 涉及技能无效时,不需要这个函数也可以实现效果。
|
|
||||||
---@param player Player @ 玩家
|
---@param player Player @ 玩家
|
||||||
---@return boolean
|
---@return boolean
|
||||||
function Skill:isEffectable(player)
|
function Skill:isEffectable(player)
|
||||||
|
|
|
@ -69,6 +69,9 @@ Util.Id2CardMapper = function(id) return Fk:getCardById(id) end
|
||||||
Util.Id2PlayerMapper = function(id)
|
Util.Id2PlayerMapper = function(id)
|
||||||
return Fk:currentRoom():getPlayerById(id)
|
return Fk:currentRoom():getPlayerById(id)
|
||||||
end
|
end
|
||||||
|
Util.NameMapper = function(e) return e.name end
|
||||||
|
Util.Name2GeneralMapper = function(e) return Fk.generals[e] end
|
||||||
|
Util.Name2SkillMapper = function(e) return Fk.skills[e] end
|
||||||
|
|
||||||
---@generic T
|
---@generic T
|
||||||
---@param self T[]
|
---@param self T[]
|
||||||
|
|
|
@ -98,20 +98,22 @@ GameEvent.cleaners[GameEvent.Turn] = function(self)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local current = room.current
|
||||||
|
local logic = room.logic
|
||||||
if self.interrupted then
|
if self.interrupted then
|
||||||
room.current.phase = Player.Finish
|
current.phase = Player.Finish
|
||||||
room.logic:trigger(fk.EventPhaseStart, room.current, nil, true)
|
logic:trigger(fk.EventPhaseStart, current, nil, true)
|
||||||
room.logic:trigger(fk.EventPhaseEnd, room.current, nil, true)
|
logic:trigger(fk.EventPhaseEnd, current, nil, true)
|
||||||
|
|
||||||
room.current.phase = Player.NotActive
|
current.phase = Player.NotActive
|
||||||
room:notifyProperty(room.current, room.current, "phase")
|
room:notifyProperty(current, current, "phase")
|
||||||
room.logic:trigger(fk.EventPhaseChanging, room.current,
|
logic:trigger(fk.EventPhaseChanging, current,
|
||||||
{ from = Player.Finish, to = Player.NotActive }, true)
|
{ from = Player.Finish, to = Player.NotActive }, true)
|
||||||
room.logic:trigger(fk.EventPhaseStart, room.current, nil, true)
|
logic:trigger(fk.EventPhaseStart, current, nil, true)
|
||||||
|
|
||||||
room.current.skipped_phases = {}
|
current.skipped_phases = {}
|
||||||
|
|
||||||
room.logic:trigger(fk.TurnEnd, room.current, nil, true)
|
logic:trigger(fk.TurnEnd, current, nil, true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -184,18 +186,15 @@ GameEvent.functions[GameEvent.Phase] = function(self)
|
||||||
end,
|
end,
|
||||||
[Player.Finish] = function()
|
[Player.Finish] = function()
|
||||||
|
|
||||||
end,
|
|
||||||
[Player.NotActive] = function()
|
|
||||||
|
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if self.phase ~= Player.NotActive then
|
if player.phase ~= Player.NotActive then
|
||||||
logic:trigger(fk.EventPhaseEnd, self)
|
logic:trigger(fk.EventPhaseEnd, player)
|
||||||
else
|
else
|
||||||
self.skipped_phases = {}
|
player.skipped_phases = {}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,13 @@ function GameLogic:run()
|
||||||
self.room:adjustSeats()
|
self.room:adjustSeats()
|
||||||
|
|
||||||
self:chooseGenerals()
|
self:chooseGenerals()
|
||||||
|
|
||||||
|
self:buildPlayerCircle()
|
||||||
|
self:broadcastGeneral()
|
||||||
|
self:prepareDrawPile()
|
||||||
|
self:attachSkillToPlayers()
|
||||||
self:prepareForStart()
|
self:prepareForStart()
|
||||||
|
|
||||||
self.room.game_started = true
|
self.room.game_started = true
|
||||||
self:action()
|
self:action()
|
||||||
end
|
end
|
||||||
|
@ -123,7 +129,7 @@ function GameLogic:chooseGenerals()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function GameLogic:prepareForStart()
|
function GameLogic:buildPlayerCircle()
|
||||||
local room = self.room
|
local room = self.room
|
||||||
local players = room.players
|
local players = room.players
|
||||||
room.alive_players = {table.unpack(players)}
|
room.alive_players = {table.unpack(players)}
|
||||||
|
@ -131,6 +137,11 @@ function GameLogic:prepareForStart()
|
||||||
players[i].next = players[i + 1]
|
players[i].next = players[i + 1]
|
||||||
end
|
end
|
||||||
players[#players].next = players[1]
|
players[#players].next = players[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
function GameLogic:broadcastGeneral()
|
||||||
|
local room = self.room
|
||||||
|
local players = room.players
|
||||||
|
|
||||||
for _, p in ipairs(players) do
|
for _, p in ipairs(players) do
|
||||||
assert(p.general ~= "")
|
assert(p.general ~= "")
|
||||||
|
@ -153,13 +164,21 @@ function GameLogic:prepareForStart()
|
||||||
room:broadcastProperty(p, "hp")
|
room:broadcastProperty(p, "hp")
|
||||||
room:broadcastProperty(p, "shield")
|
room:broadcastProperty(p, "shield")
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function GameLogic:prepareDrawPile()
|
||||||
|
local room = self.room
|
||||||
local allCardIds = Fk:getAllCardIds()
|
local allCardIds = Fk:getAllCardIds()
|
||||||
table.shuffle(allCardIds)
|
table.shuffle(allCardIds)
|
||||||
room.draw_pile = allCardIds
|
room.draw_pile = allCardIds
|
||||||
for _, id in ipairs(room.draw_pile) do
|
for _, id in ipairs(room.draw_pile) do
|
||||||
self.room:setCardArea(id, Card.DrawPile, nil)
|
self.room:setCardArea(id, Card.DrawPile, nil)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function GameLogic:attachSkillToPlayers()
|
||||||
|
local room = self.room
|
||||||
|
local players = room.players
|
||||||
|
|
||||||
local addRoleModSkills = function(player, skillName)
|
local addRoleModSkills = function(player, skillName)
|
||||||
local skill = Fk.skills[skillName]
|
local skill = Fk.skills[skillName]
|
||||||
|
@ -189,6 +208,11 @@ function GameLogic:prepareForStart()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function GameLogic:prepareForStart()
|
||||||
|
local room = self.room
|
||||||
|
local players = room.players
|
||||||
|
|
||||||
self:addTriggerSkill(GameRule)
|
self:addTriggerSkill(GameRule)
|
||||||
for _, trig in ipairs(Fk.global_trigger) do
|
for _, trig in ipairs(Fk.global_trigger) do
|
||||||
|
|
|
@ -444,6 +444,47 @@ function Room:setDeputyGeneral(player, general)
|
||||||
self:notifyProperty(player, player, "deputyGeneral")
|
self:notifyProperty(player, player, "deputyGeneral")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param player ServerPlayer @ 要换将的玩家
|
||||||
|
---@param new_general string @ 要变更的武将,若不存在则变身为孙策,孙策也不存在则nil错
|
||||||
|
---@param full boolean @ 是否血量满状态变身
|
||||||
|
---@param isDeputy boolean @ 是否变的是副将
|
||||||
|
---@param sendLog boolean @ 是否发Log
|
||||||
|
function Room:changeHero(player, new_general, full, isDeputy, sendLog)
|
||||||
|
local orig = isDeputy and (player.deputyGeneral or "") or player.general
|
||||||
|
|
||||||
|
orig = Fk.generals[orig]
|
||||||
|
local orig_skills = orig and orig:getSkillNameList() or {}
|
||||||
|
|
||||||
|
local new = Fk.generals[new_general] or Fk.generals["sunce"]
|
||||||
|
local new_skills = new:getSkillNameList()
|
||||||
|
|
||||||
|
table.insertTable(new_skills, table.map(orig_skills, function(e)
|
||||||
|
return "-" .. e
|
||||||
|
end))
|
||||||
|
|
||||||
|
self:handleAddLoseSkills(player, table.concat(new_skills, "|"), nil, false)
|
||||||
|
|
||||||
|
if isDeputy then
|
||||||
|
player.deputyGeneral = new_general
|
||||||
|
self:broadcastProperty(player, "deputyGeneral")
|
||||||
|
else
|
||||||
|
player.general = new_general
|
||||||
|
self:broadcastProperty(player, "general")
|
||||||
|
end
|
||||||
|
|
||||||
|
if player.kingdom == "unknown" then
|
||||||
|
player.kingdom = new.kingdom
|
||||||
|
self:broadcastProperty(player, "kingdom")
|
||||||
|
end
|
||||||
|
|
||||||
|
player.maxHp = player:getGeneralMaxHp()
|
||||||
|
self:broadcastProperty(player, "hp")
|
||||||
|
if full then
|
||||||
|
player.hp = player.maxHp
|
||||||
|
self:broadcastProperty(player, "maxHp")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
------------------------------------------------------------------------
|
------------------------------------------------------------------------
|
||||||
-- 网络通信有关
|
-- 网络通信有关
|
||||||
------------------------------------------------------------------------
|
------------------------------------------------------------------------
|
||||||
|
@ -601,7 +642,7 @@ function Room:requestLoop(rest_time)
|
||||||
end
|
end
|
||||||
|
|
||||||
player:doNotify("UpdateDrawPile", #self.draw_pile)
|
player:doNotify("UpdateDrawPile", #self.draw_pile)
|
||||||
player:doNotify("UpdateRoundNum", self:getTag("RoundCount"))
|
player:doNotify("UpdateRoundNum", self:getTag("RoundCount") or 0)
|
||||||
|
|
||||||
table.insert(self.observers, {observee.id, player})
|
table.insert(self.observers, {observee.id, player})
|
||||||
end
|
end
|
||||||
|
@ -639,14 +680,17 @@ function Room:requestLoop(rest_time)
|
||||||
local request = self.room:fetchRequest()
|
local request = self.room:fetchRequest()
|
||||||
if request ~= "" then
|
if request ~= "" then
|
||||||
ret = true
|
ret = true
|
||||||
local id, command = table.unpack(request:split(","))
|
local reqlist = request:split(",")
|
||||||
id = tonumber(id)
|
local id = tonumber(reqlist[1])
|
||||||
|
local command = reqlist[2]
|
||||||
if command == "reconnect" then
|
if command == "reconnect" then
|
||||||
self:getPlayerById(id):reconnect()
|
self:getPlayerById(id):reconnect()
|
||||||
elseif command == "observe" then
|
elseif command == "observe" then
|
||||||
addObserver(id)
|
addObserver(id)
|
||||||
elseif command == "leave" then
|
elseif command == "leave" then
|
||||||
removeObserver(id)
|
removeObserver(id)
|
||||||
|
elseif command == "prelight" then
|
||||||
|
self:getPlayerById(id):prelightSkill(reqlist[3], reqlist[4] == "true")
|
||||||
end
|
end
|
||||||
elseif rest_time > 10 then
|
elseif rest_time > 10 then
|
||||||
-- let current thread sleep 10ms
|
-- let current thread sleep 10ms
|
||||||
|
@ -2349,6 +2393,7 @@ end
|
||||||
---@param skill Skill @ 发动的技能
|
---@param skill Skill @ 发动的技能
|
||||||
---@param effect_cb fun() @ 实际要调用的函数
|
---@param effect_cb fun() @ 实际要调用的函数
|
||||||
function Room:useSkill(player, skill, effect_cb)
|
function Room:useSkill(player, skill, effect_cb)
|
||||||
|
player:revealBySkillName(skill.name)
|
||||||
if not skill.mute then
|
if not skill.mute then
|
||||||
if skill.attached_equip then
|
if skill.attached_equip then
|
||||||
local equip = Fk:cloneCard(skill.attached_equip)
|
local equip = Fk:cloneCard(skill.attached_equip)
|
||||||
|
|
|
@ -261,7 +261,7 @@ function ServerPlayer:reconnect()
|
||||||
end
|
end
|
||||||
|
|
||||||
self:doNotify("UpdateDrawPile", #room.draw_pile)
|
self:doNotify("UpdateDrawPile", #room.draw_pile)
|
||||||
self:doNotify("UpdateRoundNum", room:getTag("RoundCount"))
|
self:doNotify("UpdateRoundNum", room:getTag("RoundCount") or 0)
|
||||||
|
|
||||||
room:broadcastProperty(self, "state")
|
room:broadcastProperty(self, "state")
|
||||||
end
|
end
|
||||||
|
@ -568,4 +568,82 @@ function ServerPlayer:pindian(tos, skillName, initialCard)
|
||||||
return pindianData
|
return pindianData
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Hegemony func
|
||||||
|
|
||||||
|
function ServerPlayer:prelightSkill(skill, isPrelight)
|
||||||
|
if isPrelight then
|
||||||
|
self:addSkill(skill)
|
||||||
|
else
|
||||||
|
self:loseSkill(skill)
|
||||||
|
end
|
||||||
|
self:doNotify("PrelightSkill", json.encode{ skill, isPrelight })
|
||||||
|
end
|
||||||
|
|
||||||
|
function ServerPlayer:revealGeneral(isDeputy)
|
||||||
|
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]
|
||||||
|
for _, s in ipairs(general:getSkillNameList()) do
|
||||||
|
local skill = Fk.skills[s]
|
||||||
|
if skill:isInstanceOf(TriggerSkill) or table.find(skill.related_skills,
|
||||||
|
function(s) return s:isInstanceOf(TriggerSkill) end) then
|
||||||
|
self:doNotify("LoseSkill", json.encode{ self.id, s, true })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local oldKingdom = self.kingdom
|
||||||
|
room:changeHero(self, generalName, false, isDeputy)
|
||||||
|
local kingdom = general.kingdom
|
||||||
|
if oldKingdom == "unknown" and #table.filter(room:getOtherPlayers(self),
|
||||||
|
function(p)
|
||||||
|
return p.kingdom == kingdom
|
||||||
|
end) >= #room.alive_players // 2 then
|
||||||
|
|
||||||
|
self.kingdom = "wild"
|
||||||
|
room:broadcastProperty(self, "kingdom")
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.gender ~= General.Male and self.gender ~= General.Female then
|
||||||
|
self.gender = general.gender
|
||||||
|
end
|
||||||
|
|
||||||
|
room:sendLog{
|
||||||
|
type = "#RevealGeneral",
|
||||||
|
from = self.id,
|
||||||
|
arg = isDeputy and "deputyGeneral" or "mainGeneral",
|
||||||
|
arg2 = generalName,
|
||||||
|
}
|
||||||
|
|
||||||
|
-- TODO: 阴阳鱼摸牌
|
||||||
|
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
|
||||||
|
|
||||||
return ServerPlayer
|
return ServerPlayer
|
||||||
|
|
377
packages/standard/hegemony.lua
Normal file
|
@ -0,0 +1,377 @@
|
||||||
|
local heg_description = [==[
|
||||||
|
# 国战简介
|
||||||
|
|
||||||
|
《三国杀·国战》是一款可以支持2\~12人(线上版4\~8人)同时参与的桌面卡牌游戏。在游戏中,每名玩家将甄选三国时代中包括魏国、蜀国、吴国、群雄在内的各位名将,组成自己的战斗团队,利用他们珠联璧合的组合技能发起进攻,消灭其他各方势力,赢得最终的胜利。
|
||||||
|
|
||||||
|
## 准备游戏
|
||||||
|
|
||||||
|
**挑选武将:**
|
||||||
|
|
||||||
|
发给每位玩家四张武将牌(会员5张),选出两张势力相同的武将牌并背面朝上放置,称为“暗置”(参考段落“明置和暗置”)。
|
||||||
|
|
||||||
|
靠近体力牌的武将视为副将,另一个视为主将。
|
||||||
|
|
||||||
|
游戏中,每名玩家扮演的角色由两张武将牌组成。
|
||||||
|
|
||||||
|
**分发体力牌:**
|
||||||
|
|
||||||
|
每位玩家拿取一张体力牌,翻到对应体力上限的一面,放置在武将牌旁边。体力上限为两张武将牌上的完整阴阳鱼的数量之和。两个单独的阴阳鱼可以组成一个完整的阴阳鱼。
|
||||||
|
|
||||||
|
注:当一名角色的两张武将牌第一次均明置时,若其武将牌上有单独的阴阳鱼没有组成1 点体力,则他可以立即摸一张牌。
|
||||||
|
|
||||||
|
扣减体力时,用主将挡住扣减的体力,露出当前体力值。
|
||||||
|
|
||||||
|
## 进行游戏
|
||||||
|
|
||||||
|
随机选择一名玩家作为起始玩家。由该玩家开始,按逆时针方向以回合的方式进行。即每名玩家有一个自己的回合,一名玩家的回合结束后, 右边玩家的回合开始,依次轮流进行。
|
||||||
|
|
||||||
|
每个玩家的回合可以分为六个阶段:
|
||||||
|
|
||||||
|
回合开始阶段 -> 判定阶段 -> 摸牌阶段 -> 出牌阶段 -> 弃牌阶段 -> 回合结束阶段
|
||||||
|
|
||||||
|
**回合开始阶段:**
|
||||||
|
|
||||||
|
有些技能可以在此阶段发动。你的暗置的武将牌也可于此阶段明置。
|
||||||
|
|
||||||
|
**判定阶段:**
|
||||||
|
|
||||||
|
若你的面前横置着延时类锦囊,你必须依次对这些延时类锦囊进行判定。若有多张延时类锦囊,先判定最后放置的那张,然后以此类推。
|
||||||
|
|
||||||
|
**摸牌阶段:**
|
||||||
|
|
||||||
|
你从牌堆顶摸两张牌。
|
||||||
|
|
||||||
|
**出牌阶段:**
|
||||||
|
|
||||||
|
你可以使用任意张牌,但必须遵守以下两条规则:
|
||||||
|
|
||||||
|
1. 每个出牌阶段仅限使用一次【杀】。
|
||||||
|
2. 任何一名角色面前的判定区里不能放有两张同名的牌。
|
||||||
|
|
||||||
|
每使用一张牌,即执行该牌之效果,详见“游戏牌详解”。如无特殊说明,游戏牌在使用后均需置入弃牌堆。
|
||||||
|
|
||||||
|
**弃牌阶段:**
|
||||||
|
|
||||||
|
在出牌阶段中,不想出或没法出牌时,就进入弃牌阶段。此时检查你的手牌数是否超出你当前的体力值( 你的手牌上限等于你当前的体力值), 每超出一张,须弃置一张手牌。
|
||||||
|
|
||||||
|
**回合结束阶段:**
|
||||||
|
|
||||||
|
有些技能可以在此阶段发动。
|
||||||
|
|
||||||
|
|
||||||
|
## 角色死亡
|
||||||
|
|
||||||
|
当一名角色的体力降到0 时,即进入濒死状态,除非该角色在此时使用【酒】,或有角色使用【桃】来挽救该角色,否则该角色死亡。
|
||||||
|
|
||||||
|
死亡的角色明置其武将牌,弃置该角色所有牌及其判定区里的牌,然后执行奖惩。
|
||||||
|
|
||||||
|
奖惩方式:
|
||||||
|
|
||||||
|
1. 已经确定势力的角色杀死相同势力的角色须弃置所有手牌和装备区的牌;
|
||||||
|
2. 已经确定势力的角色杀死不同势力的角色,从牌堆摸取等同于该势力人数(包括刚刚杀死的角色)张牌。
|
||||||
|
|
||||||
|
例:“蜀”势力角色杀死了一名“魏”势力角色,此时还有其他两名“魏”势力角色存活,则该“蜀”势力角色摸三张牌。
|
||||||
|
|
||||||
|
注:若被杀死的角色还没有明置武将牌(即没确定势力),则须明置验明势力。 没有势力的角色(即武将牌没有明置的角色)杀死其他角色没有奖惩。
|
||||||
|
|
||||||
|
## 胜负结算
|
||||||
|
|
||||||
|
玩家的游戏目标与势力有关:消灭所有与自己不同势力的角色。
|
||||||
|
|
||||||
|
特殊的:野心家需要消灭所有其他角色。
|
||||||
|
|
||||||
|
当全场所有角色均确定势力后, 才可以进行胜利条件的判断:
|
||||||
|
当全场只剩下一种势力存活时, 该势力的角色获胜( 没有确定势力的角色无法取得游戏胜利, 即使与存活的其他角色为同一势力)。
|
||||||
|
|
||||||
|
## 暗将规则
|
||||||
|
|
||||||
|
处于暗置状态的武将牌没有任何武将技能、性别以及势力。当暗置的武将牌发动技能时,将武将牌明置,然后发动相应的技能。
|
||||||
|
|
||||||
|
暗置的武将牌只有两个时机可以将武将牌明置:1. 回合开始阶段开始时;2. 发动武将牌的技能时。
|
||||||
|
|
||||||
|
例:郭嘉、司马懿等,受到伤害后发动技能时明置武将牌;
|
||||||
|
马超、黄忠等,使用【杀】指定一名角色为目标后,发动技能并明置武将牌;
|
||||||
|
孙权、甘宁等,在出牌阶段发动技能时明置武将牌;
|
||||||
|
|
||||||
|
没有明置武将牌的角色没有性别,任何与性别有关的技能和武器效果均不能对其发动。
|
||||||
|
|
||||||
|
有一张武将牌明置时,角色性别与明置的武将牌相同。当一名角色的两张武将牌均亮明后,性别与主将的武将牌相同。
|
||||||
|
|
||||||
|
没有明置武将牌的角色没有势力,明置一张武将牌后确定势力:与武将牌左上角所示的势力相同,或成为野心家。野心家用“野心家牌”表示。
|
||||||
|
|
||||||
|
野心家规则:
|
||||||
|
|
||||||
|
当一名角色明置武将牌确定势力时,若该势力的角色超过了游戏总玩家数的一半,则他视为野心家,拿取一张野心家牌表示。若之后仍然有该势力的角色明置武将牌,均视为野心家。野心家为单独的一种势力,与其他角色的势力均不同。他(们)需要杀死所有其他角色,成为唯一的存活者。
|
||||||
|
|
||||||
|
注意:野心家与野心家之间也是不同势力。
|
||||||
|
|
||||||
|
例:
|
||||||
|
|
||||||
|
★ 6 人、7 人游戏时,当出现第四名同势力角色时,该角色及之后明置的该势力角色均成为野心家。
|
||||||
|
|
||||||
|
★ 8 人、9 人游戏时,当出现第五名同势力角色时,该角色及之后明置的该势力角色均成为野心家。
|
||||||
|
|
||||||
|
## 珠联璧合
|
||||||
|
|
||||||
|
珠联璧合表示了部分武将之间的特殊联系。
|
||||||
|
|
||||||
|
武将牌中下方的其他武将姓名表示了可以和此武将牌形成珠联璧合的其他武将。
|
||||||
|
|
||||||
|
若你选择的两张武将牌形成珠联璧合,则在第一次两张武将牌均明置时,可以立即选择摸两张牌或回复1 点体力。
|
||||||
|
|
||||||
|
若触发珠联璧合时正在进行其他事件的结算,则先进行“珠联璧合”的选择,再继续结算该事件。
|
||||||
|
]==]
|
||||||
|
|
||||||
|
local heg
|
||||||
|
|
||||||
|
---@class HegLogic: GameLogic
|
||||||
|
local HegLogic = {}
|
||||||
|
|
||||||
|
function HegLogic:assignRoles()
|
||||||
|
local room = self.room
|
||||||
|
for _, p in ipairs(room.players) do
|
||||||
|
p.role_shown = true
|
||||||
|
p.role = "hidden"
|
||||||
|
room:broadcastProperty(p, "role")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- for adjustSeats
|
||||||
|
room.players[1].role = "lord"
|
||||||
|
end
|
||||||
|
|
||||||
|
function HegLogic:chooseGenerals()
|
||||||
|
local room = self.room
|
||||||
|
local generalNum = math.max(room.settings.generalNum, 6)
|
||||||
|
|
||||||
|
local lord = room:getLord()
|
||||||
|
room.current = lord
|
||||||
|
lord.role = "hidden"
|
||||||
|
|
||||||
|
local nonlord = room.players
|
||||||
|
local generals = Fk:getGeneralsRandomly(#nonlord * generalNum)
|
||||||
|
-- table.shuffle(generals)
|
||||||
|
for _, p in ipairs(nonlord) do
|
||||||
|
local arg = { map = table.map }
|
||||||
|
for i = 1, generalNum do
|
||||||
|
table.insert(arg, table.remove(generals, 1))
|
||||||
|
end
|
||||||
|
table.sort(arg, function(a, b) return a.kingdom > b.kingdom end)
|
||||||
|
|
||||||
|
for idx, _ in ipairs(arg) do
|
||||||
|
if arg[idx].kingdom == arg[idx + 1].kingdom then
|
||||||
|
p.default_reply = { arg[idx].name, arg[idx + 1].name }
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
arg = arg:map(function(g) return g.name end)
|
||||||
|
p.request_data = json.encode({ arg, 2, true })
|
||||||
|
end
|
||||||
|
|
||||||
|
room:notifyMoveFocus(nonlord, "AskForGeneral")
|
||||||
|
room:doBroadcastRequest("AskForGeneral", nonlord)
|
||||||
|
for _, p in ipairs(nonlord) do
|
||||||
|
local general, deputy
|
||||||
|
if p.general == "" and p.reply_ready then
|
||||||
|
local generals = json.decode(p.client_reply)
|
||||||
|
general = generals[1]
|
||||||
|
deputy = generals[2]
|
||||||
|
room:setPlayerGeneral(p, general, true)
|
||||||
|
room:setDeputyGeneral(p, deputy)
|
||||||
|
else
|
||||||
|
general = p.default_reply[1]
|
||||||
|
deputy = p.default_reply[2]
|
||||||
|
end
|
||||||
|
|
||||||
|
p:setMark("__heg_general", general)
|
||||||
|
p:setMark("__heg_deputy", deputy)
|
||||||
|
p:doNotify("SetPlayerMark", json.encode{ p.id, "__heg_general", general})
|
||||||
|
p:doNotify("SetPlayerMark", json.encode{ p.id, "__heg_deputy", deputy})
|
||||||
|
|
||||||
|
room:setPlayerGeneral(p, "anjiang", true)
|
||||||
|
room:setDeputyGeneral(p, "anjiang")
|
||||||
|
|
||||||
|
p.default_reply = ""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function HegLogic:broadcastGeneral()
|
||||||
|
local room = self.room
|
||||||
|
local players = room.players
|
||||||
|
|
||||||
|
for _, p in ipairs(players) do
|
||||||
|
assert(p.general ~= "")
|
||||||
|
local general = Fk.generals[p:getMark("__heg_general")]
|
||||||
|
local deputy = Fk.generals[p:getMark("__heg_deputy")]
|
||||||
|
p.maxHp = math.floor((deputy.maxHp + general.maxHp) / 2)
|
||||||
|
p.hp = math.floor((deputy.hp + general.hp) / 2)
|
||||||
|
-- p.shield = math.min(general.shield + deputy.shield, 5)
|
||||||
|
p.shield = 0
|
||||||
|
-- TODO: setup AI here
|
||||||
|
|
||||||
|
room:broadcastProperty(p, "general")
|
||||||
|
room:broadcastProperty(p, "deputyGeneral")
|
||||||
|
room:broadcastProperty(p, "maxHp")
|
||||||
|
room:broadcastProperty(p, "hp")
|
||||||
|
room:broadcastProperty(p, "shield")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function HegLogic:attachSkillToPlayers()
|
||||||
|
local room = self.room
|
||||||
|
local players = room.players
|
||||||
|
|
||||||
|
room:handleAddLoseSkills(players[1], "#_heg_invalid", nil, false, true)
|
||||||
|
|
||||||
|
local addHegSkills = function(player, skillName)
|
||||||
|
local skill = Fk.skills[skillName]
|
||||||
|
if skill.lordSkill and (player.role ~= "lord" or #room.players < 5) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- room:handleAddLoseSkills(player, skillName, nil, false)
|
||||||
|
player:doNotify("AddSkill", json.encode{ player.id, skillName })
|
||||||
|
|
||||||
|
if skill:isInstanceOf(TriggerSkill) or table.find(skill.related_skills,
|
||||||
|
function(s) return s:isInstanceOf(TriggerSkill) end) then
|
||||||
|
player:doNotify("AddSkill", json.encode{ player.id, skillName, true })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, p in ipairs(room.alive_players) do
|
||||||
|
local general = Fk.generals[p:getMark("__heg_general")]
|
||||||
|
local skills = general.skills
|
||||||
|
for _, s in ipairs(skills) do
|
||||||
|
addHegSkills(p, s.name)
|
||||||
|
end
|
||||||
|
for _, sname in ipairs(general.other_skills) do
|
||||||
|
addHegSkills(p, sname)
|
||||||
|
end
|
||||||
|
|
||||||
|
local deputy = Fk.generals[p:getMark("__heg_deputy")]
|
||||||
|
if deputy then
|
||||||
|
skills = deputy.skills
|
||||||
|
for _, s in ipairs(skills) do
|
||||||
|
addHegSkills(p, s.name)
|
||||||
|
end
|
||||||
|
for _, sname in ipairs(deputy.other_skills) do
|
||||||
|
addHegSkills(p, sname)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
room:setTag("SkipNormalDeathProcess", true)
|
||||||
|
end
|
||||||
|
|
||||||
|
local heg_getlogic = function()
|
||||||
|
local h = GameLogic:subclass("HegLogic")
|
||||||
|
for k, v in pairs(HegLogic) do
|
||||||
|
h[k] = v
|
||||||
|
end
|
||||||
|
return h
|
||||||
|
end
|
||||||
|
|
||||||
|
local heg_invalid = fk.CreateInvaliditySkill{
|
||||||
|
name = "#_heg_invalid",
|
||||||
|
invalidity_func = function(self, player, skill)
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
local function getWinnerHeg(victim)
|
||||||
|
local room = victim.room
|
||||||
|
local alive = room.alive_players
|
||||||
|
if #alive == 1 then
|
||||||
|
local p = alive[1]
|
||||||
|
p:revealGeneral(false)
|
||||||
|
p:revealGeneral(true)
|
||||||
|
return p.kingdom
|
||||||
|
end
|
||||||
|
|
||||||
|
local winner = alive[1].kingdom
|
||||||
|
if winner == "unknown" then return "" end
|
||||||
|
for _, p in ipairs(alive) do
|
||||||
|
if p.kingdom ~= winner or p.kingdom == "wild" then
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return winner
|
||||||
|
end
|
||||||
|
|
||||||
|
local heg_rule = fk.CreateTriggerSkill{
|
||||||
|
name = "#heg_rule",
|
||||||
|
priority = 0.001,
|
||||||
|
events = {fk.TurnStart, fk.GameOverJudge, fk.BuryVictim},
|
||||||
|
can_trigger = function(self, event, target, player, data)
|
||||||
|
return target == player
|
||||||
|
end,
|
||||||
|
on_trigger = function(self, event, target, player, data)
|
||||||
|
local room = player.room
|
||||||
|
if event == fk.TurnStart then
|
||||||
|
local choices = {}
|
||||||
|
if player.general == "anjiang" then
|
||||||
|
table.insert(choices, "revealMain")
|
||||||
|
end
|
||||||
|
if player.deputyGeneral == "anjiang" then
|
||||||
|
table.insert(choices, "revealDeputy")
|
||||||
|
end
|
||||||
|
if #choices == 0 then return end
|
||||||
|
if #choices == 2 then table.insert(choices, "revealAll") end
|
||||||
|
table.insert(choices, "Cancel")
|
||||||
|
|
||||||
|
local choice = room:askForChoice(player, choices, self.name)
|
||||||
|
if choice == "revealMain" then player:revealGeneral(false)
|
||||||
|
elseif choice == "revealDeputy" then player:revealGeneral(true)
|
||||||
|
elseif choice == "revealAll" then
|
||||||
|
player:revealGeneral(false)
|
||||||
|
player:revealGeneral(true)
|
||||||
|
end
|
||||||
|
elseif event == fk.GameOverJudge then
|
||||||
|
player:revealGeneral(false)
|
||||||
|
player:revealGeneral(true)
|
||||||
|
local winner = getWinnerHeg(player)
|
||||||
|
if winner ~= "" then
|
||||||
|
room:gameOver("hidden")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
room:setTag("SkipGameRule", true)
|
||||||
|
elseif event == fk.BuryVictim then
|
||||||
|
local damage = data.damage
|
||||||
|
if damage and damage.from then
|
||||||
|
local killer = damage.from
|
||||||
|
if killer.kingdom == "unknown" then return end
|
||||||
|
|
||||||
|
local victim = damage.to
|
||||||
|
if killer.kingdom == victim.kingdom then
|
||||||
|
killer:throwAllCards("he")
|
||||||
|
else
|
||||||
|
killer:drawCards(victim.kingdom == "wild" and 1 or
|
||||||
|
#table.filter(room.alive_players, function(p)
|
||||||
|
return p.kingdom == victim.kingdom
|
||||||
|
end) + 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
Fk:addSkill(heg_rule)
|
||||||
|
|
||||||
|
heg = fk.CreateGameMode{
|
||||||
|
name = "heg_mode",
|
||||||
|
minPlayer = 2,
|
||||||
|
maxPlayer = 8,
|
||||||
|
rule = heg_rule,
|
||||||
|
logic = heg_getlogic,
|
||||||
|
}
|
||||||
|
|
||||||
|
Fk:loadTranslationTable{
|
||||||
|
["heg_mode"] = "国战测试版",
|
||||||
|
[":heg_mode"] = heg_description,
|
||||||
|
["wild"] = "野心家",
|
||||||
|
["#heg_rule"] = "国战规则",
|
||||||
|
["revealMain"] = "明置主将",
|
||||||
|
["revealDeputy"] = "明置副将",
|
||||||
|
["revealAll"] = "背水:全部明置",
|
||||||
|
}
|
||||||
|
|
||||||
|
return heg
|
BIN
packages/standard/image/generals/anjiang.jpg
Normal file
After Width: | Height: | Size: 6.9 KiB |
|
@ -1108,6 +1108,17 @@ local role_mode = fk.CreateGameMode{
|
||||||
}
|
}
|
||||||
extension:addGameMode(role_mode)
|
extension:addGameMode(role_mode)
|
||||||
|
|
||||||
|
local anjiang = General(extension, "anjiang", "unknown", 5)
|
||||||
|
anjiang.gender = General.Agender
|
||||||
|
anjiang.total_hidden = true
|
||||||
|
|
||||||
|
Fk:loadTranslationTable{
|
||||||
|
["anjiang"] = "暗将",
|
||||||
|
}
|
||||||
|
|
||||||
|
local heg_mode = require "packages.standard.hegemony"
|
||||||
|
extension:addGameMode(heg_mode)
|
||||||
|
|
||||||
-- load translations of this package
|
-- load translations of this package
|
||||||
dofile "packages/standard/i18n/init.lua"
|
dofile "packages/standard/i18n/init.lua"
|
||||||
|
|
||||||
|
|
|
@ -816,7 +816,7 @@ Item {
|
||||||
deputyGeneral: "",
|
deputyGeneral: "",
|
||||||
screenName: Self.screenName,
|
screenName: Self.screenName,
|
||||||
role: "unknown",
|
role: "unknown",
|
||||||
kingdom: "qun",
|
kingdom: "unknown",
|
||||||
netstate: "online",
|
netstate: "online",
|
||||||
maxHp: 0,
|
maxHp: 0,
|
||||||
hp: 0,
|
hp: 0,
|
||||||
|
@ -841,7 +841,7 @@ Item {
|
||||||
deputyGeneral: "",
|
deputyGeneral: "",
|
||||||
screenName: "",
|
screenName: "",
|
||||||
role: "unknown",
|
role: "unknown",
|
||||||
kingdom: "qun",
|
kingdom: "unknown",
|
||||||
netstate: "online",
|
netstate: "online",
|
||||||
maxHp: 0,
|
maxHp: 0,
|
||||||
hp: 0,
|
hp: 0,
|
||||||
|
|
|
@ -11,6 +11,7 @@ GraphicsBox {
|
||||||
property var choices: []
|
property var choices: []
|
||||||
property var selectedItem: []
|
property var selectedItem: []
|
||||||
property bool loaded: false
|
property bool loaded: false
|
||||||
|
property bool needSameKingdom: false
|
||||||
|
|
||||||
ListModel {
|
ListModel {
|
||||||
id: generalList
|
id: generalList
|
||||||
|
@ -115,10 +116,12 @@ GraphicsBox {
|
||||||
|
|
||||||
GeneralCardItem {
|
GeneralCardItem {
|
||||||
name: model.name
|
name: model.name
|
||||||
selectable: true
|
//enabled: //!(choices[0] && choices[0].kingdom !== this.kingdom)
|
||||||
|
selectable: !(selectedItem[0] && selectedItem[0].kingdom !== kingdom)
|
||||||
draggable: true
|
draggable: true
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
if (!selectable) return;
|
||||||
let toSelect = true;
|
let toSelect = true;
|
||||||
for (let i = 0; i < selectedItem.length; i++) {
|
for (let i = 0; i < selectedItem.length; i++) {
|
||||||
if (selectedItem[i] === this) {
|
if (selectedItem[i] === this) {
|
||||||
|
@ -149,7 +152,7 @@ GraphicsBox {
|
||||||
selectedItem = [];
|
selectedItem = [];
|
||||||
for (i = 0; i < generalList.count; i++) {
|
for (i = 0; i < generalList.count; i++) {
|
||||||
item = generalCardList.itemAt(i);
|
item = generalCardList.itemAt(i);
|
||||||
if (item.y > splitLine.y)
|
if (item.y > splitLine.y && item.selectable)
|
||||||
selectedItem.push(item);
|
selectedItem.push(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,6 +184,7 @@ GraphicsBox {
|
||||||
|
|
||||||
for (i = 0; i < generalCardList.count; i++) {
|
for (i = 0; i < generalCardList.count; i++) {
|
||||||
item = generalCardList.itemAt(i);
|
item = generalCardList.itemAt(i);
|
||||||
|
item.selectable = needSameKingdom ? !(selectedItem[0] && (selectedItem[0].kingdom !== item.kingdom)) : true;
|
||||||
if (selectedItem.indexOf(item) != -1)
|
if (selectedItem.indexOf(item) != -1)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
|
@ -328,12 +328,23 @@ RowLayout {
|
||||||
cardSelected(-1);
|
cardSelected(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addSkill(skill_name) {
|
function addSkill(skill_name, prelight) {
|
||||||
skillPanel.addSkill(skill_name);
|
skillPanel.addSkill(skill_name, prelight);
|
||||||
}
|
}
|
||||||
|
|
||||||
function loseSkill(skill_name) {
|
function loseSkill(skill_name, prelight) {
|
||||||
skillPanel.loseSkill(skill_name);
|
skillPanel.loseSkill(skill_name, prelight);
|
||||||
|
}
|
||||||
|
|
||||||
|
function prelightSkill(skill_name, prelight) {
|
||||||
|
let btns = skillPanel.prelight_buttons;
|
||||||
|
for (let i = 0; i < btns.count; i++) {
|
||||||
|
let btn = btns.itemAt(i);
|
||||||
|
if (btn.orig === skill_name) {
|
||||||
|
btn.prelighted = prelight;
|
||||||
|
btn.enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function enableSkills(cname) {
|
function enableSkills(cname) {
|
||||||
|
|
|
@ -394,10 +394,10 @@ Item {
|
||||||
|
|
||||||
LimitSkillArea {
|
LimitSkillArea {
|
||||||
id: limitSkills
|
id: limitSkills
|
||||||
anchors.top: role.bottom
|
anchors.top: parent.top
|
||||||
anchors.left: role.left
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 2
|
anchors.topMargin: role.height + 2
|
||||||
anchors.leftMargin: -2
|
anchors.rightMargin: 30
|
||||||
}
|
}
|
||||||
|
|
||||||
GlowText {
|
GlowText {
|
||||||
|
|
|
@ -6,15 +6,20 @@ import QtQuick.Layouts
|
||||||
Flickable {
|
Flickable {
|
||||||
id: root
|
id: root
|
||||||
property alias skill_buttons: skill_buttons
|
property alias skill_buttons: skill_buttons
|
||||||
|
property alias prelight_buttons: prelight_buttons
|
||||||
|
|
||||||
clip: true
|
clip: true
|
||||||
contentWidth: panel.width
|
contentWidth: panel.width
|
||||||
contentHeight: panel.height
|
contentHeight: panel.height
|
||||||
contentX: contentWidth - width
|
contentX: contentWidth - width
|
||||||
width: Math.min(150, panel.width)
|
width: Math.min(180, panel.width)
|
||||||
height: Math.min(180, panel.height)
|
height: Math.min(200, panel.height)
|
||||||
flickableDirection: Flickable.AutoFlickIfNeeded
|
flickableDirection: Flickable.AutoFlickIfNeeded
|
||||||
|
|
||||||
|
ListModel {
|
||||||
|
id: prelight_skills
|
||||||
|
}
|
||||||
|
|
||||||
ListModel {
|
ListModel {
|
||||||
id: active_skills
|
id: active_skills
|
||||||
}
|
}
|
||||||
|
@ -25,10 +30,37 @@ Flickable {
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: panel
|
id: panel
|
||||||
width: Math.max(grid1.width, grid2.width)
|
width: Math.max(grid0.width, grid1.width, grid2.width)
|
||||||
height: grid1.height + grid2.height
|
height: grid0.height + grid1.height + grid2.height
|
||||||
|
Grid {
|
||||||
|
id: grid0
|
||||||
|
columns: 2
|
||||||
|
columnSpacing: 2
|
||||||
|
rowSpacing: 2
|
||||||
|
Repeater {
|
||||||
|
id: prelight_buttons
|
||||||
|
model: prelight_skills
|
||||||
|
onItemAdded: parent.forceLayout()
|
||||||
|
SkillButton {
|
||||||
|
skill: model.skill
|
||||||
|
type: "prelight"
|
||||||
|
enabled: !config.observing
|
||||||
|
orig: model.orig_skill
|
||||||
|
|
||||||
|
onPressedChanged: {
|
||||||
|
if (!pressed) return;
|
||||||
|
enabled = false;
|
||||||
|
ClientInstance.notifyServer("PrelightSkill", [
|
||||||
|
"prelight", orig, (!prelighted).toString()
|
||||||
|
].join(","));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Grid {
|
Grid {
|
||||||
id: grid1
|
id: grid1
|
||||||
|
anchors.top: grid0.bottom
|
||||||
columns: 2
|
columns: 2
|
||||||
columnSpacing: 2
|
columnSpacing: 2
|
||||||
rowSpacing: 2
|
rowSpacing: 2
|
||||||
|
@ -69,19 +101,46 @@ Flickable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addSkill(skill_name) {
|
function addSkill(skill_name, prelight) {
|
||||||
|
const modelContains = (m, e) => {
|
||||||
|
for (let i = 0; i < m.count; i++) {
|
||||||
|
if (m.get(i).orig_skill === e.orig_skill) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
let data = JSON.parse(Backend.callLuaFunction(
|
let data = JSON.parse(Backend.callLuaFunction(
|
||||||
"GetSkillData",
|
"GetSkillData",
|
||||||
[skill_name]
|
[skill_name]
|
||||||
));
|
));
|
||||||
|
|
||||||
|
if (prelight) {
|
||||||
|
if (!modelContains(prelight_skills, data))
|
||||||
|
prelight_skills.append(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (data.freq === "active") {
|
if (data.freq === "active") {
|
||||||
active_skills.append(data);
|
if (!modelContains(active_skills, data)) active_skills.append(data);
|
||||||
} else {
|
} else {
|
||||||
not_active_skills.append(data);
|
if (!modelContains(not_active_skills, data))
|
||||||
|
not_active_skills.append(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function loseSkill(skill_name) {
|
function loseSkill(skill_name, prelight) {
|
||||||
|
if (prelight) {
|
||||||
|
for (let i = 0; i < prelight_skills.count; i++) {
|
||||||
|
let item = prelight_skills.get(i);
|
||||||
|
if (item.orig_skill == skill_name) {
|
||||||
|
prelight_skills.remove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < active_skills.count; i++) {
|
for (let i = 0; i < active_skills.count; i++) {
|
||||||
let item = active_skills.get(i);
|
let item = active_skills.get(i);
|
||||||
if (item.orig_skill == skill_name) {
|
if (item.orig_skill == skill_name) {
|
||||||
|
|
|
@ -9,24 +9,34 @@ Item {
|
||||||
property string type: "active"
|
property string type: "active"
|
||||||
property string orig: ""
|
property string orig: ""
|
||||||
property bool pressed: false
|
property bool pressed: false
|
||||||
|
property bool prelighted: false
|
||||||
|
|
||||||
onEnabledChanged: {
|
onEnabledChanged: {
|
||||||
if (!enabled)
|
if (!enabled)
|
||||||
pressed = false;
|
pressed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
width: type === "active" ? Math.max(80, skill.width + 8) : skill.width
|
width: type !== "notactive" ? Math.max(80, skill.width + 8) : skill.width
|
||||||
height: type === "active" ? 36 : 24
|
height: type !== "notactive" ? 36 : 24
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
x: -13 - 120 * 0.166
|
x: -13 - 120 * 0.166
|
||||||
y: -6 - 55 * 0.166
|
y: -6 - 55 * 0.166
|
||||||
scale: 0.66
|
scale: 0.66
|
||||||
source: type !== "active" ? ""
|
source: type === "notactive" ? ""
|
||||||
: AppPath + "/image/button/skill/active/"
|
: AppPath + "/image/button/skill/" + type + "/"
|
||||||
+ (enabled ? (pressed ? "pressed" : "normal") : "disabled")
|
+ (enabled ? (pressed ? "pressed" : "normal") : "disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
visible: type === "prelight"
|
||||||
|
source: AppPath + "/image/button/skill/" +
|
||||||
|
(prelighted ? "prelight.png" : "unprelight.png")
|
||||||
|
transformOrigin: Item.TopLeft
|
||||||
|
x: -10
|
||||||
|
scale: 0.7
|
||||||
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
id: skill
|
id: skill
|
||||||
|
@ -54,7 +64,7 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
TapHandler {
|
TapHandler {
|
||||||
enabled: root.type === "active" && root.enabled
|
enabled: root.type !== "notactive" && root.enabled
|
||||||
onTapped: parent.pressed = !parent.pressed;
|
onTapped: parent.pressed = !parent.pressed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -573,15 +573,16 @@ callbacks["AskForGeneral"] = function(jsonData) {
|
||||||
let data = JSON.parse(jsonData);
|
let data = JSON.parse(jsonData);
|
||||||
let generals = data[0];
|
let generals = data[0];
|
||||||
let n = data[1];
|
let n = data[1];
|
||||||
|
let heg = data[2];
|
||||||
roomScene.promptText = Backend.translate("#AskForGeneral");
|
roomScene.promptText = Backend.translate("#AskForGeneral");
|
||||||
roomScene.state = "replying";
|
roomScene.state = "replying";
|
||||||
roomScene.popupBox.source = "RoomElement/ChooseGeneralBox.qml";
|
roomScene.popupBox.source = "RoomElement/ChooseGeneralBox.qml";
|
||||||
let box = roomScene.popupBox.item;
|
let box = roomScene.popupBox.item;
|
||||||
box.choiceNum = 1;
|
|
||||||
box.accepted.connect(() => {
|
box.accepted.connect(() => {
|
||||||
replyToServer(JSON.stringify(box.choices));
|
replyToServer(JSON.stringify(box.choices));
|
||||||
});
|
});
|
||||||
box.choiceNum = n;
|
box.choiceNum = n;
|
||||||
|
box.needSameKingdom = !!heg;
|
||||||
for (let i = 0; i < generals.length; i++)
|
for (let i = 0; i < generals.length; i++)
|
||||||
box.generalList.append({ "name": generals[i] });
|
box.generalList.append({ "name": generals[i] });
|
||||||
box.updatePosition();
|
box.updatePosition();
|
||||||
|
@ -751,8 +752,9 @@ callbacks["LoseSkill"] = function(jsonData) {
|
||||||
let data = JSON.parse(jsonData);
|
let data = JSON.parse(jsonData);
|
||||||
let id = data[0];
|
let id = data[0];
|
||||||
let skill_name = data[1];
|
let skill_name = data[1];
|
||||||
|
let prelight = data[2];
|
||||||
if (id === Self.id) {
|
if (id === Self.id) {
|
||||||
dashboard.loseSkill(skill_name);
|
dashboard.loseSkill(skill_name, prelight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -761,11 +763,20 @@ callbacks["AddSkill"] = function(jsonData) {
|
||||||
let data = JSON.parse(jsonData);
|
let data = JSON.parse(jsonData);
|
||||||
let id = data[0];
|
let id = data[0];
|
||||||
let skill_name = data[1];
|
let skill_name = data[1];
|
||||||
|
let prelight = data[2];
|
||||||
if (id === Self.id) {
|
if (id === Self.id) {
|
||||||
dashboard.addSkill(skill_name);
|
dashboard.addSkill(skill_name, prelight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
callbacks["PrelightSkill"] = function(jsonData) {
|
||||||
|
let data = JSON.parse(jsonData);
|
||||||
|
let skill_name = data[0];
|
||||||
|
let prelight = data[1];
|
||||||
|
|
||||||
|
dashboard.prelightSkill(skill_name, prelight);
|
||||||
|
}
|
||||||
|
|
||||||
// prompt: 'string:<src>:<dest>:<arg>:<arg2>'
|
// prompt: 'string:<src>:<dest>:<arg>:<arg2>'
|
||||||
function processPrompt(prompt) {
|
function processPrompt(prompt) {
|
||||||
let data = prompt.split(":");
|
let data = prompt.split(":");
|
||||||
|
|
|
@ -71,7 +71,7 @@ function getPhotoBack(kingdom) {
|
||||||
} else {
|
} else {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
return PHOTO_BACK_DIR + "qun";
|
return PHOTO_BACK_DIR + "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
function getGeneralCardDir(kingdom) {
|
function getGeneralCardDir(kingdom) {
|
||||||
|
|
|
@ -250,6 +250,8 @@ void Router::handlePacket(const QByteArray &rawPacket) {
|
||||||
room->addRobot(player);
|
room->addRobot(player);
|
||||||
} else if (command == "Chat") {
|
} else if (command == "Chat") {
|
||||||
room->chat(player, jsonData);
|
room->chat(player, jsonData);
|
||||||
|
} else if (command == "PrelightSkill") {
|
||||||
|
room->pushRequest(QString("%1,").arg(player->getId()) + jsonData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -286,6 +286,12 @@ void Room::chat(ServerPlayer *sender, const QString &jsonData) {
|
||||||
auto doc = String2Json(jsonData).object();
|
auto doc = String2Json(jsonData).object();
|
||||||
auto type = doc["type"].toInt();
|
auto type = doc["type"].toInt();
|
||||||
doc["sender"] = sender->getId();
|
doc["sender"] = sender->getId();
|
||||||
|
|
||||||
|
// 屏蔽.号,防止有人在HTML文本发链接,而正常发链接看不出来有啥改动
|
||||||
|
auto msg = doc["msg"].toString();
|
||||||
|
msg.replace(".", "․");
|
||||||
|
doc["msg"] = msg;
|
||||||
|
|
||||||
if (type == 1) {
|
if (type == 1) {
|
||||||
doc["userName"] = sender->getScreenName();
|
doc["userName"] = sender->getScreenName();
|
||||||
auto json = QJsonDocument(doc).toJson(QJsonDocument::Compact);
|
auto json = QJsonDocument(doc).toJson(QJsonDocument::Compact);
|
||||||
|
@ -296,6 +302,7 @@ void Room::chat(ServerPlayer *sender, const QString &jsonData) {
|
||||||
doBroadcastNotify(observers, "Chat", json);
|
doBroadcastNotify(observers, "Chat", json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
qInfo("[Chat] %s: %s", sender->getScreenName().toUtf8().constData(),
|
qInfo("[Chat] %s: %s", sender->getScreenName().toUtf8().constData(),
|
||||||
doc["msg"].toString().toUtf8().constData());
|
doc["msg"].toString().toUtf8().constData());
|
||||||
}
|
}
|
||||||
|
|