Skip to content

Battle Framework

Sunist edited this page Dec 16, 2022 · 1 revision

Battle Framework of GISB


本文档于 2022-12-16sunist-c 创建,版本0.0.1


Summary

要介绍GISB的战斗框架,我们需要先知道GISB的战斗框架的作用。

GISB的战斗框架负责从网络/cli等输入模块获取并解析玩家的战斗操作,并执行相关指令;GISB负责执行战斗阶段的所有动作,包括:

  • 骰子的投掷与重新投掷
  • 费用的计算与管理
  • 伤害(普通攻击/元素战技/元素爆发)的计算与执行
  • 装备(圣遗物/武器/天赋)的附加与切换
  • 事件(切换角色/攻击结束/使用食物等)的触发与回调
  • 元素反应的计算(类型计算/伤害计算/效果计算)与执行

GISB的战斗框架管理着所有的战斗相关实体,包括:

  • 玩家信息
    • 基本信息(UID/昵称);
    • 持有物品(骰子/持有卡牌/牌堆);
    • 协同效果(协同技能/协同Buff);
    • 在场信息(角色/支援/召唤物);
    • 前台Buff(防御Buff/事件效果)
  • 角色信息
    • 基本信息(ID/元素类型/武器类型);
    • 战斗信息(HP/最大HP/MP/最大MP/饱腹/装备/状态/元素附着);
    • 技能信息(技能效果/技能花费/技能伤害);
    • 角色Buff(攻击Buff/装备Buff)

Attack-Defence

大部份的战斗过程都是在攻击-防御操作中进行的,但是由于攻击-防御过程的特殊性,我们没办法将这个操作在一个角色内进行完成,所以攻击-防御过程均托管在GISB的战斗框架中。战斗过程中,核心的数据是AttackDamageContextDefenceDamageContext,它们都包括下面的核心结构:

type Damage struct {
    element ElementType // 伤害的元素类型
    amount uint // 伤害的数值
}

type DamageContext struct {
    damages map[*Character]Damage // 伤害表,主键为承受伤害的角色,值为具体伤害
}

在攻击防御过程中,GISB战斗框架的战斗流程如下:

  1. GISB战斗框架解析玩家操作,并确定玩家释放的技能
  2. GISB战斗框架根据技能生成基础伤害,类型为AttackDamageContext,包括伤害元素类型,伤害值与伤害目标
  3. GISB战斗框架将调用攻击发起玩家的前台角色的攻击BUFF事件链,生成相应的执行函数,对AttackDamageContext进行操作
  4. GISB战斗框架调用指定的规则实现,根据AttackDamageContext的元素类型与目标角色自身的元素附着,计算元素反应类型并对伤害做出相应修正
  5. GISB战斗框架将最终的AttackDamageContext传递给目标,并生成DefenceDamageContext
  6. GISB战斗框架调用目标玩家身上的防御BUFF事件链,生成相应的执行函数,对DefenceDamageContext进行操作
  7. GISB战斗框架对最终的DefenceDamageContext进行执行,扣除相应目标的HP并更新他们的状态
  8. GISB战斗框架对触发的元素反应效果(对方角色的元素反应效果与自身角色的元素反应效果)进行操作
  9. 操作执行完毕,阻塞并等待下个玩家指令

在GISB中,我们广泛地使用了中间件的思想与类似结构,让我们通过func (ctx *Context)这类HandlerFunc对我们相关过程的Context进行操作并能够传递到下层。

由于事件链的存在和Context的引入,我们可以很方便的对战斗过程进行把握,例如我们需要增加一个攻击BUFF,其作用为角色造成的伤害+1,如果为物理伤害则额外+1,我们就可以这么实现(此处代码仅为理解用,非实际结构或接口):

func Handler(ctx *AttackDamageContext) {
    ctx.damage.Add(1) // 伤害+1

    ctx.Continue() // 先让事件链后面的事件执行,等待执行完毕后判断是否为物理伤害

    if ctx.damage.ElementType == ElementNone {
        ctx.damage.Add(1) // 如果是物理伤害,则伤害+1
    }
}

同时,go的闭包特性也能在框架中使用,例如我们需要增加一个攻击BUFF,其作用为角色造成的伤害+1,若本回合没有进行过攻击,则伤害额外+1,那么我们可以这么实现:

type BuffEvent struct {
    activated bool
}

func (b *BuffEvent) Handler(ctx *AttackDamageContext) {
    if !b.activated {
        ctx.damage.Add(2) // 如果没有进行过攻击,则伤害+2
        b.activated = true // 将攻击信息传入事件
    } else {
        ctx.damage.Add(1) // 如果进行过攻击,则伤害+1
    }
}

当然,这个BUFF需要在每回合开始时重置activated,这个我们将在事件系统中做出额外说明。

防御BUFF也同理,此处我们就不过多赘述了。

Heal/Charge/Switch/EatFood/Equip

这一类操作仅涉及对当前角色的操作,只是具体细节有所不同罢了,他们的本质都是修改当前角色/玩家的相关数据,例如增加HP/MP,增加攻击BUFF等,此类操作GISB战斗框架均直接执行,只是具体的接口不一致而已,此处不做过多展开。

但此处重点介绍BUFF类操作,由于GISB战斗系统将BUFF分类托管于不同的队列中,以便于战斗系统进行统一操作,生成相应的函数链,所以在实现大多数卡时,我们都需要将BUFF正确地挂到相应的队列中去,下面是主要的事件队列的说明,详情请转到./model目录下查看相应源代码:

  • AttackModifiers 攻击修正队列,此队列托管于角色之下,因为每个角色的装备和天赋都不相同
  • DefenceModifiers 防御修正队列,此队列托管于玩家之下,因为只有前台角色能够享受护盾/减伤效果
  • CostModifiers 元素骰子费用修正队列,此队列托管于角色之下,因为圣遗物/料理效果只对一个角色有效
  • CooperativeAttackModifiers 协同攻击修正队列,此队列托管于玩家之下
  • SwitchModifiers 切换角色事件队列,此队列托管于玩家之下
  • CallbackChain 回调函数队列,此队列托管于角色之下,将在事件相关文档重点介绍

例如,我们想实现一个料理,其作用为当前角色攻击所消耗的无色骰子-1,我们可以这么实现:

type FoodEvent struct {}

func (f FoodEvent) Handler(ctx *CharacterContext) {
    ctx.AddCostModifier("${FoodEventName}", func (context *CostContext) {
        context.SubCosts(ElementSet{ElementCurrency: 1})
    })
}

CooperativeSkill

在对战中,我们还有一类技能比较特殊,它们会在角色攻击结束后追加一次操作,这就是协同技能。在GISB中,我们的协同技能依赖于事件系统进行触发,在触发后,GISB将如同普通的攻击-防御过程一样调用CooperativeSkill,以相同的流程发起攻击。

由于涉及回调函数与事件钩子,此处不针对事件系统给出示例。

Clone this wiki locally