国战雏形
This commit is contained in:
notify 2023-04-23 21:10:07 +08:00 committed by GitHub
parent 5888df6e7c
commit a09023c487
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 824 additions and 60 deletions

71
docs/dev/hegemony.rst Normal file
View File

@ -0,0 +1,71 @@
.. SPDX-License-Identifier: GFDL-1.3-or-later
国战实现相关碎碎念
=================
国战流程与身份版基本相同,就有一些有区别:
- 双将,并且是同势力双将
- 开局所有人暗将状态
- 胜利判断和奖惩不同
- 亮将
- 预亮技能
下面来一个个分析一下。胜利和奖惩已经在22写过了不分析。
双将与同势力选将
-----------
双将好说,已经做完了。主要是同势力选将,可能需要单独写个处理函数吧。还有抽武将的时候也要避免出现没人同势力的情况啊。
简而言之就是UI加个同势力选项同时随机生成武将环节中如果可选人数小于X+1X为游戏内势力数一般为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。
调虎离山
-----
将目标设为死人即可(删除)
真正实现的话,可能还是就按照牌面描述来吧,但是要对距离计算和上家/下家做修改。前者可能还能用距离技顶一下,后者非改源码不可。

View File

@ -16,3 +16,4 @@ Dev文档
scenario.rst scenario.rst
todo.rst todo.rst
ui.rst ui.rst
hegemony.rst

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
image/photo/back/wild.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -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]
if not prelight then
target:loseSkill(skill) target:loseSkill(skill)
end
if skill.visible then if skill.visible then
ClientInstance:notifyUI("LoseSkill", jsonData) ClientInstance:notifyUI("LoseSkill", jsonData)
end end

View File

@ -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,8 +110,10 @@ 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
if not g.total_hidden then
table.insert(ret, g.name) table.insert(ret, g.name)
end end
end
return json.encode(ret) return json.encode(ret)
end end

View File

@ -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"] = "牌序",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 {
if (!modelContains(not_active_skills, data))
not_active_skills.append(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) {

View File

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

View File

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

View File

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

View File

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

View File

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