diff --git a/internal/artifacts/marechausseehunter/marechausseehunter.go b/internal/artifacts/marechausseehunter/marechausseehunter.go index 137b58019..55820fbfe 100644 --- a/internal/artifacts/marechausseehunter/marechausseehunter.go +++ b/internal/artifacts/marechausseehunter/marechausseehunter.go @@ -78,7 +78,7 @@ func NewSet(c *core.Core, char *character.CharWrapper, count int, param map[stri c.Events.Subscribe(event.OnPlayerHPDrain, func(args ...interface{}) bool { di := args[0].(player.DrainInfo) - if c.Player.Active() != char.Index { + if di.ActorIndex != char.Index { return false } if di.ActorIndex != char.Index { diff --git a/internal/artifacts/vourukashasglow/vourukashasglow.go b/internal/artifacts/vourukashasglow/vourukashasglow.go index 879bfff58..6110f569e 100644 --- a/internal/artifacts/vourukashasglow/vourukashasglow.go +++ b/internal/artifacts/vourukashasglow/vourukashasglow.go @@ -48,10 +48,6 @@ func NewSet(c *core.Core, char *character.CharWrapper, count int, param map[stri if count >= 4 { counter := 0 - permStacks := param["stacks"] - if permStacks > 5 { - permStacks = 5 - } mStack := make([]float64, attributes.EndStatType) mStack[attributes.DmgP] = 0.08 addStackMod := func(idx int, duration int) { @@ -69,20 +65,18 @@ func NewSet(c *core.Core, char *character.CharWrapper, count int, param map[stri }, }) } - for i := 0; i < permStacks; i++ { - addStackMod(i, -1) - } c.Events.Subscribe(event.OnPlayerHPDrain, func(args ...interface{}) bool { di := args[0].(player.DrainInfo) - if di.Amount <= 0 { + if di.ActorIndex != char.Index { return false } - if di.ActorIndex != char.Index { + if di.Amount <= 0 { return false } - if counter >= permStacks { - addStackMod(counter, 300) + if !di.External { + return false } + addStackMod(counter, 300) counter = (counter + 1) % 5 return false }, fmt.Sprintf("vg-4pc-%v", char.Base.Key.String())) diff --git a/internal/weapons/claymore/beacon/beacon.go b/internal/weapons/claymore/beacon/beacon.go index 23855d670..5b2150bbd 100644 --- a/internal/weapons/claymore/beacon/beacon.go +++ b/internal/weapons/claymore/beacon/beacon.go @@ -10,6 +10,7 @@ import ( "github.com/genshinsim/gcsim/pkg/core/event" "github.com/genshinsim/gcsim/pkg/core/info" "github.com/genshinsim/gcsim/pkg/core/keys" + "github.com/genshinsim/gcsim/pkg/core/player" "github.com/genshinsim/gcsim/pkg/core/player/character" "github.com/genshinsim/gcsim/pkg/modifier" ) @@ -35,7 +36,6 @@ func NewWeapon(c *core.Core, char *character.CharWrapper, p info.WeaponProfile) r := p.Refine stackAtk := .15 + float64(r)*.05 - damaged := p.Params["damaged"] stackDuration := 480 //8s * 60 const skillKey = "beacon-of-the-reed-sea-skill" @@ -55,6 +55,7 @@ func NewWeapon(c *core.Core, char *character.CharWrapper, p info.WeaponProfile) }) mATK := make([]float64, attributes.EndStatType) + mATK[attributes.ATKP] = stackAtk c.Events.Subscribe(event.OnEnemyDamage, func(args ...interface{}) bool { atk := args[1].(*combat.AttackEvent) if atk.Info.ActorIndex != char.Index { @@ -64,7 +65,6 @@ func NewWeapon(c *core.Core, char *character.CharWrapper, p info.WeaponProfile) return false } - mATK[attributes.ATKP] = stackAtk char.AddStatMod(character.StatMod{ Base: modifier.NewBaseWithHitlag(skillKey, stackDuration), AffectedStat: attributes.ATKP, @@ -72,18 +72,31 @@ func NewWeapon(c *core.Core, char *character.CharWrapper, p info.WeaponProfile) return mATK, true }, }) - if damaged > 0 { - char.AddStatMod(character.StatMod{ - Base: modifier.NewBaseWithHitlag(damagedKey, stackDuration), - AffectedStat: attributes.ATKP, - Amount: func() ([]float64, bool) { - return mATK, true - }, - }) + + return false + }, fmt.Sprintf("beacon-of-the-reed-sea-enemy-%v", char.Base.Key.String())) + + c.Events.Subscribe(event.OnPlayerHPDrain, func(args ...interface{}) bool { + di := args[0].(player.DrainInfo) + if di.ActorIndex != char.Index { + return false + } + if di.Amount <= 0 { + return false + } + if !di.External { + return false } + char.AddStatMod(character.StatMod{ + Base: modifier.NewBaseWithHitlag(damagedKey, stackDuration), + AffectedStat: attributes.ATKP, + Amount: func() ([]float64, bool) { + return mATK, true + }, + }) return false - }, fmt.Sprintf("beacon-of-the-reed-sea-%v", char.Base.Key.String())) + }, fmt.Sprintf("beacon-of-the-reed-sea-player-%v", char.Base.Key.String())) return w, nil } diff --git a/internal/weapons/sword/alley/alley.go b/internal/weapons/sword/alley/alley.go index ae9e254e5..098513323 100644 --- a/internal/weapons/sword/alley/alley.go +++ b/internal/weapons/sword/alley/alley.go @@ -2,7 +2,6 @@ package alley import ( "fmt" - "math" "github.com/genshinsim/gcsim/pkg/core" "github.com/genshinsim/gcsim/pkg/core/attributes" @@ -18,7 +17,6 @@ func init() { core.RegisterWeaponFunc(keys.TheAlleyFlash, NewWeapon) } -// Upon damaging an opponent, increases CRIT Rate by 8/10/12/14/16%. Max 5 stacks. A CRIT Hit removes all stacks. type Weapon struct { Index int c *core.Core @@ -29,15 +27,6 @@ const lockoutKey = "alley-flash-lockout" func (w *Weapon) SetIndex(idx int) { w.Index = idx } func (w *Weapon) Init() error { return nil } -func (w *Weapon) selfDisable(lambda float64) func() { - return func() { - //disable for 5 sec - w.char.AddStatus(lockoutKey, 300, true) - //-ln(U)/lambda` (where U~Uniform[0,1]). - next := int(math.Log(w.c.Rand.Float64()) / lambda) - w.c.Tasks.Add(w.selfDisable(lambda), next) - } -} func NewWeapon(c *core.Core, char *character.CharWrapper, p info.WeaponProfile) (info.Weapon, error) { w := &Weapon{ @@ -46,25 +35,17 @@ func NewWeapon(c *core.Core, char *character.CharWrapper, p info.WeaponProfile) } r := p.Refine - //allow user to periodically lock out this weapon (just to screw around with bennett) - //follows poisson distribution, user provides lambda: - //https://stackoverflow.com/questions/6527345/simulating-poisson-waiting-times - if lambda, ok := p.Params["lambda"]; ok { - //user supplied lambda should be per min, so we need to scale this down by *60*60 - l := float64(lambda) / 3600.0 - //queue tasks to disable - next := int(-math.Log(1-w.c.Rand.Float64()) / l) - c.Tasks.Add(w.selfDisable(l), next) - } - c.Events.Subscribe(event.OnPlayerHPDrain, func(args ...interface{}) bool { di := args[0].(player.DrainInfo) - if !di.External { + if di.ActorIndex != char.Index { return false } if di.Amount <= 0 { return false } + if !di.External { + return false + } w.char.AddStatus(lockoutKey, 300, true) return false }, fmt.Sprintf("alleyflash-%v", char.Base.Key.String())) diff --git a/internal/weapons/sword/aquila/aquila.go b/internal/weapons/sword/aquila/aquila.go index 5f104fac0..b549ebc4e 100644 --- a/internal/weapons/sword/aquila/aquila.go +++ b/internal/weapons/sword/aquila/aquila.go @@ -57,7 +57,7 @@ func NewWeapon(c *core.Core, char *character.CharWrapper, p info.WeaponProfile) if di.Amount <= 0 { return false } - if c.Player.Active() != char.Index { + if di.ActorIndex != char.Index { return false } if char.StatusIsActive(icdKey) { diff --git a/pkg/core/info/actionlist.go b/pkg/core/info/actionlist.go index ce0e5e4d4..d8bc62443 100644 --- a/pkg/core/info/actionlist.go +++ b/pkg/core/info/actionlist.go @@ -4,6 +4,7 @@ import ( "encoding/json" "log" + "github.com/genshinsim/gcsim/pkg/core/attributes" "github.com/genshinsim/gcsim/pkg/core/keys" ) @@ -13,6 +14,7 @@ type ActionList struct { Characters []CharacterProfile `json:"characters"` InitialChar keys.Char `json:"initial"` Energy EnergySettings `json:"energy_settings"` + Hurt HurtSettings `json:"hurt_settings"` Settings SimulatorSettings `json:"settings"` Errors []error `json:"-"` //These represents errors preventing ActionList from being executed ErrorMsgs []string `json:"errors"` @@ -27,6 +29,17 @@ type EnergySettings struct { LastEnergyDrop int `json:"last_energy_drop"` } +type HurtSettings struct { + Active bool `json:"active"` + Once bool `json:"once"` + Start int `json:"start"` + End int `json:"end"` + Min float64 `json:"min"` + Max float64 `json:"max"` + Element attributes.Element `json:"element"` + LastHurt int `json:"last_hurt"` +} + type SimulatorSettings struct { Duration float64 `json:"-"` DamageMode bool `json:"damage_mode"` diff --git a/pkg/gcs/ast/item.go b/pkg/gcs/ast/item.go index 2aa82da49..f70257c85 100644 --- a/pkg/gcs/ast/item.go +++ b/pkg/gcs/ast/item.go @@ -107,6 +107,7 @@ const ( keywordEnergy // energy keywordParticleThreshold // particle_threshold keywordParticleDropCount // particle_drop_count + keywordHurt // hurt // Keywords specific to gcsim appears after this itemKeys diff --git a/pkg/gcs/ast/keys.go b/pkg/gcs/ast/keys.go index 1227fb6ae..946c3d8d1 100644 --- a/pkg/gcs/ast/keys.go +++ b/pkg/gcs/ast/keys.go @@ -41,6 +41,7 @@ var key = map[string]TokenType{ "particle_drop_count": keywordParticleDropCount, "resist": keywordResist, "energy": keywordEnergy, + "hurt": keywordHurt, //commands //team keywords //flags diff --git a/pkg/gcs/ast/parse.go b/pkg/gcs/ast/parse.go index 5d632d42d..d47fc1c6e 100644 --- a/pkg/gcs/ast/parse.go +++ b/pkg/gcs/ast/parse.go @@ -165,6 +165,9 @@ func parseRows(p *Parser) (parseFn, error) { case keywordEnergy: p.next() return parseEnergy, nil + case keywordHurt: + p.next() + return parseHurt, nil case keywordOptions: p.next() return parseOptions, nil diff --git a/pkg/gcs/ast/parseHurt.go b/pkg/gcs/ast/parseHurt.go new file mode 100644 index 000000000..24b1a586c --- /dev/null +++ b/pkg/gcs/ast/parseHurt.go @@ -0,0 +1,158 @@ +package ast + +import ( + "errors" + "fmt" +) + +func parseHurt(p *Parser) (parseFn, error) { + //hurt once interval=300 amount=1,300 element=phys #once at frame 300 (or nearest) + //hurt every interval=480,720 amount=1,300 element=phys #randomly 1 to 300 dmg every 480 to 720 frames + n := p.next() + switch n.Typ { + case itemIdentifier: + switch n.Val { + case "once": + return parseHurtOnce, nil + case "every": + return parseHurtEvery, nil + default: + return nil, fmt.Errorf("ln%v: unrecognized option specified: %v", n.line, n.Val) + } + case itemTerminateLine: + return parseRows, nil + default: + return nil, fmt.Errorf("ln%v: unrecognized token parsing options: %v", n.line, n) + } +} + +func parseHurtOnce(p *Parser) (parseFn, error) { + //hurt once interval=300 amount=1,300 element=phys #once at frame 300 + var err error + p.res.Hurt.Active = true + p.res.Hurt.Once = true + + for n := p.next(); n.Typ != itemEOF; n = p.next() { + switch n.Typ { + case itemIdentifier: + switch n.Val { + case "interval": + n, err = p.acceptSeqReturnLast(itemAssign, itemNumber) + if err == nil { + p.res.Hurt.Start, err = itemNumberToInt(n) + } + case "amount": + err := parseHurtAmount(p) + if err != nil { + return nil, err + } + case "element": + err := parseHurtElement(p) + if err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("ln%v: unrecognized hurt event specified: %v", n.line, n.Val) + } + case itemTerminateLine: + return parseRows, nil + default: + return nil, fmt.Errorf("ln%v: unrecognized token parsing hurt event: %v", n.line, n) + } + if err != nil { + return nil, err + } + } + return nil, errors.New("unexpected end of line while parsing hurt event") +} + +func parseHurtEvery(p *Parser) (parseFn, error) { + //hurt every interval=480,720 amount=1,300 element=phys #randomly 1 to 300 dmg every 480 to 720 frames + var err error + p.res.Hurt.Active = true + p.res.Hurt.Once = false + + for n := p.next(); n.Typ != itemEOF; n = p.next() { + switch n.Typ { + case itemIdentifier: + switch n.Val { + case "interval": + n, err = p.acceptSeqReturnLast(itemAssign, itemNumber) + if err != nil { + return nil, err + } + p.res.Hurt.Start, err = itemNumberToInt(n) + if err != nil { + return nil, err + } + + n, err = p.acceptSeqReturnLast(itemComma, itemNumber) + if err != nil { + return nil, err + } + p.res.Hurt.End, err = itemNumberToInt(n) + if err != nil { + return nil, err + } + case "amount": + err := parseHurtAmount(p) + if err != nil { + return nil, err + } + case "element": + err := parseHurtElement(p) + if err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("ln%v: unrecognized hurt event specified: %v", n.line, n.Val) + } + case itemTerminateLine: + return parseRows, nil + default: + return nil, fmt.Errorf("ln%v: unrecognized token parsing hurt event: %v", n.line, n) + } + if err != nil { + return nil, err + } + } + return nil, errors.New("unexpected end of line while parsing hurt event") +} + +func parseHurtAmount(p *Parser) error { + item, err := p.acceptSeqReturnLast(itemAssign, itemNumber) + if err != nil { + return err + } + min, err := itemNumberToFloat64(item) + if err != nil { + return err + } + + item, err = p.acceptSeqReturnLast(itemComma, itemNumber) + if err != nil { + return err + } + max, err := itemNumberToFloat64(item) + if err != nil { + return err + } + + p.res.Hurt.Min = min + p.res.Hurt.Max = max + + return nil +} + +func parseHurtElement(p *Parser) error { + _, err := p.consume(itemAssign) + if err != nil { + return err + } + n := p.next() + if n.Typ != itemElementKey { + return fmt.Errorf(" bad token at line %v - %v: %v", n.line, n.pos, n) + } + p.res.Hurt.Element = eleKeys[n.Val] + return nil +} diff --git a/pkg/simulation/config.go b/pkg/simulation/config.go deleted file mode 100644 index 3de886580..000000000 --- a/pkg/simulation/config.go +++ /dev/null @@ -1,24 +0,0 @@ -package simulation - -import ( - "github.com/genshinsim/gcsim/pkg/core/attributes" -) - -type EnergyEvent struct { - Active bool - Once bool //how often - Start int - End int - Particles int - LastEnergyDrop int -} - -type HurtEvent struct { - Active bool - Once bool //how often - Start int // - End int - Min float64 - Max float64 - Ele attributes.Element -} diff --git a/pkg/simulation/run.go b/pkg/simulation/run.go index 667d4e0f0..231cf772d 100644 --- a/pkg/simulation/run.go +++ b/pkg/simulation/run.go @@ -262,6 +262,7 @@ func (s *Simulation) nextFrame() (bool, error) { return false, err } s.handleEnergy() + s.handleHurt() s.C.Events.Emit(event.OnTick) return s.stopCheck(), nil } diff --git a/pkg/simulation/setup.go b/pkg/simulation/setup.go index aea3e0f98..83b1ec808 100644 --- a/pkg/simulation/setup.go +++ b/pkg/simulation/setup.go @@ -16,6 +16,7 @@ import ( "github.com/genshinsim/gcsim/pkg/core/info" "github.com/genshinsim/gcsim/pkg/core/keys" "github.com/genshinsim/gcsim/pkg/core/player/character" + "github.com/genshinsim/gcsim/pkg/core/targets" "github.com/genshinsim/gcsim/pkg/enemy" "github.com/genshinsim/gcsim/pkg/gadget" "github.com/genshinsim/gcsim/pkg/modifier" @@ -343,3 +344,66 @@ func (s *Simulation) handleEnergy() { Write("energy_frame", s.C.F+f) } } + +func (s *Simulation) handleHurt() { + //hurt once interval=300 amount=1,300 element=phys #once at frame 300 (or nearest) + if s.cfg.Hurt.Active && s.cfg.Hurt.Once { + f := s.cfg.Hurt.Start + amt := s.cfg.Hurt.Min + s.C.Rand.Float64()*(s.cfg.Hurt.Max-s.cfg.Hurt.Min) + s.cfg.Hurt.Active = false + + ai := combat.AttackInfo{ + ActorIndex: s.C.Player.Active(), + Abil: "Hurt", + AttackTag: attacks.AttackTagNone, + ICDTag: attacks.ICDTagNone, + ICDGroup: attacks.ICDGroupDefault, + StrikeType: attacks.StrikeTypeDefault, + Durability: 0, + Element: s.cfg.Hurt.Element, + FlatDmg: amt, + } + ap := combat.NewSingleTargetHit(s.C.Combat.Player().Key()) + ap.SkipTargets[targets.TargettablePlayer] = false + ap.SkipTargets[targets.TargettableEnemy] = true + ap.SkipTargets[targets.TargettableGadget] = true + + s.C.QueueAttack(ai, ap, -1, f) // -1 to avoid snapshot + + s.C.Log.NewEventBuildMsg(glog.LogHurtEvent, -1, "hurt queued (once)"). + Write("last", s.cfg.Hurt.LastHurt). + Write("cfg", s.cfg.Hurt). + Write("amt", amt). + Write("hurt_frame", s.C.F+f) + } + //hurt every interval=480,720 amount=1,300 element=phys #randomly 1 to 300 dmg every 480 to 720 frames + if s.cfg.Hurt.Active && s.C.F-s.cfg.Hurt.LastHurt >= s.cfg.Hurt.Start { + f := s.C.Rand.Intn(s.cfg.Hurt.End - s.cfg.Hurt.Start) + amt := s.cfg.Hurt.Min + s.C.Rand.Float64()*(s.cfg.Hurt.Max-s.cfg.Hurt.Min) + s.cfg.Hurt.LastHurt = s.C.F + f + + ai := combat.AttackInfo{ + ActorIndex: s.C.Player.Active(), + Abil: "Hurt", + AttackTag: attacks.AttackTagNone, + ICDTag: attacks.ICDTagNone, + ICDGroup: attacks.ICDGroupDefault, + StrikeType: attacks.StrikeTypeDefault, + Durability: 0, + Element: s.cfg.Hurt.Element, + FlatDmg: amt, + } + ap := combat.NewSingleTargetHit(s.C.Combat.Player().Key()) + ap.SkipTargets[targets.TargettablePlayer] = false + ap.SkipTargets[targets.TargettableEnemy] = true + ap.SkipTargets[targets.TargettableGadget] = true + + s.C.QueueAttack(ai, ap, -1, f) // -1 to avoid snapshot + + s.C.Log.NewEventBuildMsg(glog.LogHurtEvent, -1, "hurt queued"). + Write("last", s.cfg.Hurt.LastHurt). + Write("cfg", s.cfg.Hurt). + Write("amt", amt). + Write("hurt_frame", s.C.F+f) + } +} diff --git a/ui/packages/docs/docs/guides/understanding_config_files.md b/ui/packages/docs/docs/guides/understanding_config_files.md index c9a9ae7eb..2932932de 100644 --- a/ui/packages/docs/docs/guides/understanding_config_files.md +++ b/ui/packages/docs/docs/guides/understanding_config_files.md @@ -242,15 +242,15 @@ Repeating `active ` multiple times or setting `active ` to different To execute a swap manually, see [here](#swaps). ::: -## Energy setting +## Energy Setting -Optionally, you can add a method of obtaining energy from enemies. The first, and easier way to to simply make energy drop every so often with the below syntax. +Optionally, you can add a method of obtaining energy from enemies. The first, and easier way is to simply make energy drop every so often with the below syntax. ``` energy every interval=480,720 amount=1; ``` -`interval` tells the sim how often to drop a particle. In this case, it drops a particles a random number between 8 and 12s after the last time it dropped a particle. (480 and 720 frames respectivele) `amount` tells it to drop one clear particle. +`interval` tells the sim how often to drop a particle. In this case, it drops a particles a random number between 8 and 12s after the last time it dropped a particle. (480 and 720 frames respectively). `amount` tells it to drop one clear particle. The second way to HP based drops. When you declare an enemy, you can also tell it to drop energy after it takes a certain amount of damage. @@ -266,6 +266,16 @@ For reference, a level 100 Maguu Kenki in Abyss would be target lvl=100 resist=0.1 particle_threshold=460000 particle_drop_count=3; ``` +## Hurt Setting + +Optionally, you can add a method of taking damage from the sim. + +``` +hurt every interval=480,720 amount=1,300 element=phys; +``` + +`interval` tells the sim how often to deal damage. In this case, it deals damage a random number between 8 and 12s after the last time it dealt damage. (480 and 720 frames respectively). `amount` tells it to deal between 1 and 300 damage. `element` tells it which element type the damage is. In this case it is physical damage. + ## gcsim Script (`gcsl`) The gcsim script is the core of the config file. It tells the simulator what actions to execute. At the very basic level, it's simply a written down instruction of the buttons you would press in game. For example: diff --git a/ui/packages/docs/docs/reference/config.md b/ui/packages/docs/docs/reference/config.md index 2cb281dea..63482ad61 100644 --- a/ui/packages/docs/docs/reference/config.md +++ b/ui/packages/docs/docs/reference/config.md @@ -47,6 +47,29 @@ energy once interval=300 amount=1; This drops 1 clear elemental particle once at frame 300. ::: +### Set hurt generation + +Example: +``` +hurt every interval=480,720 amount=1,300 element=phys; +``` +This means that gcsim will deal between 1 and 300 physical damage to the active character every 480 to 720 frames randomly. + +:::note +If multiple `hurt every` lines are added, then the values specified by the final one will be used. +::: + +:::info +Generating hurt only at a specific frame for one time can also be specified. +Multiple `hurt once` lines can be added to deal damage at different points in time. + +Example: +``` +hurt once interval=300 amount=1,300 element=phys; +``` +This will deal between 1 and 300 physical damage to the active character once at frame 300. +::: + ### Perform character, weapon, artifact setup Character data can be roughly broken into 4 parts: diff --git a/ui/packages/docs/src/components/Params/artifact_data.json b/ui/packages/docs/src/components/Params/artifact_data.json index 4ba680459..3b5f29f53 100644 --- a/ui/packages/docs/src/components/Params/artifact_data.json +++ b/ui/packages/docs/src/components/Params/artifact_data.json @@ -1,11 +1,4 @@ { - "vourukashasglow": [ - { - "ability": "Vourukasha's Glow", - "param": "stacks", - "desc": "Number of stacks for the 4pc affect to gain permanently, because Player taking dmg is not implemented yet. Default 0, maximum 5." - } - ], "huskofopulentdreams": [ { "ability": "Husk of Opulent Dreams", @@ -13,4 +6,4 @@ "desc": "Number of stacks for the 4pc affect to gain at the beginning. Default 0, maximum 4." } ] -} \ No newline at end of file +} diff --git a/ui/packages/docs/src/components/Params/weapon_data.json b/ui/packages/docs/src/components/Params/weapon_data.json index 8f6148765..dd3261f5a 100644 --- a/ui/packages/docs/src/components/Params/weapon_data.json +++ b/ui/packages/docs/src/components/Params/weapon_data.json @@ -6,13 +6,6 @@ "desc": "Number of stacks to gain at the beginning. Default 0, maximum 10." } ], - "beaconofthereedsea": [ - { - "ability": "Beacon of the Reed Sea", - "param": "damaged", - "desc": "0 for damaged passive never triggered (default), 1 for triggered every time skill passive is triggered." - } - ], "forestregalia": [ { "ability": "Forest Regalia", @@ -68,12 +61,5 @@ "param": "passive", "desc": "0 for passive disabled, 1 for enabled (default)." } - ], - "thealleyflash": [ - { - "ability": "The Alley Flash", - "param": "lambda", - "desc": "Lambda for Poisson distribution to periodically turn off the weapon passive." - } ] -} \ No newline at end of file +}