Files
WoWee/tests/test_pet_action.cpp
Kelsi a0c4c59791 fix(pet): send the dismiss command instead of telling the pet to stay
CMSG_PET_ACTION's action field is a pair, not an id: the high byte says what
kind of action it is and the low 24 bits say which one. Commands and stances
reuse the same small numbers, so 1 is follow as a command and defensive as a
stance, and only the byte above them tells the two apart.

Four callers each read that differently. Dismiss packed action 0 under the
command type, which is COMMAND_STAY — the pet planted itself and stayed. The
Lua bindings had the type and the action swapped. The chat commands sent a
bare 1..6 with no type byte at all. The action bar labelled slots off a flat
1..6 numbering that does not exist on the wire, so it mislabelled the built-in
buttons and left the id-0 ones blank, and the stance row matched stance ids
against command slots.

The encoding now lives in game/pet_action.hpp and every caller builds through
it. Bar slots the server sends down were already packed correctly and still go
back unchanged, which is why clicking the bar worked while nothing else did.
2026-07-31 19:30:20 -07:00

65 lines
3.0 KiB
C++

#include <catch_amalgamated.hpp>
#include "game/pet_action.hpp"
using namespace wowee::game::pet;
// The bug these cover: dismiss packed action 0 under the command type, which is
// COMMAND_STAY, so the pet planted itself instead of leaving. The ids overlap
// between the two built-in types, so a test that only checks the low bits would
// have passed on the broken encoding too — each case pins both halves.
TEST_CASE("pet action packs type into the high byte", "[pet]") {
REQUIRE(packPetAction(ActionType::Command, kAbandon) == 0x07000003u);
REQUIRE(packPetAction(ActionType::Command, kStay) == 0x07000000u);
REQUIRE(packPetAction(ActionType::Command, kFollow) == 0x07000001u);
REQUIRE(packPetAction(ActionType::Command, kAttack) == 0x07000002u);
REQUIRE(packPetAction(ActionType::Reaction, kPassive) == 0x06000000u);
REQUIRE(packPetAction(ActionType::Reaction, kDefensive) == 0x06000001u);
REQUIRE(packPetAction(ActionType::Reaction, kAggressive) == 0x06000002u);
}
TEST_CASE("dismiss is not stay", "[pet]") {
REQUIRE(packPetAction(ActionType::Command, kAbandon) !=
packPetAction(ActionType::Command, kStay));
}
TEST_CASE("built-in ids collide across types and are told apart by type", "[pet]") {
const uint32_t follow = packPetAction(ActionType::Command, kFollow);
const uint32_t defensive = packPetAction(ActionType::Reaction, kDefensive);
// Same low bits — the id alone cannot distinguish them.
REQUIRE(petActionId(follow) == petActionId(defensive));
REQUIRE(follow != defensive);
REQUIRE(petActionType(follow) == ActionType::Command);
REQUIRE(petActionType(defensive) == ActionType::Reaction);
}
TEST_CASE("round trip through unpack", "[pet]") {
for (uint32_t id : {kStay, kFollow, kAttack, kAbandon, kMoveTo}) {
const uint32_t packed = packPetAction(ActionType::Command, id);
REQUIRE(petActionId(packed) == id);
REQUIRE(petActionType(packed) == ActionType::Command);
}
}
TEST_CASE("only castable slots count as spells", "[pet]") {
// Commands and stances are not spells, whatever their id — the old test for
// this was "id > 6", which called stay/follow/attack spells the moment the
// server sent them with their real ids of 0/1/2.
REQUIRE_FALSE(isPetSpellAction(packPetAction(ActionType::Command, kStay)));
REQUIRE_FALSE(isPetSpellAction(packPetAction(ActionType::Command, kAttack)));
REQUIRE_FALSE(isPetSpellAction(packPetAction(ActionType::Reaction, kPassive)));
REQUIRE(isPetSpellAction(packPetAction(ActionType::Disabled, 2649u)));
REQUIRE(isPetSpellAction(packPetAction(ActionType::Enabled, 2649u)));
REQUIRE(isPetSpellAction(packPetAction(ActionType::Passive, 3110u)));
}
TEST_CASE("a spell id larger than 24 bits cannot corrupt the type", "[pet]") {
const uint32_t packed = packPetAction(ActionType::Enabled, 0xFFFFFFFFu);
REQUIRE(petActionType(packed) == ActionType::Enabled);
REQUIRE(petActionId(packed) == 0x00FFFFFFu);
}