From a9e220fcddaed706644be02141d6a805f12374da Mon Sep 17 00:00:00 2001 From: srliao <906239+srliao@users.noreply.github.com> Date: Fri, 19 Aug 2022 08:58:27 -0400 Subject: [PATCH 1/8] refactor conditionals --- pkg/conditional/character.go | 132 +++++++++++++++++++++++++++++++++ pkg/conditional/charcustom.go | 22 ------ pkg/conditional/charmods.go | 35 --------- pkg/conditional/conditional.go | 42 ++++------- pkg/conditional/cons.go | 31 -------- pkg/conditional/construct.go | 30 ++++---- pkg/conditional/cooldown.go | 43 ----------- pkg/conditional/debuff.go | 25 +++---- pkg/conditional/element.go | 26 +++---- pkg/conditional/energy.go | 32 -------- pkg/conditional/infusion.go | 33 --------- pkg/conditional/keys.go | 39 +++++----- pkg/conditional/normal.go | 29 -------- pkg/conditional/onfield.go | 32 -------- pkg/conditional/ready.go | 43 ----------- pkg/conditional/stam.go | 9 --- pkg/conditional/status.go | 20 ----- pkg/conditional/tags.go | 32 -------- pkg/conditional/weapon.go | 31 -------- pkg/core/core.go | 4 + pkg/gcs/expr.go | 17 ++++- 21 files changed, 216 insertions(+), 491 deletions(-) create mode 100644 pkg/conditional/character.go delete mode 100644 pkg/conditional/charcustom.go delete mode 100644 pkg/conditional/charmods.go delete mode 100644 pkg/conditional/cons.go delete mode 100644 pkg/conditional/cooldown.go delete mode 100644 pkg/conditional/energy.go delete mode 100644 pkg/conditional/infusion.go delete mode 100644 pkg/conditional/normal.go delete mode 100644 pkg/conditional/onfield.go delete mode 100644 pkg/conditional/ready.go delete mode 100644 pkg/conditional/stam.go delete mode 100644 pkg/conditional/status.go delete mode 100644 pkg/conditional/tags.go delete mode 100644 pkg/conditional/weapon.go diff --git a/pkg/conditional/character.go b/pkg/conditional/character.go new file mode 100644 index 000000000..67f34a3b0 --- /dev/null +++ b/pkg/conditional/character.go @@ -0,0 +1,132 @@ +package conditional + +import ( + "fmt" + "strings" + + "github.com/genshinsim/gcsim/pkg/core" + "github.com/genshinsim/gcsim/pkg/core/action" + "github.com/genshinsim/gcsim/pkg/core/attributes" + "github.com/genshinsim/gcsim/pkg/core/keys" + "github.com/genshinsim/gcsim/pkg/core/player/character" +) + +func fieldsCheck(fields []string, expecting int, category string) error { + if len(fields) < expecting { + return fmt.Errorf("bad %v condition; invalid num of fields; expecting at least %v; got %v", category, expecting, len(fields)) + } + return nil +} + +func evalCharacter[V core.Number](c *core.Core, key keys.Char, fields []string) (V, error) { + if err := fieldsCheck(fields, 2, "character"); err != nil { + return 0, err + } + char, ok := c.Player.ByKey(key) + if !ok { + return 0, fmt.Errorf("character %v not in team when evaluating condition", key) + } + + switch fields[1] { + case ".cd": + if err := fieldsCheck(fields, 3, "character cooldown"); err != nil { + return 0, err + } + return evalCharacterCooldown[V](char, fields[2]) + case ".energy": + return V(char.Energy), nil + case ".status": + if err := fieldsCheck(fields, 3, "character status"); err != nil { + return 0, err + } + return evalCharacterMods[V](char, fields[2]) + case ".mods": + if err := fieldsCheck(fields, 3, "character mods"); err != nil { + return 0, err + } + return evalCharacterStatus[V](char, fields[2]) + case ".infusion": + if err := fieldsCheck(fields, 3, "character stats"); err != nil { + return 0, err + } + if c.Player.WeaponInfuseIsActive(char.Index, strings.TrimPrefix(fields[2], ".")) { + return 1, nil + } + return 0, nil + case ".normal": + return V(char.NextNormalCounter()), nil + case ".onfield": + if c.Player.Active() == char.Index { + return 1, nil + } + return 0, nil + case ".weapon": + return V(char.Weapon.Key), nil + case ".cons": + return V(char.Base.Cons), nil + case ".tags": + return V(char.Tag(strings.TrimPrefix(fields[2], "."))), nil + case ".ready": + if err := fieldsCheck(fields, 3, "character abil ready"); err != nil { + return 0, err + } + return evalCharacterAbilReady[V](char, fields[2]) + case ".stats": + if err := fieldsCheck(fields, 3, "character stats"); err != nil { + return 0, err + } + return evalCharacterStats[V](char, fields[2]) + default: + return V(char.Condition(strings.TrimPrefix(fields[1], "."))), nil + } +} + +func evalCharacterStats[V core.Number](char *character.CharWrapper, stat string) (V, error) { + key := attributes.StrToStatType(strings.TrimPrefix(stat, ".")) + if key == -1 { + return 0, fmt.Errorf("invalid stat key %v in character stat condition", stat) + } + return V(char.Stat(key)), nil +} + +func evalCharacterMods[V core.Number](char *character.CharWrapper, mod string) (V, error) { + mod = strings.TrimPrefix(mod, ".") + if char.StatModIsActive(mod) { + return 1, nil + } + return 0, nil +} + +func evalCharacterStatus[V core.Number](char *character.CharWrapper, mod string) (V, error) { + mod = strings.TrimPrefix(mod, ".") + if char.StatusIsActive(mod) { + return 1, nil + } + return 0, nil +} + +func evalCharacterAbilReady[V core.Number](char *character.CharWrapper, abil string) (V, error) { + abil = strings.TrimPrefix(abil, ".") + ak := action.StringToAction(abil) + if ak == action.InvalidAction { + return 0, fmt.Errorf("invalid abil %v in ready condition", abil) + } + + //TODO: nil map may cause problems here?? + if char.ActionReady(ak, nil) { + return 1, nil + } + + return 0, nil +} + +func evalCharacterCooldown[V core.Number](char *character.CharWrapper, abil string) (V, error) { + switch abil { + case ".skill": + return V(char.Cooldown(action.ActionSkill)), nil + case ".burst": + return V(char.Cooldown(action.ActionBurst)), nil + default: + return 0, fmt.Errorf("invalid ability %v in character cooldown condition", abil) + } +} diff --git a/pkg/conditional/charcustom.go b/pkg/conditional/charcustom.go deleted file mode 100644 index ca4de8ba7..000000000 --- a/pkg/conditional/charcustom.go +++ /dev/null @@ -1,22 +0,0 @@ -package conditional - -import ( - "strings" - - "github.com/genshinsim/gcsim/pkg/core" - "github.com/genshinsim/gcsim/pkg/core/glog" - "github.com/genshinsim/gcsim/pkg/core/keys" -) - -func evalCharCustom(c *core.Core, key keys.Char, fields []string) int64 { - if len(fields) < 2 { - c.Log.NewEvent("bad char custom conditon: invalid num of fields", glog.LogWarnings, -1). - Write("fields", fields) - return 0 - } - char, ok := c.Player.ByKey(key) - if !ok { - return 0 - } - return char.Condition(strings.TrimPrefix(fields[1], ".")) -} diff --git a/pkg/conditional/charmods.go b/pkg/conditional/charmods.go deleted file mode 100644 index 68571f892..000000000 --- a/pkg/conditional/charmods.go +++ /dev/null @@ -1,35 +0,0 @@ -package conditional - -import ( - "strings" - - "github.com/genshinsim/gcsim/pkg/core" - "github.com/genshinsim/gcsim/pkg/core/glog" - "github.com/genshinsim/gcsim/pkg/shortcut" -) - -func evalCharMods(c *core.Core, fields []string) int64 { - //.mods.bennett.buff==1 - if len(fields) < 3 { - c.Log.NewEvent("bad char mod conditon: invalid num of fields", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - name := strings.TrimPrefix(fields[1], ".") - key, ok := shortcut.CharNameToKey[name] - if !ok { - c.Log.NewEvent("bad char mod conditon: invalid character", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - char, ok := c.Player.ByKey(key) - if !ok { - c.Log.NewEvent("bad char mod conditon: invalid character", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - tag := strings.TrimPrefix(fields[2], ".") - //TODO: be nice if we can check attackmods somehow but those are conditional - //on attacks/targets and we cant really supply a fake attack or fake target here - if char.StatModIsActive(tag) { - return 1 - } - return 0 -} diff --git a/pkg/conditional/conditional.go b/pkg/conditional/conditional.go index ebec1634a..f087604ac 100644 --- a/pkg/conditional/conditional.go +++ b/pkg/conditional/conditional.go @@ -1,52 +1,36 @@ package conditional import ( + "fmt" "strings" "github.com/genshinsim/gcsim/pkg/core" "github.com/genshinsim/gcsim/pkg/shortcut" ) -func Eval(c *core.Core, fields []string) int64 { +func Eval[V core.Number](c *core.Core, fields []string) (V, error) { switch fields[0] { case ".debuff": - return evalDebuff(c, fields) + return evalDebuff[V](c, fields) case ".element": - return evalElement(c, fields) - case ".cd": - return evalCD(c, fields) - case ".energy": - return evalEnergy(c, fields) + return evalElement[V](c, fields) case ".status": - return evalStatus(c, fields) - case ".tags": - return evalTags(c, fields) + if err := fieldsCheck(fields, 2, "status"); err != nil { + return 0, err + } + return V(c.Status.Duration(strings.TrimPrefix(fields[1], "."))), nil case ".stam": - return evalStam(c, fields) - case ".ready": - return evalReady(c, fields) - case ".mods": - return evalCharMods(c, fields) - case ".infusion": - return evalInfusion(c, fields) + return V(c.Player.Stam), nil case ".construct": - return evalConstruct(c, fields) - case ".normal": - return evalNormalCounter(c, fields) - case ".onfield": - return evalOnField(c, fields) - case ".weapon": - return evalWeapon(c, fields) + return evalConstruct[V](c, fields) case ".keys": - return evalKeys(c, fields) - case ".cons": - return evalConstellation(c, fields) + return evalKeys[V](c, fields) default: //check if it's a char name; if so check char custom eval func name := strings.TrimPrefix(fields[0], ".") if key, ok := shortcut.CharNameToKey[name]; ok { - return evalCharCustom(c, key, fields) + return evalCharacter[V](c, key, fields) } - return 0 + return 0, fmt.Errorf("invalid character %v in character condition", name) } } diff --git a/pkg/conditional/cons.go b/pkg/conditional/cons.go deleted file mode 100644 index 92d359f2c..000000000 --- a/pkg/conditional/cons.go +++ /dev/null @@ -1,31 +0,0 @@ -package conditional - -import ( - "strings" - - "github.com/genshinsim/gcsim/pkg/core" - "github.com/genshinsim/gcsim/pkg/core/glog" - "github.com/genshinsim/gcsim/pkg/shortcut" -) - -func evalConstellation(c *core.Core, fields []string) int64 { - // .cons.fischl - if len(fields) < 2 { - c.Log.NewEvent("bad cons conditon: invalid num of fields", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - - name := strings.TrimPrefix(fields[1], ".") - key, ok := shortcut.CharNameToKey[name] - if !ok { - c.Log.NewEvent("bad cons conditon: invalid character", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - char, ok := c.Player.ByKey(key) - if !ok { - c.Log.NewEvent("bad cons conditon: invalid character", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - - return int64(char.Base.Cons) -} diff --git a/pkg/conditional/construct.go b/pkg/conditional/construct.go index 0c9d67b14..b996cb226 100644 --- a/pkg/conditional/construct.go +++ b/pkg/conditional/construct.go @@ -1,47 +1,43 @@ package conditional import ( + "fmt" "strings" "github.com/genshinsim/gcsim/pkg/core" "github.com/genshinsim/gcsim/pkg/core/construct" - "github.com/genshinsim/gcsim/pkg/core/glog" ) -func evalConstruct(c *core.Core, fields []string) int64 { - if len(fields) < 3 { - c.Log.NewEvent("bad construct conditon: invalid num of fields", glog.LogWarnings, -1).Write("fields", fields) - return 0 +func evalConstruct[V core.Number](c *core.Core, fields []string) (V, error) { + if err := fieldsCheck(fields, 3, "construct"); err != nil { + return 0, err } switch fields[1] { case ".duration": - return evalConstructDuration(c, fields) + return evalConstructDuration[V](c, fields) case ".count": - return evalConstructCount(c, fields) + return evalConstructCount[V](c, fields) default: - c.Log.NewEvent("bad construct conditon: invalid critera", glog.LogWarnings, -1).Write("fields", fields) - return 0 + return 0, fmt.Errorf("bad construct condition: invalid criteria %v", fields[1]) } } -func evalConstructDuration(c *core.Core, fields []string) int64 { +func evalConstructDuration[V core.Number](c *core.Core, fields []string) (V, error) { //.construct.duration. s := strings.TrimPrefix(fields[2], ".") key, ok := construct.ConstructNameToKey[s] if !ok { - c.Log.NewEvent("bad construct conditon: invalid construct", glog.LogWarnings, -1).Write("fields", fields) - return 0 + return 0, fmt.Errorf("bad construction condition: invalid construct %v", s) } - return int64(c.Constructs.Expiry(key)) + return V(c.Constructs.Expiry(key)), nil } -func evalConstructCount(c *core.Core, fields []string) int64 { +func evalConstructCount[V core.Number](c *core.Core, fields []string) (V, error) { //.construct.count. s := strings.TrimPrefix(fields[2], ".") key, ok := construct.ConstructNameToKey[s] if !ok { - c.Log.NewEvent("bad construct conditon: invalid construct", glog.LogWarnings, -1).Write("fields", fields) - return 0 + return 0, fmt.Errorf("bad construction condition: invalid construct %v", s) } - return int64(c.Constructs.CountByType(key)) + return V(c.Constructs.CountByType(key)), nil } diff --git a/pkg/conditional/cooldown.go b/pkg/conditional/cooldown.go deleted file mode 100644 index 493921597..000000000 --- a/pkg/conditional/cooldown.go +++ /dev/null @@ -1,43 +0,0 @@ -package conditional - -import ( - "strings" - - "github.com/genshinsim/gcsim/pkg/core" - "github.com/genshinsim/gcsim/pkg/core/action" - "github.com/genshinsim/gcsim/pkg/core/glog" - "github.com/genshinsim/gcsim/pkg/shortcut" -) - -func evalCD(c *core.Core, fields []string) int64 { - //.element.t1.pyro - if len(fields) < 3 { - c.Log.NewEvent("bad cooldown conditon: invalid num of fields", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - //check target is valid - name := strings.TrimPrefix(fields[1], ".") - key, ok := shortcut.CharNameToKey[name] - if !ok { - c.Log.NewEvent("bad cooldown conditon: invalid character", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - char, ok := c.Player.ByKey(key) - if !ok { - c.Log.NewEvent("bad cooldown conditon: invalid character", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - var cd int - - switch fields[2] { - case ".skill": - cd = char.Cooldown(action.ActionSkill) - case ".burst": - cd = char.Cooldown(action.ActionBurst) - default: - c.Log.NewEvent("bad cooldown conditon: invalid action", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - //check vs the conditions - return int64(cd) -} diff --git a/pkg/conditional/debuff.go b/pkg/conditional/debuff.go index 2f1db7fc2..7ff9d8fea 100644 --- a/pkg/conditional/debuff.go +++ b/pkg/conditional/debuff.go @@ -1,50 +1,47 @@ package conditional import ( + "fmt" "strconv" "strings" "github.com/genshinsim/gcsim/pkg/core" - "github.com/genshinsim/gcsim/pkg/core/glog" "github.com/genshinsim/gcsim/pkg/enemy" ) -func evalDebuff(c *core.Core, fields []string) int64 { +func evalDebuff[V core.Number](c *core.Core, fields []string) (V, error) { //.debuff.res.t1.name - if len(fields) < 4 { - c.Log.NewEvent("bad debuff conditon: invalid num of fields", glog.LogWarnings, -1).Write("fields", fields) - return 0 + if err := fieldsCheck(fields, 4, "debuff"); err != nil { + return 0, err } typ := strings.TrimPrefix(fields[1], ".") trg := strings.TrimPrefix(fields[2], ".t") //trg should be an int tid, err := strconv.ParseInt(trg, 10, 64) if err != nil { - //invalid target - c.Log.NewEvent("bad debuff conditon: invalid target", glog.LogWarnings, -1).Write("fields", fields) + return 0, fmt.Errorf("bad debuff condition: invalid target %v", trg) } d := strings.TrimPrefix(fields[3], ".") t := c.Combat.Target(int(tid)) if t == nil { - c.Log.NewEvent("bad debuff conditon: invalid target", glog.LogWarnings, -1).Write("fields", fields) - return 0 + return 0, fmt.Errorf("bad debuff condition: invalid target %v", tid) } + enemy, ok := t.(*enemy.Enemy) if !ok { - c.Log.NewEvent("bad debuff conditon: target not an enemy", glog.LogWarnings, -1).Write("fields", fields) - return 0 + return 0, fmt.Errorf("bad debuff condition: target %v is not an enemy", tid) } switch typ { case "res": if enemy.ResistModIsActive(d) { - return 1 + return 1, nil } case "def": if enemy.DefModIsActive(d) { - return 1 + return 1, nil } } - return 0 + return 0, nil } diff --git a/pkg/conditional/element.go b/pkg/conditional/element.go index 91726e21e..b36429257 100644 --- a/pkg/conditional/element.go +++ b/pkg/conditional/element.go @@ -1,49 +1,45 @@ package conditional import ( + "fmt" "strconv" "strings" "github.com/genshinsim/gcsim/pkg/core" "github.com/genshinsim/gcsim/pkg/core/attributes" - "github.com/genshinsim/gcsim/pkg/core/glog" "github.com/genshinsim/gcsim/pkg/enemy" ) -func evalElement(c *core.Core, fields []string) int64 { +func evalElement[V core.Number](c *core.Core, fields []string) (V, error) { //.element.t1.pyro - if len(fields) < 3 { - c.Log.NewEvent("bad element conditon: invalid num of fields", glog.LogWarnings, -1).Write("fields", fields) - return 0 + if err := fieldsCheck(fields, 3, "element"); err != nil { + return 0, err } trg := strings.TrimPrefix(fields[1], ".t") //trg should be an int tid, err := strconv.ParseInt(trg, 10, 64) if err != nil { - //invalid target - c.Log.NewEvent("bad element conditon: invalid target", glog.LogWarnings, -1).Write("fields", fields) + return 0, fmt.Errorf("bad element condition: invalid target %v", trg) } ele := strings.TrimPrefix(fields[2], ".") elekey := attributes.StringToEle(ele) if elekey == attributes.UnknownElement { - c.Log.NewEvent("bad element conditon: invalid element", glog.LogWarnings, -1).Write("fields", fields) - return 0 + return 0, fmt.Errorf("bad element condition: invalid element %v", ele) } t := c.Combat.Target(int(tid)) if t == nil { - c.Log.NewEvent("bad element conditon: invalid target", glog.LogWarnings, -1).Write("fields", fields) - return 0 + return 0, fmt.Errorf("bad element condition: invalid target %v", tid) } + enemy, ok := t.(*enemy.Enemy) if !ok { - c.Log.NewEvent("bad element conditon: target not an enemy", glog.LogWarnings, -1).Write("fields", fields) - return 0 + return 0, fmt.Errorf("bad element condition: target %v is not an enemy", tid) } if enemy.AuraContains(elekey) { - return 1 + return 1, nil } - return 0 + return 0, nil } diff --git a/pkg/conditional/energy.go b/pkg/conditional/energy.go deleted file mode 100644 index c07989777..000000000 --- a/pkg/conditional/energy.go +++ /dev/null @@ -1,32 +0,0 @@ -package conditional - -import ( - "strings" - - "github.com/genshinsim/gcsim/pkg/core" - "github.com/genshinsim/gcsim/pkg/core/glog" - "github.com/genshinsim/gcsim/pkg/shortcut" -) - -func evalEnergy(c *core.Core, fields []string) int64 { - //.energy.char - if len(fields) < 2 { - c.Log.NewEvent("bad energy conditon: invalid num of fields", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - //check target is valid - name := strings.TrimPrefix(fields[1], ".") - key, ok := shortcut.CharNameToKey[name] - if !ok { - c.Log.NewEvent("bad energy conditon: invalid character", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - char, ok := c.Player.ByKey(key) - if !ok { - c.Log.NewEvent("bad energy conditon: invalid character", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - - //this will floor it - return int64(char.Energy) -} diff --git a/pkg/conditional/infusion.go b/pkg/conditional/infusion.go deleted file mode 100644 index 44f675ae4..000000000 --- a/pkg/conditional/infusion.go +++ /dev/null @@ -1,33 +0,0 @@ -package conditional - -import ( - "strings" - - "github.com/genshinsim/gcsim/pkg/core" - "github.com/genshinsim/gcsim/pkg/core/glog" - "github.com/genshinsim/gcsim/pkg/shortcut" -) - -func evalInfusion(c *core.Core, fields []string) int64 { - //.infusion.bennett.key - if len(fields) < 3 { - c.Log.NewEvent("bad infusion conditon: invalid num of fields", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - name := strings.TrimPrefix(fields[1], ".") - key, ok := shortcut.CharNameToKey[name] - if !ok { - c.Log.NewEvent("bad infusion conditon: invalid character", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - char, ok := c.Player.ByKey(key) - if !ok { - c.Log.NewEvent("bad infusion conditon: invalid character", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - inf := strings.TrimPrefix(fields[2], ".") - if c.Player.WeaponInfuseIsActive(char.Index, inf) { - return 1 - } - return 0 -} diff --git a/pkg/conditional/keys.go b/pkg/conditional/keys.go index 7943c2ba2..bedb89cf2 100644 --- a/pkg/conditional/keys.go +++ b/pkg/conditional/keys.go @@ -1,60 +1,55 @@ package conditional import ( + "fmt" "strings" "github.com/genshinsim/gcsim/pkg/core" - "github.com/genshinsim/gcsim/pkg/core/glog" "github.com/genshinsim/gcsim/pkg/shortcut" ) -func evalWeaponKey(c *core.Core, fields []string) int64 { +func evalWeaponKey[V core.Number](c *core.Core, fields []string) (V, error) { name := strings.TrimPrefix(fields[2], ".") key, ok := shortcut.WeaponNameToKey[name] if !ok { - c.Log.NewEvent("bad keys conditon: invalid weapon", glog.LogWarnings, -1).Write("fields", fields) - return -1 + return 0, fmt.Errorf("bad key condition: invalid weapon %v", name) } - return int64(key) + return V(key), nil } -func evalSetKey(c *core.Core, fields []string) int64 { +func evalSetKey[V core.Number](c *core.Core, fields []string) (V, error) { name := strings.TrimPrefix(fields[2], ".") key, ok := shortcut.SetNameToKey[name] if !ok { - c.Log.NewEvent("bad keys conditon: invalid set", glog.LogWarnings, -1).Write("fields", fields) - return -1 + return 0, fmt.Errorf("bad key condition: invalid artifact set %v", name) } - return int64(key) + return V(key), nil } -func evalCharacterKey(c *core.Core, fields []string) int64 { +func evalCharacterKey[V core.Number](c *core.Core, fields []string) (V, error) { name := strings.TrimPrefix(fields[2], ".") key, ok := shortcut.CharNameToKey[name] if !ok { - c.Log.NewEvent("bad keys conditon: invalid character", glog.LogWarnings, -1).Write("fields", fields) - return -1 + return 0, fmt.Errorf("bad key condition: invalid character %v", name) } - return int64(key) + return V(key), nil } -func evalKeys(c *core.Core, fields []string) int64 { +func evalKeys[V core.Number](c *core.Core, fields []string) (V, error) { // .keys.weapon.polarstar - if len(fields) < 3 { - c.Log.NewEvent("bad keys conditon: invalid num of fields", glog.LogWarnings, -1).Write("fields", fields) - return -1 + if err := fieldsCheck(fields, 3, "keys"); err != nil { + return 0, err } name := strings.TrimPrefix(fields[1], ".") switch name { case "weapon": - return evalWeaponKey(c, fields) + return evalWeaponKey[V](c, fields) case "set": - return evalSetKey(c, fields) + return evalSetKey[V](c, fields) case "char": // is this necessary? :pepela: - return evalCharacterKey(c, fields) + return evalCharacterKey[V](c, fields) default: - c.Log.NewEvent("bad keys conditon: invalid type", glog.LogWarnings, -1).Write("fields", fields) - return -1 + return 0, fmt.Errorf("bad key condition: invalid type %v", name) } } diff --git a/pkg/conditional/normal.go b/pkg/conditional/normal.go deleted file mode 100644 index 87a6a3973..000000000 --- a/pkg/conditional/normal.go +++ /dev/null @@ -1,29 +0,0 @@ -package conditional - -import ( - "strings" - - "github.com/genshinsim/gcsim/pkg/core" - "github.com/genshinsim/gcsim/pkg/core/glog" - "github.com/genshinsim/gcsim/pkg/shortcut" -) - -func evalNormalCounter(c *core.Core, fields []string) int64 { - //.normal.bennett - if len(fields) < 2 { - c.Log.NewEvent("bad normal counter conditon: invalid num of fields", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - name := strings.TrimPrefix(fields[1], ".") - key, ok := shortcut.CharNameToKey[name] - if !ok { - c.Log.NewEvent("bad normal counter conditon: invalid character", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - char, ok := c.Player.ByKey(key) - if !ok { - c.Log.NewEvent("bad normal counter conditon: invalid character", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - return int64(char.NextNormalCounter()) -} diff --git a/pkg/conditional/onfield.go b/pkg/conditional/onfield.go deleted file mode 100644 index d38d0b536..000000000 --- a/pkg/conditional/onfield.go +++ /dev/null @@ -1,32 +0,0 @@ -package conditional - -import ( - "strings" - - "github.com/genshinsim/gcsim/pkg/core" - "github.com/genshinsim/gcsim/pkg/core/glog" - "github.com/genshinsim/gcsim/pkg/shortcut" -) - -func evalOnField(c *core.Core, fields []string) int64 { - // .onfield.bennett - if len(fields) < 2 { - c.Log.NewEvent("bad onfield conditon: invalid num of fields", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - name := strings.TrimPrefix(fields[1], ".") - key, ok := shortcut.CharNameToKey[name] - if !ok { - c.Log.NewEvent("bad onfield conditon: invalid character", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - char, ok := c.Player.ByKey(key) - if !ok { - c.Log.NewEvent("bad onfield conditon: invalid character", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - if c.Player.Active() == char.Index { - return 1 - } - return 0 -} diff --git a/pkg/conditional/ready.go b/pkg/conditional/ready.go deleted file mode 100644 index 3f80a8c0b..000000000 --- a/pkg/conditional/ready.go +++ /dev/null @@ -1,43 +0,0 @@ -package conditional - -import ( - "strings" - - "github.com/genshinsim/gcsim/pkg/core" - "github.com/genshinsim/gcsim/pkg/core/action" - "github.com/genshinsim/gcsim/pkg/core/glog" - "github.com/genshinsim/gcsim/pkg/shortcut" -) - -func evalReady(c *core.Core, fields []string) int64 { - if len(fields) < 3 { - c.Log.NewEvent("bad ready conditon: invalid num of fields", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - //check target is valid - name := strings.TrimPrefix(fields[1], ".") - key, ok := shortcut.CharNameToKey[name] - if !ok { - c.Log.NewEvent("bad ready conditon: invalid character", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - char, ok := c.Player.ByKey(key) - if !ok { - c.Log.NewEvent("bad ready conditon: invalid character", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - - abil := strings.TrimPrefix(fields[2], ".") - ak := action.StringToAction(abil) - if ak == action.InvalidAction { - c.Log.NewEvent("bad ready conditon: invalid action", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - - //TODO: nil map may cause problems here?? - if char.ActionReady(ak, nil) { - return 1 - } - - return 0 -} diff --git a/pkg/conditional/stam.go b/pkg/conditional/stam.go deleted file mode 100644 index 7dd3cb5f1..000000000 --- a/pkg/conditional/stam.go +++ /dev/null @@ -1,9 +0,0 @@ -package conditional - -import ( - "github.com/genshinsim/gcsim/pkg/core" -) - -func evalStam(c *core.Core, fields []string) int64 { - return int64(c.Player.Stam) -} diff --git a/pkg/conditional/status.go b/pkg/conditional/status.go deleted file mode 100644 index 89c81d223..000000000 --- a/pkg/conditional/status.go +++ /dev/null @@ -1,20 +0,0 @@ -package conditional - -import ( - "strings" - - "github.com/genshinsim/gcsim/pkg/core" - "github.com/genshinsim/gcsim/pkg/core/glog" -) - -func evalStatus(c *core.Core, fields []string) int64 { - //.energy.char - if len(fields) < 2 { - c.Log.NewEvent("bad status conditon: invalid num of fields", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - //check target is valid - status := strings.TrimPrefix(fields[1], ".") - - return int64(c.Status.Duration(status)) -} diff --git a/pkg/conditional/tags.go b/pkg/conditional/tags.go deleted file mode 100644 index 3c283d6b5..000000000 --- a/pkg/conditional/tags.go +++ /dev/null @@ -1,32 +0,0 @@ -package conditional - -import ( - "strings" - - "github.com/genshinsim/gcsim/pkg/core" - "github.com/genshinsim/gcsim/pkg/core/glog" - "github.com/genshinsim/gcsim/pkg/shortcut" -) - -func evalTags(c *core.Core, fields []string) int64 { - //.tags.char.tag - if len(fields) < 3 { - c.Log.NewEvent("bad tags conditon: invalid num of fields", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - //check target is valid - name := strings.TrimPrefix(fields[1], ".") - key, ok := shortcut.CharNameToKey[name] - if !ok { - c.Log.NewEvent("bad tags conditon: invalid character", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - char, ok := c.Player.ByKey(key) - if !ok { - c.Log.NewEvent("bad tags conditon: invalid character", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - tag := strings.TrimPrefix(fields[2], ".") - - return int64(char.Tag(tag)) -} diff --git a/pkg/conditional/weapon.go b/pkg/conditional/weapon.go deleted file mode 100644 index 8c9425465..000000000 --- a/pkg/conditional/weapon.go +++ /dev/null @@ -1,31 +0,0 @@ -package conditional - -import ( - "strings" - - "github.com/genshinsim/gcsim/pkg/core" - "github.com/genshinsim/gcsim/pkg/core/glog" - "github.com/genshinsim/gcsim/pkg/shortcut" -) - -func evalWeapon(c *core.Core, fields []string) int64 { - // .weapon.fischl - if len(fields) < 2 { - c.Log.NewEvent("bad weapon conditon: invalid num of fields", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - - name := strings.TrimPrefix(fields[1], ".") - key, ok := shortcut.CharNameToKey[name] - if !ok { - c.Log.NewEvent("bad weapon conditon: invalid character", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - char, ok := c.Player.ByKey(key) - if !ok { - c.Log.NewEvent("bad weapon conditon: invalid character", glog.LogWarnings, -1).Write("fields", fields) - return 0 - } - - return int64(char.Weapon.Key) -} diff --git a/pkg/core/core.go b/pkg/core/core.go index 2998b021a..d2ce97cc4 100644 --- a/pkg/core/core.go +++ b/pkg/core/core.go @@ -24,6 +24,10 @@ import ( "github.com/genshinsim/gcsim/pkg/core/task" ) +type Number interface { + int64 | float64 +} + type Core struct { F int Flags Flags diff --git a/pkg/gcs/expr.go b/pkg/gcs/expr.go index 3bde892f6..62322be1e 100644 --- a/pkg/gcs/expr.go +++ b/pkg/gcs/expr.go @@ -187,8 +187,21 @@ func (e *Eval) evalBinaryExpr(b *ast.BinaryExpr, env *Env) (Obj, error) { } func (e *Eval) evalField(n *ast.Field, env *Env) (Obj, error) { - r := conditional.Eval(e.Core, n.Value) + //TODO: this is so hack.... someone come up with a better solution :( + if len(n.Value) > 1 && n.Value[1] == ".stats" { + switch n.Value[1] { + case ".stats", ".energy": + default: + break + } + r, err := conditional.Eval[float64](e.Core, n.Value) + return &number{ + fval: r, + isFloat: true, + }, err + } + r, err := conditional.Eval[int64](e.Core, n.Value) return &number{ ival: r, - }, nil + }, err } From fc6f1f6553ee760f02a31d5e2caed881065988e2 Mon Sep 17 00:00:00 2001 From: Shizuka Date: Sat, 20 Aug 2022 00:05:02 +0400 Subject: [PATCH 2/8] Refactor conditionals again --- internal/characters/albedo/albedo.go | 13 +- internal/characters/aloy/aloy.go | 8 +- internal/characters/fischl/fischl.go | 12 +- internal/characters/ningguang/ningguang.go | 10 +- internal/template/character/default.go | 10 ++ pkg/conditional/character.go | 139 +++++++-------------- pkg/conditional/conditional.go | 41 +++--- pkg/conditional/construct.go | 38 ++---- pkg/conditional/debuff.go | 47 ------- pkg/conditional/element.go | 45 ------- pkg/conditional/enemy.go | 74 +++++++++++ pkg/conditional/keys.go | 54 ++++---- pkg/core/core.go | 4 - pkg/core/player/character/character.go | 2 +- pkg/gcs/expr.go | 35 +++--- 15 files changed, 237 insertions(+), 295 deletions(-) create mode 100644 internal/template/character/default.go delete mode 100644 pkg/conditional/debuff.go delete mode 100644 pkg/conditional/element.go create mode 100644 pkg/conditional/enemy.go diff --git a/internal/characters/albedo/albedo.go b/internal/characters/albedo/albedo.go index 7dc2d153d..0c863a83f 100644 --- a/internal/characters/albedo/albedo.go +++ b/internal/characters/albedo/albedo.go @@ -42,18 +42,15 @@ func (c *char) Init() error { return nil } -func (c *char) Condition(k string) int64 { - switch k { +func (c *char) Condition(fields []string) (any, error) { + switch fields[0] { case "skill": fallthrough case "elevator": - if c.skillActive { - return 1 - } - return 0 + return c.skillActive, nil case "c2stacks": - return int64(c.c2stacks) + return c.c2stacks, nil default: - return 0 + return c.Character.Condition(fields) } } diff --git a/internal/characters/aloy/aloy.go b/internal/characters/aloy/aloy.go index 02a635ccf..2d3183509 100644 --- a/internal/characters/aloy/aloy.go +++ b/internal/characters/aloy/aloy.go @@ -42,11 +42,11 @@ func (c *char) Init() error { return nil } -func (c *char) Condition(k string) int64 { - switch k { +func (c *char) Condition(fields []string) (any, error) { + switch fields[0] { case "coil": - return int64(c.coils) + return c.coils, nil default: - return 0 + return c.Character.Condition(fields) } } diff --git a/internal/characters/fischl/fischl.go b/internal/characters/fischl/fischl.go index c586a1aa6..1f411a043 100644 --- a/internal/characters/fischl/fischl.go +++ b/internal/characters/fischl/fischl.go @@ -44,16 +44,16 @@ func (c *char) Init() error { return nil } -func (c *char) Condition(k string) int64 { - switch k { +func (c *char) Condition(fields []string) (any, error) { + switch fields[0] { case "oz": if c.ozActiveUntil <= c.Core.F { - return 0 + return false, nil } - return int64(c.ozActiveUntil - c.Core.F) + return (c.ozActiveUntil - c.Core.F), nil case "oz-source": - return int64(c.ozSource) + return c.ozSource, nil default: - return 0 + return c.Character.Condition(fields) } } diff --git a/internal/characters/ningguang/ningguang.go b/internal/characters/ningguang/ningguang.go index 049e1ee1a..1910fa9e9 100644 --- a/internal/characters/ningguang/ningguang.go +++ b/internal/characters/ningguang/ningguang.go @@ -79,13 +79,13 @@ func (c *char) ActionStam(a action.Action, p map[string]int) float64 { return c.Character.ActionStam(a, p) } -func (c *char) Condition(field string) int64 { - switch field { +func (c *char) Condition(fields []string) (any, error) { + switch fields[0] { case "jadeCount": - return int64(c.jadeCount) + return c.jadeCount, nil case "prevAttack": - return int64(c.prevAttack) + return c.prevAttack, nil default: - return 0 + return c.Character.Condition(fields) } } diff --git a/internal/template/character/default.go b/internal/template/character/default.go new file mode 100644 index 000000000..122e5fb12 --- /dev/null +++ b/internal/template/character/default.go @@ -0,0 +1,10 @@ +package character + +import ( + "fmt" + "strings" +) + +func (c *Character) Condition(fields []string) (any, error) { + return false, fmt.Errorf("invalid character condition: .%v.%v", c.Base.Key.String(), strings.Join(fields, ".")) +} diff --git a/pkg/conditional/character.go b/pkg/conditional/character.go index 67f34a3b0..4d172e7bd 100644 --- a/pkg/conditional/character.go +++ b/pkg/conditional/character.go @@ -2,7 +2,6 @@ package conditional import ( "fmt" - "strings" "github.com/genshinsim/gcsim/pkg/core" "github.com/genshinsim/gcsim/pkg/core/action" @@ -11,14 +10,7 @@ import ( "github.com/genshinsim/gcsim/pkg/core/player/character" ) -func fieldsCheck(fields []string, expecting int, category string) error { - if len(fields) < expecting { - return fmt.Errorf("bad %v condition; invalid num of fields; expecting at least %v; got %v", category, expecting, len(fields)) - } - return nil -} - -func evalCharacter[V core.Number](c *core.Core, key keys.Char, fields []string) (V, error) { +func evalCharacter(c *core.Core, key keys.Char, fields []string) (any, error) { if err := fieldsCheck(fields, 2, "character"); err != nil { return 0, err } @@ -27,105 +19,70 @@ func evalCharacter[V core.Number](c *core.Core, key keys.Char, fields []string) return 0, fmt.Errorf("character %v not in team when evaluating condition", key) } - switch fields[1] { - case ".cd": - if err := fieldsCheck(fields, 3, "character cooldown"); err != nil { - return 0, err - } - return evalCharacterCooldown[V](char, fields[2]) - case ".energy": - return V(char.Energy), nil - case ".status": - if err := fieldsCheck(fields, 3, "character status"); err != nil { - return 0, err - } - return evalCharacterMods[V](char, fields[2]) - case ".mods": - if err := fieldsCheck(fields, 3, "character mods"); err != nil { - return 0, err - } - return evalCharacterStatus[V](char, fields[2]) - case ".infusion": - if err := fieldsCheck(fields, 3, "character stats"); err != nil { - return 0, err - } - if c.Player.WeaponInfuseIsActive(char.Index, strings.TrimPrefix(fields[2], ".")) { - return 1, nil - } - return 0, nil - case ".normal": - return V(char.NextNormalCounter()), nil - case ".onfield": - if c.Player.Active() == char.Index { - return 1, nil - } - return 0, nil - case ".weapon": - return V(char.Weapon.Key), nil - case ".cons": - return V(char.Base.Cons), nil - case ".tags": - return V(char.Tag(strings.TrimPrefix(fields[2], "."))), nil - case ".ready": - if err := fieldsCheck(fields, 3, "character abil ready"); err != nil { - return 0, err - } - return evalCharacterAbilReady[V](char, fields[2]) - case ".stats": - if err := fieldsCheck(fields, 3, "character stats"); err != nil { - return 0, err - } - return evalCharacterStats[V](char, fields[2]) - default: - return V(char.Condition(strings.TrimPrefix(fields[1], "."))), nil + typ := fields[1] + switch typ { + case "cons": + return char.Base.Cons, nil + case "energy": + return char.Energy, nil + case "energymax": + return char.EnergyMax, nil + case "normal": + return char.NextNormalCounter(), nil + case "onfield": + return c.Player.Active() == char.Index, nil + case "weapon": + return int(char.Weapon.Key), nil } -} -func evalCharacterStats[V core.Number](char *character.CharWrapper, stat string) (V, error) { - key := attributes.StrToStatType(strings.TrimPrefix(stat, ".")) - if key == -1 { - return 0, fmt.Errorf("invalid stat key %v in character stat condition", stat) + // call character condition early + if err := fieldsCheck(fields, 3, "character "+fields[1]); err != nil { + // .kokomi. + return char.Condition(fields[1:]) } - return V(char.Stat(key)), nil -} -func evalCharacterMods[V core.Number](char *character.CharWrapper, mod string) (V, error) { - mod = strings.TrimPrefix(mod, ".") - if char.StatModIsActive(mod) { - return 1, nil + val := fields[2] + switch typ { + case "cd": + return evalCharacterCooldown(char, val) + case "status", "mods": + return char.StatusIsActive(val), nil + case "infusion": + return c.Player.WeaponInfuseIsActive(char.Index, val), nil + case "tags": + return char.Tag(val), nil + case "ready": + return evalCharacterAbilReady(char, val) + case "stats": + return evalCharacterStats(char, val) + default: // .kokomi..* + return char.Condition(fields[1:]) } - return 0, nil } -func evalCharacterStatus[V core.Number](char *character.CharWrapper, mod string) (V, error) { - mod = strings.TrimPrefix(mod, ".") - if char.StatusIsActive(mod) { - return 1, nil +func evalCharacterStats(char *character.CharWrapper, stat string) (float64, error) { + key := attributes.StrToStatType(stat) + if key == -1 { + return 0, fmt.Errorf("invalid stat key %v in character stat condition", stat) } - return 0, nil + return char.Stat(key), nil } -func evalCharacterAbilReady[V core.Number](char *character.CharWrapper, abil string) (V, error) { - abil = strings.TrimPrefix(abil, ".") +func evalCharacterAbilReady(char *character.CharWrapper, abil string) (bool, error) { ak := action.StringToAction(abil) if ak == action.InvalidAction { - return 0, fmt.Errorf("invalid abil %v in ready condition", abil) + return false, fmt.Errorf("invalid abil %v in ready condition", abil) } - //TODO: nil map may cause problems here?? - if char.ActionReady(ak, nil) { - return 1, nil - } - - return 0, nil + return char.ActionReady(ak, nil), nil } -func evalCharacterCooldown[V core.Number](char *character.CharWrapper, abil string) (V, error) { +func evalCharacterCooldown(char *character.CharWrapper, abil string) (int, error) { switch abil { - case ".skill": - return V(char.Cooldown(action.ActionSkill)), nil - case ".burst": - return V(char.Cooldown(action.ActionBurst)), nil + case "skill": + return char.Cooldown(action.ActionSkill), nil + case "burst": + return char.Cooldown(action.ActionBurst), nil default: return 0, fmt.Errorf("invalid ability %v in character cooldown condition", abil) } diff --git a/pkg/conditional/conditional.go b/pkg/conditional/conditional.go index f087604ac..c401d29b0 100644 --- a/pkg/conditional/conditional.go +++ b/pkg/conditional/conditional.go @@ -8,28 +8,39 @@ import ( "github.com/genshinsim/gcsim/pkg/shortcut" ) -func Eval[V core.Number](c *core.Core, fields []string) (V, error) { +func fieldsCheck(fields []string, expecting int, category string) error { + if len(fields) < expecting { + return fmt.Errorf("bad %v condition; invalid num of fields; expecting at least %v; got %v", category, expecting, len(fields)) + } + return nil +} + +func Eval(c *core.Core, fields []string) (any, error) { + for i := 0; i < len(fields); i++ { + fields[i] = strings.Trim(fields[i], ".") + } + switch fields[0] { - case ".debuff": - return evalDebuff[V](c, fields) - case ".element": - return evalElement[V](c, fields) - case ".status": + case "debuff": + return evalDebuff(c, fields) + case "element": + return evalElement(c, fields) + case "status": if err := fieldsCheck(fields, 2, "status"); err != nil { return 0, err } - return V(c.Status.Duration(strings.TrimPrefix(fields[1], "."))), nil - case ".stam": - return V(c.Player.Stam), nil - case ".construct": - return evalConstruct[V](c, fields) - case ".keys": - return evalKeys[V](c, fields) + return c.Status.Duration(fields[1]), nil + case "stam": + return c.Player.Stam, nil + case "construct": + return evalConstruct(c, fields) + case "keys": + return evalKeys(c, fields) default: //check if it's a char name; if so check char custom eval func - name := strings.TrimPrefix(fields[0], ".") + name := fields[0] if key, ok := shortcut.CharNameToKey[name]; ok { - return evalCharacter[V](c, key, fields) + return evalCharacter(c, key, fields) } return 0, fmt.Errorf("invalid character %v in character condition", name) } diff --git a/pkg/conditional/construct.go b/pkg/conditional/construct.go index b996cb226..de0d2aa82 100644 --- a/pkg/conditional/construct.go +++ b/pkg/conditional/construct.go @@ -2,42 +2,30 @@ package conditional import ( "fmt" - "strings" "github.com/genshinsim/gcsim/pkg/core" "github.com/genshinsim/gcsim/pkg/core/construct" ) -func evalConstruct[V core.Number](c *core.Core, fields []string) (V, error) { +func evalConstruct(c *core.Core, fields []string) (int, error) { + //.construct.count. + //.construct.duration. if err := fieldsCheck(fields, 3, "construct"); err != nil { return 0, err } - switch fields[1] { - case ".duration": - return evalConstructDuration[V](c, fields) - case ".count": - return evalConstructCount[V](c, fields) - default: - return 0, fmt.Errorf("bad construct condition: invalid criteria %v", fields[1]) - } -} -func evalConstructDuration[V core.Number](c *core.Core, fields []string) (V, error) { - //.construct.duration. - s := strings.TrimPrefix(fields[2], ".") - key, ok := construct.ConstructNameToKey[s] + name := fields[2] + key, ok := construct.ConstructNameToKey[name] if !ok { - return 0, fmt.Errorf("bad construction condition: invalid construct %v", s) + return 0, fmt.Errorf("bad construct condition: invalid construct %v", name) } - return V(c.Constructs.Expiry(key)), nil -} -func evalConstructCount[V core.Number](c *core.Core, fields []string) (V, error) { - //.construct.count. - s := strings.TrimPrefix(fields[2], ".") - key, ok := construct.ConstructNameToKey[s] - if !ok { - return 0, fmt.Errorf("bad construction condition: invalid construct %v", s) + switch v := fields[1]; v { + case "count": + return c.Constructs.CountByType(key), nil + case "duration": + return c.Constructs.Expiry(key), nil + default: + return 0, fmt.Errorf("bad construct condition: invalid criteria %v", v) } - return V(c.Constructs.CountByType(key)), nil } diff --git a/pkg/conditional/debuff.go b/pkg/conditional/debuff.go deleted file mode 100644 index 7ff9d8fea..000000000 --- a/pkg/conditional/debuff.go +++ /dev/null @@ -1,47 +0,0 @@ -package conditional - -import ( - "fmt" - "strconv" - "strings" - - "github.com/genshinsim/gcsim/pkg/core" - "github.com/genshinsim/gcsim/pkg/enemy" -) - -func evalDebuff[V core.Number](c *core.Core, fields []string) (V, error) { - //.debuff.res.t1.name - if err := fieldsCheck(fields, 4, "debuff"); err != nil { - return 0, err - } - typ := strings.TrimPrefix(fields[1], ".") - trg := strings.TrimPrefix(fields[2], ".t") - //trg should be an int - tid, err := strconv.ParseInt(trg, 10, 64) - if err != nil { - return 0, fmt.Errorf("bad debuff condition: invalid target %v", trg) - } - - d := strings.TrimPrefix(fields[3], ".") - t := c.Combat.Target(int(tid)) - if t == nil { - return 0, fmt.Errorf("bad debuff condition: invalid target %v", tid) - } - - enemy, ok := t.(*enemy.Enemy) - if !ok { - return 0, fmt.Errorf("bad debuff condition: target %v is not an enemy", tid) - } - - switch typ { - case "res": - if enemy.ResistModIsActive(d) { - return 1, nil - } - case "def": - if enemy.DefModIsActive(d) { - return 1, nil - } - } - return 0, nil -} diff --git a/pkg/conditional/element.go b/pkg/conditional/element.go deleted file mode 100644 index b36429257..000000000 --- a/pkg/conditional/element.go +++ /dev/null @@ -1,45 +0,0 @@ -package conditional - -import ( - "fmt" - "strconv" - "strings" - - "github.com/genshinsim/gcsim/pkg/core" - "github.com/genshinsim/gcsim/pkg/core/attributes" - "github.com/genshinsim/gcsim/pkg/enemy" -) - -func evalElement[V core.Number](c *core.Core, fields []string) (V, error) { - //.element.t1.pyro - if err := fieldsCheck(fields, 3, "element"); err != nil { - return 0, err - } - trg := strings.TrimPrefix(fields[1], ".t") - //trg should be an int - tid, err := strconv.ParseInt(trg, 10, 64) - if err != nil { - return 0, fmt.Errorf("bad element condition: invalid target %v", trg) - } - ele := strings.TrimPrefix(fields[2], ".") - elekey := attributes.StringToEle(ele) - if elekey == attributes.UnknownElement { - return 0, fmt.Errorf("bad element condition: invalid element %v", ele) - } - - t := c.Combat.Target(int(tid)) - if t == nil { - return 0, fmt.Errorf("bad element condition: invalid target %v", tid) - } - - enemy, ok := t.(*enemy.Enemy) - if !ok { - return 0, fmt.Errorf("bad element condition: target %v is not an enemy", tid) - } - - if enemy.AuraContains(elekey) { - return 1, nil - } - - return 0, nil -} diff --git a/pkg/conditional/enemy.go b/pkg/conditional/enemy.go new file mode 100644 index 000000000..3fff4faa5 --- /dev/null +++ b/pkg/conditional/enemy.go @@ -0,0 +1,74 @@ +package conditional + +import ( + "fmt" + "strconv" + "strings" + + "github.com/genshinsim/gcsim/pkg/core" + "github.com/genshinsim/gcsim/pkg/core/attributes" + "github.com/genshinsim/gcsim/pkg/enemy" +) + +func evalDebuff(c *core.Core, fields []string) (bool, error) { + //.debuff.res.t1.name + if err := fieldsCheck(fields, 4, "debuff"); err != nil { + return false, err + } + typ := fields[1] + trg := fields[2] + mod := fields[3] + + e, err := parseTarget(c, trg) + if err != nil { + return false, fmt.Errorf("bad debuff condition: %v", err) + } + + switch typ { + case "def": + return e.DefModIsActive(mod), nil + case "res": + return e.ResistModIsActive(mod), nil + default: + return false, fmt.Errorf("bad debuff condition: invalid type %v", typ) + } +} + +func evalElement(c *core.Core, fields []string) (bool, error) { + //.element.t1.pyro + if err := fieldsCheck(fields, 3, "element"); err != nil { + return false, err + } + trg := fields[1] + ele := fields[2] + + e, err := parseTarget(c, trg) + if err != nil { + return false, fmt.Errorf("bad element condition: %v", err) + } + + elekey := attributes.StringToEle(ele) + if elekey == attributes.UnknownElement { + return false, fmt.Errorf("bad element condition: invalid element %v", ele) + } + return e.AuraContains(elekey), nil +} + +func parseTarget(c *core.Core, trg string) (*enemy.Enemy, error) { + trg = strings.TrimPrefix(trg, "t") + tid, err := strconv.ParseInt(trg, 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid target %v", trg) + } + + t := c.Combat.Target(int(tid)) + if t == nil { + return nil, fmt.Errorf("invalid target %v", tid) + } + + e, ok := t.(*enemy.Enemy) + if !ok { + return nil, fmt.Errorf("target %v is not an enemy", tid) + } + return e, nil +} diff --git a/pkg/conditional/keys.go b/pkg/conditional/keys.go index bedb89cf2..c598331fd 100644 --- a/pkg/conditional/keys.go +++ b/pkg/conditional/keys.go @@ -2,54 +2,50 @@ package conditional import ( "fmt" - "strings" "github.com/genshinsim/gcsim/pkg/core" "github.com/genshinsim/gcsim/pkg/shortcut" ) -func evalWeaponKey[V core.Number](c *core.Core, fields []string) (V, error) { - name := strings.TrimPrefix(fields[2], ".") +func evalKeys(c *core.Core, fields []string) (int, error) { + // .keys.weapon.polarstar + if err := fieldsCheck(fields, 3, "keys"); err != nil { + return 0, err + } + + name := fields[2] + switch typ := fields[1]; typ { + case "weapon": + return evalWeaponKey(name) + case "set": + return evalSetKey(name) + case "char": // is this necessary? :pepela: + return evalCharacterKey(name) + default: + return 0, fmt.Errorf("bad key condition: invalid type %v", typ) + } +} + +func evalWeaponKey(name string) (int, error) { key, ok := shortcut.WeaponNameToKey[name] if !ok { return 0, fmt.Errorf("bad key condition: invalid weapon %v", name) } - return V(key), nil + return int(key), nil } -func evalSetKey[V core.Number](c *core.Core, fields []string) (V, error) { - name := strings.TrimPrefix(fields[2], ".") +func evalSetKey(name string) (int, error) { key, ok := shortcut.SetNameToKey[name] if !ok { return 0, fmt.Errorf("bad key condition: invalid artifact set %v", name) } - return V(key), nil + return int(key), nil } -func evalCharacterKey[V core.Number](c *core.Core, fields []string) (V, error) { - name := strings.TrimPrefix(fields[2], ".") +func evalCharacterKey(name string) (int, error) { key, ok := shortcut.CharNameToKey[name] if !ok { return 0, fmt.Errorf("bad key condition: invalid character %v", name) } - return V(key), nil -} - -func evalKeys[V core.Number](c *core.Core, fields []string) (V, error) { - // .keys.weapon.polarstar - if err := fieldsCheck(fields, 3, "keys"); err != nil { - return 0, err - } - - name := strings.TrimPrefix(fields[1], ".") - switch name { - case "weapon": - return evalWeaponKey[V](c, fields) - case "set": - return evalSetKey[V](c, fields) - case "char": // is this necessary? :pepela: - return evalCharacterKey[V](c, fields) - default: - return 0, fmt.Errorf("bad key condition: invalid type %v", name) - } + return int(key), nil } diff --git a/pkg/core/core.go b/pkg/core/core.go index d2ce97cc4..2998b021a 100644 --- a/pkg/core/core.go +++ b/pkg/core/core.go @@ -24,10 +24,6 @@ import ( "github.com/genshinsim/gcsim/pkg/core/task" ) -type Number interface { - int64 | float64 -} - type Core struct { F int Flags Flags diff --git a/pkg/core/player/character/character.go b/pkg/core/player/character/character.go index 0259b8eb6..9e5bc5357 100644 --- a/pkg/core/player/character/character.go +++ b/pkg/core/player/character/character.go @@ -46,7 +46,7 @@ type Character interface { ApplyHitlag(factor, dur float64) - Condition(string) int64 + Condition([]string) (any, error) } type CharWrapper struct { diff --git a/pkg/gcs/expr.go b/pkg/gcs/expr.go index 62322be1e..449fe051b 100644 --- a/pkg/gcs/expr.go +++ b/pkg/gcs/expr.go @@ -187,21 +187,26 @@ func (e *Eval) evalBinaryExpr(b *ast.BinaryExpr, env *Env) (Obj, error) { } func (e *Eval) evalField(n *ast.Field, env *Env) (Obj, error) { - //TODO: this is so hack.... someone come up with a better solution :( - if len(n.Value) > 1 && n.Value[1] == ".stats" { - switch n.Value[1] { - case ".stats", ".energy": - default: - break + r, err := conditional.Eval(e.Core, n.Value) + if err != nil { + return nil, err + } + + num := &number{} + switch v := r.(type) { + case bool: + if v { + num.ival = 1 } - r, err := conditional.Eval[float64](e.Core, n.Value) - return &number{ - fval: r, - isFloat: true, - }, err + case int: + num.ival = int64(v) + case int64: + num.ival = v + case float64: + num.fval = v + num.isFloat = true + default: + return nil, fmt.Errorf("field condition '.%v' does not evaluate to a number, got %v", strings.Join(n.Value, "."), v) } - r, err := conditional.Eval[int64](e.Core, n.Value) - return &number{ - ival: r, - }, err + return num, nil } From 5d76556bb2c6b36790b4c68f0cc2d760bae072d2 Mon Sep 17 00:00:00 2001 From: Shizuka Date: Sat, 20 Aug 2022 00:47:11 +0400 Subject: [PATCH 3/8] Change ability condition syntax to '.raiden.burst.ready' --- pkg/conditional/character.go | 37 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/pkg/conditional/character.go b/pkg/conditional/character.go index 4d172e7bd..643755c48 100644 --- a/pkg/conditional/character.go +++ b/pkg/conditional/character.go @@ -40,19 +40,22 @@ func evalCharacter(c *core.Core, key keys.Char, fields []string) (any, error) { // .kokomi. return char.Condition(fields[1:]) } - val := fields[2] + + // special case for ability conditions. since typ/val are swapped + // .kokomi.. + act := action.StringToAction(typ) + if act != action.InvalidAction { + return evalCharacterAbil(char, act, val) + } + switch typ { - case "cd": - return evalCharacterCooldown(char, val) case "status", "mods": return char.StatusIsActive(val), nil case "infusion": return c.Player.WeaponInfuseIsActive(char.Index, val), nil case "tags": return char.Tag(val), nil - case "ready": - return evalCharacterAbilReady(char, val) case "stats": return evalCharacterStats(char, val) default: // .kokomi..* @@ -68,22 +71,14 @@ func evalCharacterStats(char *character.CharWrapper, stat string) (float64, erro return char.Stat(key), nil } -func evalCharacterAbilReady(char *character.CharWrapper, abil string) (bool, error) { - ak := action.StringToAction(abil) - if ak == action.InvalidAction { - return false, fmt.Errorf("invalid abil %v in ready condition", abil) - } - //TODO: nil map may cause problems here?? - return char.ActionReady(ak, nil), nil -} - -func evalCharacterCooldown(char *character.CharWrapper, abil string) (int, error) { - switch abil { - case "skill": - return char.Cooldown(action.ActionSkill), nil - case "burst": - return char.Cooldown(action.ActionBurst), nil +func evalCharacterAbil(char *character.CharWrapper, act action.Action, typ string) (any, error) { + switch typ { + case "cd": + return char.Cooldown(act), nil + case "ready": + //TODO: nil map may cause problems here?? + return char.ActionReady(act, nil), nil default: - return 0, fmt.Errorf("invalid ability %v in character cooldown condition", abil) + return 0, fmt.Errorf("bad character ability condition: invalid type %v", typ) } } From 24a3e80ac933b57b1dac997603470886f0be6c2c Mon Sep 17 00:00:00 2001 From: Shizuka Date: Sat, 20 Aug 2022 00:48:29 +0400 Subject: [PATCH 4/8] Add condition for ability charge --- internal/template/character/cooldown.go | 4 ---- pkg/conditional/character.go | 2 ++ 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/internal/template/character/cooldown.go b/internal/template/character/cooldown.go index e6fda08c0..ec5af0e21 100644 --- a/internal/template/character/cooldown.go +++ b/internal/template/character/cooldown.go @@ -41,10 +41,6 @@ func (c *Character) SetCD(a action.Action, dur int) { if c.AvailableCDCharge[a] < 0 { panic("unexpected charges less than 0") } - //TODO: remove these tags; add special syntax just to check for charges instead of using tags - if c.Tags["skill_charge"] > 0 { - c.Tags["skill_charge"]-- - } c.Core.Log.NewEventBuildMsg(glog.LogCooldownEvent, c.Index, a.String(), " cooldown triggered"). Write("type", a.String()). Write("expiry", c.Cooldown(a)). diff --git a/pkg/conditional/character.go b/pkg/conditional/character.go index 643755c48..118bf9800 100644 --- a/pkg/conditional/character.go +++ b/pkg/conditional/character.go @@ -75,6 +75,8 @@ func evalCharacterAbil(char *character.CharWrapper, act action.Action, typ strin switch typ { case "cd": return char.Cooldown(act), nil + case "charge": + return char.Charges(act), nil case "ready": //TODO: nil map may cause problems here?? return char.ActionReady(act, nil), nil From aea8d2ea942cb03f35770ae5e2f46d7e32cd955b Mon Sep 17 00:00:00 2001 From: Shizuka Date: Sat, 20 Aug 2022 02:46:58 +0400 Subject: [PATCH 5/8] Fix char.Condition() overriding global conditions --- internal/characters/albedo/albedo.go | 2 - pkg/conditional/character.go | 57 +++++++++++++++++----------- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/internal/characters/albedo/albedo.go b/internal/characters/albedo/albedo.go index 0c863a83f..e548951c0 100644 --- a/internal/characters/albedo/albedo.go +++ b/internal/characters/albedo/albedo.go @@ -44,8 +44,6 @@ func (c *char) Init() error { func (c *char) Condition(fields []string) (any, error) { switch fields[0] { - case "skill": - fallthrough case "elevator": return c.skillActive, nil case "c2stacks": diff --git a/pkg/conditional/character.go b/pkg/conditional/character.go index 118bf9800..3b83e98c2 100644 --- a/pkg/conditional/character.go +++ b/pkg/conditional/character.go @@ -19,7 +19,17 @@ func evalCharacter(c *core.Core, key keys.Char, fields []string) (any, error) { return 0, fmt.Errorf("character %v not in team when evaluating condition", key) } + // special case for ability conditions. since fields are swapped + // .kokomi.. typ := fields[1] + act := action.StringToAction(typ) + if act != action.InvalidAction { + if err := fieldsCheck(fields, 3, "character ability"); err != nil { + return 0, err + } + return evalCharacterAbil(char, act, fields[2]) + } + switch typ { case "cons": return char.Base.Cons, nil @@ -33,34 +43,35 @@ func evalCharacter(c *core.Core, key keys.Char, fields []string) (any, error) { return c.Player.Active() == char.Index, nil case "weapon": return int(char.Weapon.Key), nil - } - - // call character condition early - if err := fieldsCheck(fields, 3, "character "+fields[1]); err != nil { - // .kokomi. - return char.Condition(fields[1:]) - } - val := fields[2] - - // special case for ability conditions. since typ/val are swapped - // .kokomi.. - act := action.StringToAction(typ) - if act != action.InvalidAction { - return evalCharacterAbil(char, act, val) - } - - switch typ { - case "status", "mods": - return char.StatusIsActive(val), nil + case "status": + if err := fieldsCheck(fields, 3, "character "+typ); err != nil { + return 0, err + } + return char.StatusIsActive(fields[2]), nil + case "mods": + if err := fieldsCheck(fields, 3, "character "+typ); err != nil { + return 0, err + } + return char.StatModIsActive(fields[2]), nil case "infusion": - return c.Player.WeaponInfuseIsActive(char.Index, val), nil + if err := fieldsCheck(fields, 3, "character "+typ); err != nil { + return 0, err + } + return c.Player.WeaponInfuseIsActive(char.Index, fields[2]), nil case "tags": - return char.Tag(val), nil + if err := fieldsCheck(fields, 3, "character "+typ); err != nil { + return 0, err + } + return char.Tag(fields[2]), nil case "stats": - return evalCharacterStats(char, val) - default: // .kokomi..* + if err := fieldsCheck(fields, 3, "character "+typ); err != nil { + return 0, err + } + return evalCharacterStats(char, fields[2]) + default: // .kokomi.* return char.Condition(fields[1:]) } + } func evalCharacterStats(char *character.CharWrapper, stat string) (float64, error) { From 11d4ffd838b41a140ae3d15dc132d1a3bb376811 Mon Sep 17 00:00:00 2001 From: Shizuka Date: Sat, 20 Aug 2022 05:04:31 +0400 Subject: [PATCH 6/8] Implement '.char.swap' --- pkg/conditional/character.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pkg/conditional/character.go b/pkg/conditional/character.go index 3b83e98c2..5695b5b2f 100644 --- a/pkg/conditional/character.go +++ b/pkg/conditional/character.go @@ -27,7 +27,7 @@ func evalCharacter(c *core.Core, key keys.Char, fields []string) (any, error) { if err := fieldsCheck(fields, 3, "character ability"); err != nil { return 0, err } - return evalCharacterAbil(char, act, fields[2]) + return evalCharacterAbil(c, char, act, fields[2]) } switch typ { @@ -82,13 +82,19 @@ func evalCharacterStats(char *character.CharWrapper, stat string) (float64, erro return char.Stat(key), nil } -func evalCharacterAbil(char *character.CharWrapper, act action.Action, typ string) (any, error) { +func evalCharacterAbil(c *core.Core, char *character.CharWrapper, act action.Action, typ string) (any, error) { switch typ { case "cd": + if act == action.ActionSwap { + return c.Player.SwapCD, nil + } return char.Cooldown(act), nil case "charge": return char.Charges(act), nil case "ready": + if act == action.ActionSwap { + return c.Player.SwapCD == 0 || c.Player.Active() == char.Index, nil + } //TODO: nil map may cause problems here?? return char.ActionReady(act, nil), nil default: From 195f64f7f3cc8f740f02e517a0bebe793b7ea003 Mon Sep 17 00:00:00 2001 From: Shizuka Date: Sat, 20 Aug 2022 05:05:59 +0400 Subject: [PATCH 7/8] Avoid modifying condition fields --- pkg/conditional/character.go | 4 +++- pkg/conditional/conditional.go | 5 ----- pkg/gcs/ast/parse.go | 3 ++- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/pkg/conditional/character.go b/pkg/conditional/character.go index 5695b5b2f..9895d6395 100644 --- a/pkg/conditional/character.go +++ b/pkg/conditional/character.go @@ -69,7 +69,9 @@ func evalCharacter(c *core.Core, key keys.Char, fields []string) (any, error) { } return evalCharacterStats(char, fields[2]) default: // .kokomi.* - return char.Condition(fields[1:]) + v := make([]string, len(fields[1:])) + copy(v, fields[1:]) + return char.Condition(v) } } diff --git a/pkg/conditional/conditional.go b/pkg/conditional/conditional.go index c401d29b0..9347cc871 100644 --- a/pkg/conditional/conditional.go +++ b/pkg/conditional/conditional.go @@ -2,7 +2,6 @@ package conditional import ( "fmt" - "strings" "github.com/genshinsim/gcsim/pkg/core" "github.com/genshinsim/gcsim/pkg/shortcut" @@ -16,10 +15,6 @@ func fieldsCheck(fields []string, expecting int, category string) error { } func Eval(c *core.Core, fields []string) (any, error) { - for i := 0; i < len(fields); i++ { - fields[i] = strings.Trim(fields[i], ".") - } - switch fields[0] { case "debuff": return evalDebuff(c, fields) diff --git a/pkg/gcs/ast/parse.go b/pkg/gcs/ast/parse.go index 8233b8e48..ab18c0288 100644 --- a/pkg/gcs/ast/parse.go +++ b/pkg/gcs/ast/parse.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "strconv" + "strings" "github.com/genshinsim/gcsim/pkg/core/keys" "github.com/genshinsim/gcsim/pkg/shortcut" @@ -701,7 +702,7 @@ func (p *Parser) parseField() (Expr, error) { n := p.next() fields := make([]string, 0, 5) for ; n.Typ == itemField; n = p.next() { - fields = append(fields, n.Val) + fields = append(fields, strings.Trim(n.Val, ".")) } //we would have consumed one too many here p.backup() From 3950cb2c36299f53d8e0f780c26d02097e084458 Mon Sep 17 00:00:00 2001 From: Shizuka Date: Sat, 20 Aug 2022 05:26:13 +0400 Subject: [PATCH 8/8] Remove unnecessary alloc in conditional --- pkg/conditional/character.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/conditional/character.go b/pkg/conditional/character.go index 9895d6395..5695b5b2f 100644 --- a/pkg/conditional/character.go +++ b/pkg/conditional/character.go @@ -69,9 +69,7 @@ func evalCharacter(c *core.Core, key keys.Char, fields []string) (any, error) { } return evalCharacterStats(char, fields[2]) default: // .kokomi.* - v := make([]string, len(fields[1:])) - copy(v, fields[1:]) - return char.Condition(v) + return char.Condition(fields[1:]) } }