actioncycle - woodelf-treetop/rcwiki GitHub Wiki
| author | version | message |
|---|---|---|
| 宫晓博 | v1.0 | 2020年3月,战场重构,第一版指导理论 |
目前的同步方式,服务器计算结果,客户端负责表现。
将各种结果以行为的概念来定义,数据结构形如
<!-- 单个行为描述 -->
<bean name="CardAction">
<enum name="ACT_ATTACK" value="1" /> 攻击
<enum name="ACT_MOVE" value="2" /> 移动
<enum name="ACT_EXCHANGE" value="3" /> 交换
<enum name="ACT_SKILL" value="4" /> 技能
<enum name="ACT_PLAYCARD" value="5" /> 出牌
<enum name="ACT_OPENCARD" value="6" /> 翻牌,showcycle
<enum name="ACT_DEATH" value="7" /> 死亡
<enum name="ACT_PRESENT" value="8" /> 随从现身
<enum name="ACT_FOLLOWER_COVER" value="9" /> 随从盖牌上场
<enum name="ACT_EQUIP" value="10" /> 装备神器
<enum name="ACT_DRAW_CARD" value="11" /> 抽牌
<enum name="ACT_BUFF_EQUIP_EXPIRE" value="12" /> buff或者神器过期
<enum name="ACT_CARDPOOL_CALL" value="13" /> 从牌库召唤出牌,showcycle
<enum name="ACT_CARD_FROM_HEAVEN" value="14" /> 天降手牌
<enum name="ACT_DIRECT_BOARD" value="15" /> 抽到卡牌直接上场
<enum name="ACT_LEADER_TANK" value="16" /> 君主伤害转移给肉盾
<enum name="ACT_ATTACK_TRANSFER" value="17" /> 普攻伤害转移
<enum name="ACT_AP_DRAW" value="18" /> 花费行动力抽卡
<enum name="ACT_DEVOUR_OUT" value="19" /> 吐出吞噬单位
<enum name="ACT_FATIGUE_DIE" value="20" /> 疲劳机制导致输掉
<enum name="ACT_REBOUND" value="21" /> 反弹伤害
<enum name="ACT_RECALL" value="22" /> 虚空召回
<enum name="PARAM_SKILL" value="1" /> 技能id
<enum name="PARAM_SUB_SKILL" value="2" /> 子技能id
<enum name="PARAM_PLAYER" value="3" /> 玩家uid
<enum name="PARAM_CARD_UID" value="4" /> 卡牌uid
<enum name="PARAM_ATTACK_COUNTER" value="5" /> 普攻反击达成
<enum name="PARAM_CARD_CFGID" value="6" /> 卡牌配置id
<enum name="PARAM_AP" value="7" /> 行动力变化
<enum name="PARAM_ATTACK_TRANSFER_UID" value="8" /> 普攻伤害转移目标
<enum name="PARAM_NOT_INCLUDE_SENDER" value="9" /> 技能目标不包含发起者
<enum name="PARAM_USER_OP" value="10" /> 玩家直接操作导致的行为
<enum name="PARAM_SPELL_SHIFT_UID" value="11" /> 法术转移目标
<enum name="PARAM_NO_DEATHRATTLE" value="12" /> 不触发亡语的特效id
<enum name="PARAM_ATTACK_SHIFT_ORIGIN" value="13" /> 普攻的原始目标,注意跟8号区别
<enum name="PARAM_ENV_SKILL_RANGE" value="14" /> 受到ENV_SKILL_RANGE的影响
<enum name="SUB_PLAYCARD_OPEN" value="1" /> 出手牌的时候,开牌
<enum name="SUB_PLAYCARD_COVER" value="2" /> 出手牌的时候,盖牌
<enum name="SUB_OPENCARD_USER_OP" value="3" /> 玩家翻牌
<enum name="SUB_OPENCARD_ATTACK" value="4" /> 普攻翻牌
<enum name="SUB_OPENCARD_SKILL" value="5" /> 技能翻牌
<enum name="SUB_DIRECT_BOARD_OK" value="6" /> ACT_DIRECT_BOARD成功,牌上场
<enum name="SUB_DIRECT_BOARD_FAIL" value="7" /> ACT_DIRECT_BOARD失败,牌爆掉
<variable name="type" type="int" /> 行为类型
<variable name="subType" type="int" /> 子类型
<variable name="param" type="map" key="int" value="long" /> 行为参数
<variable name="sender" type="ActionSender" /> 行为发起者
<variable name="target" type="CombatPos" /> 行为目标点
<variable name="noTargetAffects" type="vector" value="ActionAffect" /> 无需receiver的效果
<variable name="gridReceiver" type="map" key="int" value="ActionGridReceiver" /> 影响的格子
<variable name="handReceiver" type="map" key="int" value="ActionHandReceiver" /> 影响的手牌
<variable name="newHandCards" type="vector" value="CombatHandCard" /> 新增的手牌
<variable name="newUnits" type="map" key="int" value="CombatUnit" /> 新增单位
<variable name="poolSize" type="map" key="long" value="short" /> 牌库数量变化
<variable name="playerBuffs" type="map" key="long" value="int" />
</bean>战斗中玩家的某次操作,往往导致一系列行为的发生,那么这些行为的发生顺序、发生时机,直接影响最终的计算结果,故此有必要形成清晰明确的约定。
约定的目标是战场规则可理解,战场逻辑可预测,战场行为健壮、稳定、完备。
一刹那者为一念,二十念为一瞬,二十瞬为一弹指,二十弹指为一罗预,二十罗预为一须臾,一日一夜有三十须臾。
《僧祗律》
姑且借用两个概念,瞬间,弹指。
可以认为同一瞬间内的行为,同时发生,在瞬间的结束才会进行死亡结算
然而并不意味着客户端同时表现一个瞬间,客户端将以CardAction为单位进行表现。诸如aoe伤害,多个单位同时移动,都可以用单个CardAction描述。
起始驱动行为(可能是玩家的一个操作,也可能是战场规则触发,比如回合计时到0导致回合结束),驱动行为可能导致衍生行为发生,衍生又导致新的衍生。。。同时触发的衍生位于同一个层级,每一个层级称之为一个瞬间
上述理论认为驱动导致一层一层的衍生,形成多个瞬间,实际上这是不够的。
衍生的概念比较宽泛,最常用的就是触发技。战场上发生了一个死亡事件,监听这个死亡事件的所有单位,都会对此做出响应,这个响应就是发动了一个技能,称之为触发技。如果触发技是一个多段技能,比如三发奥术飞弹,每打出一发都要等待其导致的衍生结束后,才打出下一发,这种情况瞬间已然无法表达。
所以有了弹指概念,弹指由一系列瞬间构成。多段技能,或者多段动作(例如抽多张牌),可以认为每一段对应一个弹指。
graph LR
行为-->行为层/瞬间
行为层/瞬间-->瞬间层/弹指
假设初始行为是一个4段技能A,那么对应4个弹指A1/A2/A3/A4,A1执行中导致触发技:3段技能B和2段技能C,A1执行完后要先执行B、C的弹指,然后才是A2。如果B2执行中,又触发一个3段技能D,那么最终的弹指执行顺序是:

