Aura Check failing
perrinaz opened this issue · 3 comments
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.
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.