tdBattlePetScript

tdBattlePetScript

389k Downloads

Aura Check failing

perrinaz opened this issue · 3 comments

commented

I have the following line in a script and it keeps executing if the command is available regardless of whether the aura is active:

use(Minefield:634) [!enemy.aura(Minefield:635).exists]

Obviously, I want to reapply Minefield if the Minefield isn't there (either by timeout or by having been triggered by a new pet being swapped in). I created the line through the auto-fill method of creating the script while in combat. When that didn't work, I copied the line from another script. When that didn't work, I tried

use(Minefield:634) [enemy.round=1]

The idea was to at least limit the script to only firing minefield if exploded because a pet was swapped in but the ability was cast as soon as it was off timeout regardless.

Either I'm getting something wrong or this is a bug.

D

P.S. Minefield isn't called anywhere else in the script -- just to make sure it was my error.

commented

This should now be fixed :)

commented

d99b9f0 appears to be at least part of the issue:

44x tdBattlePetScript\Core\Condition.lua:34: attempt to compare nil with boolean
[string "@tdBattlePetScript\Core\Condition.lua"]:34: in function <tdBattlePetScript\Core\Condition.lua:34>
[string "@tdBattlePetScript\Core\Condition.lua"]:97: in function `Run'
[string "@tdBattlePetScript\Core\Director.lua"]:32: in function `Action'
[string "@tdBattlePetScript\Core\Director.lua"]:22: in function `Run'
[string "@tdBattlePetScript\UI\PetBattle.lua"]:312: in function `OnAutoButtonClick'
[string "@tdBattlePetScript\UI\PetBattle.lua"]:67: in function <tdBattlePetScript\UI\PetBattle.lua:66>

Locals:
a = false
b = nil

which does make sense opts.arg is set, but arg is nil as parsing the ability fails (no ability with same name or id). Thus ['<'] = function(a, b) return a < b end,, i.e. false < nil is bogus. Before, it did not call < in that case but just returned false.

The script

test("dead") [ self(nope).dead ]
test("!dead") [ !self(nope).dead ]
test("stop")

before that commit ended up with stop and now results in !dead.

I feel the expected behavior actually depends on the operation/context. I wouldn't call a non-existent pet alive but error out, which kind of ties in with discussions I had on the xufu discord:

condition what I personally expect when no such pet/ability if I'm not allowed to say error
dead error true
hp, hp.full, hpp probably error as well, else as if 0 hp and infinite max hp
hp.can_explode probably error as well, else as if dead
aura.exists, weather false, error if pet doesn't exist
aura.duration, weather.duration 0, error if pet doesn't exist
active false or error
ability.usable false
ability.duration infinite
ability.strong, ability.weak, speed.fast, speed.slow false or error
ability.type, type, quality, speed, power, level error type -1, remainder 0?!
hp.low, hp.high, round ---
played false or error
level.max error, but might make sense for capture scripts and enemy slots (if enemy pet slot three is max level but there are only two enemies) false
exists false
id error -1
is false or error

The "false or error" ones kind of only make sense for abilities pets that do exist and otherwise the script makes very little sense. Why would you check if a pet that's not in the team can explode? Why would you check if an ability that you don't have is strong?

As you can see this is mostly "as if nil" or "error please", except for ability.duration/ability.usable, which are inverse.

I think this can be done by instead of always returning false or calling op on false, we remove the two checks and always call op and the function:

function Condition:RunCondition(condition)
    local owner, pet, cmd, arg, op, value = self:ParseCondition(condition)

    local fn  = self.apis[cmd]
    local opts = self.opts[cmd]
    if not fn then
        error('Big Bang !!!!!!')
    end

    local res = fn(owner, pet, arg)
    return opTabler[opts.type][op](res, value)
end

Then, in the various conditions we can handle the condition specific handling of parsing resulting in nil:

local infinite = tonumber('inf')
local function logical_health(owner, pet)
    return C_PetBattles.GetHealth(owner, pet) or 0
end
local function logical_max_health(owner, pet)
    return C_PetBattles.GetMaxHealth(owner, pet) or infinite
end

--

Addon:RegisterCondition('hp.full', { type = 'boolean', arg = false }, function(owner, pet)
    return logical_health(owner, pet) == logical_max_health(owner, pet)
end)
Addon:RegisterCondition('ability.duration', { type = 'compare', argParse = Util.ParseAbility }, function(owner, pet, ability)
    local ability = logical_ability(owner, pet, ability)
    return ability and select(2, ability) or infinite
end)