实际表现就是一个树结构的先根遍历,衍生的弹指总是放在左子树,先于右子树执行。
除了驱动行为、衍生行为外,还有一种只给前端展示使用的纯表现行为。这种行为不会有任何衍生,只是展示需要而已。
比如翻开一张盖牌,实际分成两个动作,CardAction.ACT_OPENCARD和CardAction.ACT_PRESENT,CardAction.ACT_OPENCARD就是一个表现行为。因为对逻辑无任何影响,完全可以和逻辑行为放在同一瞬间。
在瞬间内的位置,根据需求灵活处理,一般紧靠逻辑行为,可在前可在后。
无论瞬间还是弹指,本质都是一种行为顺序的编排。对于客户端来讲,只是收到服务器发来的一组行为,依次做表现。行为编排只由服务器来控制。
而且这里的编排是一种狭义的概念,准确的说是,逻辑执行当前行为的过程中,产生的或者可能产生的新行为,他们的放置点问题。放置点所有的可能性:当前瞬间、下一瞬间、下一弹指。换个角度,也可以理解为行为的隔离程度。
- 触发技都在注册的时候都放入下一瞬间,顺序按照对事件监听的先后;实际执行的时候区分是否多段技能。
- 非原子性的多段技能,例如三发奥术飞弹,第一发放在当前执行瞬间,第二发、第三方,各形成一个新弹指。
- 原子性的多段技能,放入当前瞬间,多段紧靠在一起,其余行为后移。
- 多段动作,例如抽三张牌,每抽一张都是一个新的弹指。
另外,为了规避同一瞬间内的类似多个伤害命中同一个0血怪的情况,约定所有技能目标都忽略0血。
玩家需要查看曾经的操作以及影响,那么前端就要知道各行为间的联系,所以给行为添加唯一编号。
每个行为加两个属性,关联类型:分段关联,触发关联,关联的前置行为id。但是死亡行为特殊,死亡的每个单位都要指明原因行为的id。
前端根据联系规则,遍历整个行为序列,能得出一颗树结构。