Skip to content

Commit

Permalink
add gadget selection and fix ayaka ca, lisa q at c4, yae e (#1373)
Browse files Browse the repository at this point in the history
* move enemy in area selection to own file

* add gadget to area selection

* fix ayaka ca logic

* fix area selection not checking for nil gadgets

* fix lisa q targeting logic

* fix comment in select

* fix yae e targeting logic

* fix yae e tick if no target in area
  • Loading branch information
k0l11 authored Jun 15, 2023
1 parent de6aca5 commit cc0520a
Show file tree
Hide file tree
Showing 5 changed files with 379 additions and 170 deletions.
55 changes: 41 additions & 14 deletions internal/characters/ayaka/charge.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/genshinsim/gcsim/pkg/core/attacks"
"github.com/genshinsim/gcsim/pkg/core/attributes"
"github.com/genshinsim/gcsim/pkg/core/combat"
"github.com/genshinsim/gcsim/pkg/core/geometry"
)

var chargeFrames []int
Expand Down Expand Up @@ -33,20 +34,46 @@ func (c *char) ChargeAttack(p map[string]int) action.ActionInfo {
Mult: ca[c.TalentLvlAttack()],
}

for i := 0; i < 3; i++ {
c.Core.QueueAttack(
ai,
combat.NewCircleHit(
c.Core.Combat.Player(),
c.Core.Combat.PrimaryTarget(),
nil,
1,
),
chargeHitmarks[i],
chargeHitmarks[i],
c.c1,
c.c6,
)
// spawn up to 5 attacks
// priority: enemy > gadget
chargeCount := 5
chargeArea := combat.NewCircleHitOnTarget(c.Core.Combat.Player(), nil, 4)
charge := func(pos geometry.Point, i int) {
for j := 0; j < 3; j++ {
c.Core.QueueAttack(
ai,
combat.NewCircleHitOnTarget(
pos,
nil,
1,
),
chargeHitmarks[j],
chargeHitmarks[j],
c.c1,
c.c6,
)
}
}

// target up to 5 enemies
enemies := c.Core.Combat.EnemiesWithinArea(chargeArea, nil)
enemyCount := len(enemies)
for i := 0; i < chargeCount; i++ {
if i < enemyCount {
charge(enemies[i].Pos(), i)
}
}
chargeCount -= enemyCount

// if less than 5 enemies were targeted, then check for gadgets
if chargeCount > 0 {
gadgets := c.Core.Combat.GadgetsWithinArea(chargeArea, nil)
gadgetCount := len(gadgets)
for i := 0; i < chargeCount; i++ {
if i < gadgetCount {
charge(gadgets[i].Pos(), i)
}
}
}

return action.ActionInfo{
Expand Down
53 changes: 40 additions & 13 deletions internal/characters/lisa/burst.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/genshinsim/gcsim/pkg/core/attacks"
"github.com/genshinsim/gcsim/pkg/core/attributes"
"github.com/genshinsim/gcsim/pkg/core/combat"
"github.com/genshinsim/gcsim/pkg/core/geometry"
)

var burstFrames []int
Expand Down Expand Up @@ -77,11 +78,33 @@ func (c *char) Burst(p map[string]int) action.ActionInfo {
return
}

// at c4 and above:
// https://library.keqingmains.com/evidence/characters/electro/lisa#c4-plasma-eruption
enemies := c.Core.Combat.RandomEnemiesWithinArea(burstArea, nil, 3)
// at C4 and above:
// - https://library.keqingmains.com/evidence/characters/electro/lisa#c4-plasma-eruption
// - spawn up to 3 attacks based on enemy + gadget count
// - priority: enemy > gadget
discharge := func(pos geometry.Point) {
c.Core.QueueAttackWithSnap(
ai,
snap,
combat.NewCircleHitOnTarget(pos, nil, 1),
0,
c.makeA4CB(),
)
}
dischargeCount := 0
switch len(enemies) {
dischargeLimit := 3

enemies := c.Core.Combat.RandomEnemiesWithinArea(burstArea, nil, dischargeLimit)
enemyCount := len(enemies)

var gadgets []combat.Gadget
if enemyCount < dischargeLimit {
gadgets = c.Core.Combat.RandomGadgetsWithinArea(burstArea, nil, dischargeLimit-enemyCount)
}
gadgetCount := len(gadgets)

totalEntities := enemyCount + gadgetCount
switch totalEntities {
case 0:
case 1:
dischargeCount = 1
Expand Down Expand Up @@ -117,15 +140,19 @@ func (c *char) Burst(p map[string]int) action.ActionInfo {
if dischargeCount == 0 {
return
}
for i, v := range enemies {
if i < dischargeCount {
c.Core.QueueAttackWithSnap(
ai,
snap,
combat.NewCircleHitOnTarget(v, nil, 1),
0,
c.makeA4CB(),
)

// target up to 3 enemies
for i := 0; i < dischargeCount; i++ {
if i < enemyCount {
discharge(enemies[i].Pos())
}
}
dischargeCount -= enemyCount

// if less than 3 enemies were targeted, then check for gadgets
for i := 0; i < dischargeCount; i++ {
if i < gadgetCount {
discharge(gadgets[i].Pos())
}
}
}, progress)
Expand Down
22 changes: 19 additions & 3 deletions internal/characters/yaemiko/kitsune.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/genshinsim/gcsim/pkg/core/attacks"
"github.com/genshinsim/gcsim/pkg/core/attributes"
"github.com/genshinsim/gcsim/pkg/core/combat"
"github.com/genshinsim/gcsim/pkg/core/geometry"
"github.com/genshinsim/gcsim/pkg/core/glog"
"github.com/genshinsim/gcsim/pkg/core/targets"
)
Expand Down Expand Up @@ -142,17 +143,32 @@ func (c *char) kitsuneTick(totem *kitsune) func() {
if c.Base.Cons >= 6 {
ai.IgnoreDefPercent = 0.60
}
enemy := c.Core.Combat.RandomEnemyWithinArea(totem.kitsuneArea, nil)
if enemy != nil {

// spawn 1 attack
// priority: enemy > gadget
tick := func(pos geometry.Point) {
c.Core.QueueAttack(
ai,
combat.NewCircleHitOnTarget(enemy, nil, 0.5),
combat.NewCircleHitOnTarget(pos, nil, 0.5),
1,
1,
c.particleCB,
c4cb,
)
}

// try to target an enemy first
enemy := c.Core.Combat.RandomEnemyWithinArea(totem.kitsuneArea, nil)
if enemy != nil {
tick(enemy.Pos())
} else {
// target gadget if no enemy was targeted
gadget := c.Core.Combat.RandomGadgetWithinArea(totem.kitsuneArea, nil)
if gadget != nil {
tick(gadget.Pos())
}
}

// tick per ~2.9s seconds
c.Core.Tasks.Add(c.kitsuneTick(totem), 176)
}
Expand Down
140 changes: 0 additions & 140 deletions pkg/core/combat/enemy.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package combat

import (
"sort"

"github.com/genshinsim/gcsim/pkg/core/attributes"
"github.com/genshinsim/gcsim/pkg/core/event"
"github.com/genshinsim/gcsim/pkg/core/geometry"
Expand Down Expand Up @@ -49,11 +47,6 @@ type Enemy interface {
StatusExpiry(key string) int
}

type enemyTuple struct {
enemy Enemy
dist float64
}

func (h *Handler) Enemy(i int) Target {
if i < 0 || i > len(h.enemies) {
return nil
Expand Down Expand Up @@ -103,136 +96,3 @@ func (h *Handler) PrimaryTarget() Target {
}
panic("default target does not exist?!")
}

// all enemies

// returns enemies within the given area, no sorting, pass nil for no filter
func (c *Handler) EnemiesWithinArea(a AttackPattern, filter func(t Enemy) bool) []Enemy {
var enemies []Enemy

hasFilter := filter != nil
for _, v := range c.enemies {
e, ok := v.(Enemy)
if !ok {
panic("c.enemies should contain targets that implement the Enemy interface")
}
if hasFilter && !filter(e) {
continue
}
if !v.IsAlive() {
continue
}
if !e.IsWithinArea(a) {
continue
}
enemies = append(enemies, e)
}

if len(enemies) == 0 {
return nil
}

return enemies
}

// random enemies

// returns a random enemy within the given area, pass nil for no filter
func (c *Handler) RandomEnemyWithinArea(a AttackPattern, filter func(t Enemy) bool) Enemy {
enemies := c.EnemiesWithinArea(a, filter)
if enemies == nil {
return nil
}
return enemies[c.Rand.Intn(len(enemies))]
}

// returns a list of random enemies within the given area, pass nil for no filter
func (c *Handler) RandomEnemiesWithinArea(a AttackPattern, filter func(t Enemy) bool, maxCount int) []Enemy {
enemies := c.EnemiesWithinArea(a, filter)
if enemies == nil {
return nil
}
enemyCount := len(enemies)

// generate random indexes to take from enemies (no duplicates!)
indexes := c.Rand.Perm(enemyCount)

// determine length of slice to return
count := maxCount
if enemyCount < maxCount {
count = enemyCount
}

// add enemies given by indexes to the result
result := make([]Enemy, 0, count)
for i := 0; i < count; i++ {
result = append(result, enemies[indexes[i]])
}
return result
}

// closest enemies

func (c *Handler) getEnemiesWithinAreaSorted(a AttackPattern, filter func(t Enemy) bool, skipAttackPattern bool) []enemyTuple {
var enemies []enemyTuple

hasFilter := filter != nil
for _, v := range c.enemies {
e, ok := v.(Enemy)
if !ok {
panic("c.enemies should contain targets that implement the Enemy interface")
}
if hasFilter && !filter(e) {
continue
}
if !e.IsAlive() {
continue
}
if !skipAttackPattern && !e.IsWithinArea(a) {
continue
}
enemies = append(enemies, enemyTuple{enemy: e, dist: a.Shape.Pos().Sub(e.Pos()).MagnitudeSquared()})
}

if len(enemies) == 0 {
return nil
}

sort.Slice(enemies, func(i, j int) bool {
return enemies[i].dist < enemies[j].dist
})

return enemies
}

// returns the closest enemy to the given position without any range restrictions; SHOULD NOT be used outside of pkg
func (c *Handler) ClosestEnemy(pos geometry.Point) Enemy {
enemies := c.getEnemiesWithinAreaSorted(NewCircleHitOnTarget(pos, nil, 1), nil, true)
if enemies == nil {
return nil
}
return enemies[0].enemy
}

// returns the closest enemy within the given area, pass nil for no filter
func (c *Handler) ClosestEnemyWithinArea(a AttackPattern, filter func(t Enemy) bool) Enemy {
enemies := c.getEnemiesWithinAreaSorted(a, filter, false)
if enemies == nil {
return nil
}
return enemies[0].enemy
}

// returns enemies within the given area, sorted from closest to furthest, pass nil for no filter
func (c *Handler) ClosestEnemiesWithinArea(a AttackPattern, filter func(t Enemy) bool) []Enemy {
enemies := c.getEnemiesWithinAreaSorted(a, filter, false)
if enemies == nil {
return nil
}

result := make([]Enemy, 0, len(enemies))
for _, v := range enemies {
result = append(result, v.enemy)
}
return result
}
Loading

0 comments on commit cc0520a

Please sign in to comment.