Skip to content
This repository was archived by the owner on Jun 29, 2025. It is now read-only.

Latest commit

 

History

History
181 lines (102 loc) · 6.98 KB

File metadata and controls

181 lines (102 loc) · 6.98 KB

面向状态

将游戏状态储存在一个独立单元之中, 所有逻辑单元并不储存任何状态数据, 仅根据状态工作.

该设计思路和以往的最大区别在于, "状态和逻辑分离", 所有状态数据集中在同一个地方, 而不是分散在各个类的私有变量中.

流程

  1. 根据GameState激活一个GameInstance. GameInstance会在内部重新分区以方便location索引 & 激活所有监听器
  2. GameInstance处理所有事件
  3. GameInstance直接导出GameState
  4. 保存新状态

状态类约定

状态类应该提供以下接口:

GameState:
    __init__():初始化游戏状态.
    clone():返回状态的深拷贝.
    get(location):根据位置标记返回特定数据.
    getX():返回特定区域的所有数据.
    numpy():返回状态的numpy格式. 注意由于维度等, numpy()和clone()不一定能完全互相翻译. clone()保证复制游戏内部所有组件的状态数据, 但是numpy()可能囿于设计缺陷不能完全表征所有状态.

状态类是一个read-only类. 所有对于状态的修改都交予外部类.

状态类实现

这里要解决分区定义问题和numpy格式. 格式最好能向前兼容, 以免mhy更新

对于Char, Team buff, Support, Summon而言, 他们都是list of profile. 所谓profile就是(class name, paras).

我们需要一个工具函数能够根据class name去查找对应的类.

数据结构

GameState内包含以下内容.

定义: 函数名(str) + 参数的格式我们称为Item, 参数我们称为Profile. 约定: 仅首字母大写.

GameState包含两个PlayerState. PlayerState定义为:

Deck: 字符串列表. 30int Hand: 字符串列表. 10int Char: Item列表. 其中Profile(列表, 由CharIndexer协助定位)有内部结构: 分为HP, aura, weapon, artifact, talent, buff, history. HP,aura是int/str类型 weapon, artifact,talent是Item. buff又是一个item列表, 内部是各种角色状态. history是一个字典, 记录了该角色的每个技能当回合使用了多少次 & 总共使用了多少次. Teambuff, Support, Summon: Item列表 Dice: 字典, 每一位代表某种元素骰有多少个.

此外, GameState还有一个全局字段

History: 字典, 记录了全局信息

消息处理

不管是什么架构, 七圣的消息(事件,效应等等)都遵循"就近原则". 即最近的事件最先被处理, 它看上去像一个栈结构, 但是如果一个事件包含了多个效应, 却又存在固定顺序. 所以总的来说是宏观栈微观队列的数据结构.

结算顺序

装备区, 角色状态区, 状态区, 召唤区, 支援区.

刷新逻辑

如果某个token场上已有, 则不会改变其位置而仅刷新其状态.

无效化

eid=-1会让事件失效. 风与自由等卡可以利用此特性撤销某些事件.

风与自由会撤销SwapMove. 天狐显真的第二段伤害监听一个Over事件. 如果SwapMove被撤销, 此Over根本就不会产生. 天狐显真可以在监听到下一个SwapMove时重置自己的状态. 以此类推.

目前没有出现截胡正在处理的事件的情况(前几个监听器已经处理好了, 结果后面一个监听器突然给你截胡了), 不到万不得已不新增事件.

初始事件

如果一个事件不是其他任何事件引发的,那么source_id=-1

伤害流程

七圣的伤害状态机如下:

  1. 确定伤害类型(附魔检查在这一阶段)
  2. 进行元素反应. 此处发射反应消息.
  3. 确定伤害数值(增伤阶段).
  4. 施加伤害(莫娜泡影作用阶段).
  5. 进行减伤. 3,4,5可能出现状态耗尽消息.
  6. 造成伤害(此处是最终伤害). 此处发射伤害消息(可能出现死亡消息).

将一同并入事件处理机

伤害来源:

角色,召唤物,状态.

所有元素反应造成的伤害都视触发元素反应的单位为来源.

位置

精确描述七圣里的某个token的位置大致需要5个变量, 其中常用的是3个, 最后两个用于定位角色状态或者装备.

player id, area id, intra-area index, subarea id, offset

因为对于后者而言, 先要定位角色, 才能定位角色状态/装备.

一旦发生变动, 所有下标均需改变, 这也是最麻烦的地方.

伤害类

DMGTypeCheck可能转换成DMG或者Reaction(如果有反应). Reaction和DMG都会转换成DealDMG, Reaction可能涉及到其他副效果, 也可能不产生DealDMG(如行秋自水附着). 这种情况下默认传递dvalue = -1.

伤害来源定义:

(loc, reason).

loc指代一个token, 如角色,召唤物等. reason有两个,一个是self,一个是reaction. 扩散超导等造成的后台群伤的reason都是reaction,增伤牌可以通过该字段区分是否应该增伤.

Discard

这一点不用担心. EventHub中会快照每一次访问时的监听器列表, 同时发出Discard之前, 该监听器的alive一定会被置为False, 这样可以确保Discard前不会造成错误(就算幻读, 也会被alive阻止进一步行为). 快照保证了代码的数据一致性(不会出现迭代列表时修改列表本身的行为).

唯一的可能问题是外部强制Discard,例如降魔大圣. 但Discard执行前维持监听是正确行为(比如某个召唤物可以反制等); 除非该Discard连锁反应导致出现了其他Discard, 否则应当无误.

Discard幻读

设召唤区有两个召唤物且可用次数都为1, 则回合末后两个召唤区都会在EndPhase的驱动下释放一个Discard事件. 该事件包含的location将发生幻读现象.

如果使用引用location的方法, 当前一个召唤物击杀了对位敌人时, 会引发对方的callback(此时将会copy整个事件栈和游戏状态), 引用将失效, 发生幻读.

终极解决办法: Discard多传一个名字, 让最后一个下标作废.

这主要是性能上的考虑.

伤害与强制切人

存在两种情况: 第一种, 强制切人且伤害打死了前台. 如果先处理伤害,会导致先触发死亡回调(本来不应该的). 第二种, 强制切人且伤害打死了后台, 这时必须先处理伤害, 否则结算要出错.

预计算

例如, 在有须弥城的情况下, 刻晴雷楔的费用该如何计算?

注意雷楔属于一张手牌, 但是它却可以享受到针对技能的减费(须弥城).

由于七圣定义很混乱, 这里提出一种基于约定的减费/增费方案, 即每张卡标明自己能够享受的减费范围.

技能使用等不需要标记, 因为它定义很清楚, 就是使用技能.

元素反应

Pre-

欢迎来到预计算的世界.

预计算主要涉及到两方面: 费用预计算, 可行性判断.

费用预计算

发送事件DicePreCal进行计算.

可行性判断

这一部分主要是valids的实现. 它能够判断某张牌是否可行/某个行动是否可行.

特别地, 为了代码简单, 可行性判断需要先检查各个角色的history里面的frozen和pretrified字段(这可以等价为一个事件,但没必要).

还有saturated

对于行动, 检查费用+角色的history字段

对于卡牌, 检查费用+调用卡牌的valid方法.