As I didn't want to introduce errors to the evaluation (which currently appears not to be a thing?) I came up with the following unit test:

test(expected: self(pet).dead) [ !self(pet).dead ]
test(expected: self(pet).hp == 0) [ self(pet).hp != 0 ]
test(expected: !self(pet).hp.full) [ self(pet).hp.full ]
test(expected: !self(pet).hp.can_explode) [ self(pet).hp.can_explode ]
-- no pet, no arg: hp.low
-- no pet, no arg: hp.high
test(expected: self(pet).hpp == 0) [ self(pet).hpp != 0 ]
test(expected: !self.aura(arg).exists) [ self.aura(arg).exists ]
test(expected: !self(#1).aura(arg).exists) [ self(#1).aura(arg).exists ]
test(expected: !self(pet).aura(arg).exists) [ self(pet).aura(arg).exists ]
test(expected: self.aura(arg).duration == 0) [ self.aura(arg).duration != 0 ]
test(expected: self(#1).aura(arg).duration == 0) [ self(#1).aura(arg).duration != 0 ]
test(expected: self(pet).aura(arg).duration == 0) [ self(pet).aura(arg).duration != 0 ]
test(expected: !weather(arg)) [ weather(arg) ]
test(expected: weather(arg).duration == 0) [ weather(arg).duration != 0 ]
test(expected: !self(pet).active) [ self(pet).active ]
test(expected: !self.ability(arg).usable) [ self.ability(arg).usable ]
test(expected: !self(#1).ability(arg).usable) [ self(#1).ability(arg).usable ]
test(expected: !self(pet).ability(arg).usable) [ self(pet).ability(arg).usable ]
test(expected: self.ability(arg).duration == inf) [ self.ability(arg).duration < 1000000 ]
test(expected: self(#1).ability(arg).duration == inf) [ self(#1).ability(arg).duration < 1000000 ]
test(expected: self(pet).ability(arg).duration == inf) [ self(pet).ability(arg).duration < 1000000 ]
test(expected: !self.ability(arg).strong) [ self.ability(arg).strong ]
test(expected: !self(#1).ability(arg).strong) [ self(#1).ability(arg).strong ]
test(expected: !self(pet).ability(arg).strong) [ self(pet).ability(arg).strong ]
test(expected: !self.ability(arg).weak) [ self.ability(arg).weak ]
test(expected: !self(#1).ability(arg).weak) [ self(#1).ability(arg).weak ]
test(expected: !self(pet).ability(arg).weak) [ self(pet).ability(arg).weak ]
test(expected: self.ability(arg).type !~ 1,2,3,4,5,6,7,8,9,10) [ self.ability(arg).type ~ 1,2,3,4,5,6,7,8,9,10 ]
test(expected: self(#1).ability(arg).type !~ 1,2,3,4,5,6,7,8,9,10) [ self(#1).ability(arg).type ~ 1,2,3,4,5,6,7,8,9,10 ]
test(expected: self(pet).ability(arg).type !~ 1,2,3,4,5,6,7,8,9,10) [ self(pet).ability(arg).type ~ 1,2,3,4,5,6,7,8,9,10 ]
-- no pet, no arg: round
test(expected: !self(pet).played) [ self(pet).played ]
test(expected: self(pet).speed == 0) [ self(pet).speed != 0 ]
test(expected: self(pet).power == 0) [ self(pet).power != 0 ]
test(expected: self(pet).level == 0) [ self(pet).level != 0 ]
test(expected: !self(pet).level.max) [ self(pet).level.max ]
-- no pet, no arg: speed.fast
-- no pet, no arg: speed.slow
test(expected: self(pet).type !~ 1,2,3,4,5,6,7,8,9,10) [ self(pet).type ~ 1,2,3,4,5,6,7,8,9,10 ]
test(expected: self(pet).quality !~ 1,2,3,4,5,6) [ self(pet).quality ~ 1,2,3,4,5,6 ]
test(expected: !self(pet).exists) [ self(pet).exists ]
test(expected: !self(pet).is(other)) [ self(pet).is(other) ]
test(expected: !self(pet).is(#1)) [ self(pet).is(#1) ]
test(expected: !self(#1).is(other)) [ self(#1).is(other) ]
test(expected: self(pet).id == 0) [ self(pet).id != 0 ]
test(passed)

This is implemented in the MR #38 I made.

commented

Unfortunately I have the problem too. :( You use the option very often. Such as
ability (Ghostly Bite: 654) [enemy.ability (Mudslide: 572) .duration <5]
Unfortunately, that doesn't work either.