struct npc_think_data
{
std::uint32_t m_iTeamNum;
std::uint32_t m_iHeroID;
std::uint32_t m_hReplicatingOtherHeroModel;
std::uint32_t m_clrRender;
std::uint32_t m_bHasClientSeenIllusionModifier;
std::uint32_t m_iTaggedAsVisibleByTeam;
std::uint32_t m_nUnitState64;
std::uint32_t m_iUnitType;
std::uint32_t m_hMyWearables;
color_32 illusion_color{};
std::uint64_t unit_state_flag{};
const C_BaseEntity** LocalPlayerAddress;
void(*OnColorChanged)(C_BaseEntity*);
C_BaseEntity*(*GetEntityByIndex)(std::uint32_t index);
std::uint8_t(*entity_get_team)(const C_BaseEntity*);
std::uint8_t(*get_local_team)();
bool(*is_good_hero)(const C_BaseEntity*);
void(*illusion_think)(C_BaseEntity*);
void(*vbe_think)(C_BaseEntity*);
void(*npc_think_hook)(C_BaseEntity*, int unk);
void(*npc_think_original)(C_BaseEntity*, int unk);
void(*OnUnitStateChanged_hook)(C_BaseEntity*, C_BaseEntity*, std::uint64_t* new_value);
void(*OnUnitStateChanged_original)(C_BaseEntity*, C_BaseEntity*, std::uint64_t* new_value);
};
npc_think_data* think_data_placeholder{};
extern "C"
{
/*
48 8B 05 ?? ?? ?? ?? 8B 00 0F B6 04 08 C3
*/
std::uint8_t entity_get_team(const C_BaseEntity* entity)
{
const auto& data = *think_data_placeholder;
return entity->Member<std::uint8_t>(data.m_iTeamNum);
}
auto assemble_entity_get_team(const std::uint8_t* at, void* think_data_ptr)
{
auto result = std::to_array<std::uint8_t>({
/* 0 */ 0x48, 0x8B, 0x05,
'?', '?', '?', '?',
0x8B, 0x00, 0x0F, 0xB6, 0x04, 0x08, 0xC3
});
std::ranges::copy(serialize_word<std::endian::little>
(disp32(at + 0x0 + 7, think_data_ptr)),
result.data() + 0x0 + 3);
return result;
}
constexpr auto entity_get_team_code_size = std::tuple_size_v<decltype(assemble_entity_get_team(nullptr, nullptr))>;
/*
48 8B 15 ?? ?? ?? ?? 48 8B 42 30 48 8B 08 48 85 C9 74 07 48 8B 42 48 48 FF E0 32 C0 C3
*/
std::uint8_t get_local_team()
{
const auto& data = *think_data_placeholder;
if (const auto player = *data.LocalPlayerAddress; player)
{
return data.entity_get_team(player);
}
return 0;
}
auto assemble_get_local_team(const std::uint8_t* at, void* think_data_ptr)
{
auto result = std::to_array<std::uint8_t>({
/* 0 */ 0x48, 0x8B, 0x15,
'?', '?', '?', '?',
0x48, 0x8B, 0x42, 0x30, 0x48, 0x8B, 0x08, 0x48, 0x85, 0xC9, 0x74, 0x07,
0x48, 0x8B, 0x42, 0x48, 0x48, 0xFF, 0xE0, 0x32, 0xC0, 0xC3
});
std::ranges::copy(serialize_word<std::endian::little>
(disp32(at + 0x0 + 7, think_data_ptr)),
result.data() + 0x0 + 3);
return result;
}
constexpr auto get_local_team_code_size = std::tuple_size_v<decltype(assemble_get_local_team(nullptr, nullptr))>;
/*
48 8B 05 ?? ?? ?? ?? 8B 50 04 8B 04 0A 83 F8 0A 74 0D 83 F8 52 74 08 83 F8 71 74 03 B0 01 C3 32 C0 C3
*/
bool is_good_hero(const C_BaseEntity* entity)
{
const auto& data = *think_data_placeholder;
const auto heroID = entity->Member<std::uint32_t>(data.m_iHeroID);
/* scripts/npc/npc_heroes.txt */
constexpr auto Morphling = 10;
constexpr auto Meepo = 82;
constexpr auto ArcWarden = 113;
return (heroID != Morphling)
&& (heroID != Meepo)
&& (heroID != ArcWarden);
}
auto assemble_is_good_hero(const std::uint8_t* at, void* think_data_ptr)
{
auto result = std::to_array<std::uint8_t>({
/* 0 */ 0x48, 0x8B, 0x05,
'?', '?', '?', '?',
0x8B, 0x50, 0x04, 0x8B, 0x04, 0x0A, 0x83, 0xF8, 0x0A, 0x74, 0x0D, 0x83, 0xF8, 0x52, 0x74, 0x08, 0x83, 0xF8, 0x71, 0x74, 0x03, 0xB0, 0x01, 0xC3, 0x32, 0xC0, 0xC3
});
std::ranges::copy(serialize_word<std::endian::little>
(disp32(at + 0x0 + 7, think_data_ptr)),
result.data() + 0x0 + 3);
return result;
}
constexpr auto is_good_hero_code_size = std::tuple_size_v<decltype(assemble_is_good_hero(nullptr, nullptr))>;
/*
48 89 5C 24 18 57 48 83 EC 20 48 8B 3D ?? ?? ?? ?? 48 8B D9 48 8B 47 58 FF D0 84 C0 0F
84 A6 00 00 00 8B 47 08 83 3C 18 FF 0F 84 99 00 00 00 0F B6 47 26 8B 4F 0C 48 89 74 24
38 0F B6 77 27 C1 E6 08 0B F0 0F B6 47 25 C1 E6 08 0B F0 0F B6 47 24 C1 E6 08 0B F0 83
3C 19 FF 75 67 8B 47 10 48 89 6C 24 30 C6 04 18 01 89 34 19 48 8B CB FF 57 38 8B 47 20
48 03 C3 48 8B 58 08 48 63 00 48 8D 2C 83 48 3B DD 74 37 66 0F 1F 84 00 00 00 00 00 8B
0B 48 8B 47 40 81 E1 FF 7F 00 00 FF D0 48 85 C0 74 12 8B 4F 0C 83 3C 01 FF 75 09 89 34
01 48 8B C8 FF 57 38 48 83 C3 04 48 3B DD 75 D2 48 8B 6C 24 30 48 8B 74 24 38 48 8B 5C
24 40 48 83 C4 20 5F C3
*/
void illusion_think(C_BaseEntity* entity)
{
const auto& data = *think_data_placeholder;
if (data.is_good_hero(entity))
{
if (entity->Member<std::uint32_t>(data.m_hReplicatingOtherHeroModel)
!= std::bit_cast<std::uint32_t>(-1))
{
const auto illu_clr = data.illusion_color.get();
auto& ent_clr
= entity->Member<std::uint32_t>(data.m_clrRender);
if (ent_clr == 0xFFFFFFFF)
{
entity->Member<bool>(data.m_bHasClientSeenIllusionModifier) = true;
ent_clr = illu_clr;
data.OnColorChanged(entity);
const auto& wearable_handles
= entity->Member<CUtlVector<std::uint32_t>>(data.m_hMyWearables);
for (auto handle : wearable_handles.span())
{
const auto wearable = data.GetEntityByIndex(handle & 0x7FFF);
if (wearable)
{
auto& wearable_clr
= wearable->Member<std::uint32_t>(data.m_clrRender);
if (wearable_clr == 0xFFFFFFFF)
{
wearable_clr = illu_clr;
data.OnColorChanged(wearable);
}
}
}
}
}
}
}
auto assemble_illusion_think(const std::uint8_t* at, void* think_data_ptr)
{
auto result = std::to_array<std::uint8_t>({
/* 0 */ 0x48, 0x89, 0x5C, 0x24, 0x18, 0x57, 0x48, 0x83, 0xEC, 0x20,
/* A */ 0x48, 0x8B, 0x3D,
'?', '?', '?', '?',
0x48, 0x8B, 0xD9, 0x48, 0x8B, 0x47, 0x58, 0xFF, 0xD0, 0x84, 0xC0, 0x0F, 0x84,
0xA6, 0x00, 0x00, 0x00, 0x8B, 0x47, 0x08, 0x83, 0x3C, 0x18, 0xFF, 0x0F, 0x84,
0x99, 0x00, 0x00, 0x00, 0x0F, 0xB6, 0x47, 0x26, 0x8B, 0x4F, 0x0C, 0x48, 0x89,
0x74, 0x24, 0x38, 0x0F, 0xB6, 0x77, 0x27, 0xC1, 0xE6, 0x08, 0x0B, 0xF0, 0x0F,
0xB6, 0x47, 0x25, 0xC1, 0xE6, 0x08, 0x0B, 0xF0, 0x0F, 0xB6, 0x47, 0x24, 0xC1,
0xE6, 0x08, 0x0B, 0xF0, 0x83, 0x3C, 0x19, 0xFF, 0x75, 0x67, 0x8B, 0x47, 0x10,
0x48, 0x89, 0x6C, 0x24, 0x30, 0xC6, 0x04, 0x18, 0x01, 0x89, 0x34, 0x19, 0x48,
0x8B, 0xCB, 0xFF, 0x57, 0x38, 0x8B, 0x47, 0x20, 0x48, 0x03, 0xC3, 0x48, 0x8B,
0x58, 0x08, 0x48, 0x63, 0x00, 0x48, 0x8D, 0x2C, 0x83, 0x48, 0x3B, 0xDD, 0x74,
0x37, 0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8B, 0x0B, 0x48,
0x8B, 0x47, 0x40, 0x81, 0xE1, 0xFF, 0x7F, 0x00, 0x00, 0xFF, 0xD0, 0x48, 0x85,
0xC0, 0x74, 0x12, 0x8B, 0x4F, 0x0C, 0x83, 0x3C, 0x01, 0xFF, 0x75, 0x09, 0x89,
0x34, 0x01, 0x48, 0x8B, 0xC8, 0xFF, 0x57, 0x38, 0x48, 0x83, 0xC3, 0x04, 0x48,
0x3B, 0xDD, 0x75, 0xD2, 0x48, 0x8B, 0x6C, 0x24, 0x30, 0x48, 0x8B, 0x74, 0x24,
0x38, 0x48, 0x8B, 0x5C, 0x24, 0x40, 0x48, 0x83, 0xC4, 0x20, 0x5F, 0xC3
});
std::ranges::copy(serialize_word<std::endian::little>
(disp32(at + 0xA + 7, think_data_ptr)),
result.data() + 0xA + 3);
return result;
}
constexpr auto illusion_think_code_size = std::tuple_size_v<decltype(assemble_illusion_think(nullptr, nullptr))>;
/*
48 83 EC 28 4C 8B 0D ?? ?? ?? ?? 41 8B 41 14 44 8B 04 08 41 F6 C0 10 74 5C 41 8B 41 18
41 83 E0 0C 49 8B 51 28 4C 8B 14 08 49 8B C2 48 23 C2 41 80 F8 0C 75 21 48 85 C0 75 3B
4C 0B D2 4C 8D 44 24 30 48 8B D1 4C 89 54 24 30 41 FF 91 88 00 00 00 48 83 C4 28 C3 48
85 C0 74 1A 48 F7 D2 4C 8D 44 24 30 49 23 D2 48 89 54 24 30 48 8B D1 41 FF 91 88 00 00
00 48 83 C4 28 C3
*/
void vbe_think(C_BaseEntity* entity)
{
const auto& data = *think_data_placeholder;
enum DOTATeam_t
{
DOTA_TEAM_GOODGUYS = 2,
DOTA_TEAM_BADGUYS = 3,
DOTA_TEAM_NEUTRALS = 4,
};
constexpr auto both_teams = (1 << DOTA_TEAM_GOODGUYS) | (1 << DOTA_TEAM_BADGUYS);
const auto vis = entity->Member<std::uint32_t>(data.m_iTaggedAsVisibleByTeam);
/*
Neutrals always see you; if they don't, netvar's value was erased
if they do, netvar was just updated.
*/
if (vis & (1 << DOTA_TEAM_NEUTRALS))
{
const auto flag = data.unit_state_flag;
auto& state = entity->Member<std::uint64_t>(data.m_nUnitState64);
/* Visible */
if ((vis & both_teams) == both_teams)
{
/* Wasn't visible */
if ((state & flag) == 0)
{
std::uint64_t new_state = state | flag;
data.OnUnitStateChanged_original(entity, entity, &new_state);
}
}
/* Not visible */
else
{
/* Was visible */
if ((state & flag) != 0)
{
std::uint64_t new_state = state & (~flag);
data.OnUnitStateChanged_original(entity, entity, &new_state);
}
}
}
}
auto assemble_vbe_think(const std::uint8_t* at, void* think_data_ptr)
{
auto result = std::to_array<std::uint8_t>({
/* 0 */ 0x48, 0x83, 0xEC, 0x28,
/* 4 */ 0x4C, 0x8B, 0x0D,
'?', '?', '?', '?',
0x41, 0x8B, 0x41, 0x14, 0x44, 0x8B, 0x04, 0x08, 0x41, 0xF6, 0xC0, 0x10, 0x74, 0x5C,
0x41, 0x8B, 0x41, 0x18, 0x41, 0x83, 0xE0, 0x0C, 0x49, 0x8B, 0x51, 0x28, 0x4C, 0x8B,
0x14, 0x08, 0x49, 0x8B, 0xC2, 0x48, 0x23, 0xC2, 0x41, 0x80, 0xF8, 0x0C, 0x75, 0x21,
0x48, 0x85, 0xC0, 0x75, 0x3B, 0x4C, 0x0B, 0xD2, 0x4C, 0x8D, 0x44, 0x24, 0x30, 0x48,
0x8B, 0xD1, 0x4C, 0x89, 0x54, 0x24, 0x30, 0x41, 0xFF, 0x91, 0x88, 0x00, 0x00, 0x00,
0x48, 0x83, 0xC4, 0x28, 0xC3, 0x48, 0x85, 0xC0, 0x74, 0x1A, 0x48, 0xF7, 0xD2, 0x4C,
0x8D, 0x44, 0x24, 0x30, 0x49, 0x23, 0xD2, 0x48, 0x89, 0x54, 0x24, 0x30, 0x48, 0x8B,
0xD1, 0x41, 0xFF, 0x91, 0x88, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x28, 0xC3
});
std::ranges::copy(serialize_word<std::endian::little>
(disp32(at + 0x4 + 7, think_data_ptr)),
result.data() + 0x4 + 3);
return result;
}
constexpr auto vbe_think_code_size = std::tuple_size_v<decltype(assemble_vbe_think(nullptr, nullptr))>;
/*
4C 8B 15 ?? ?? ?? ?? 41 8B 42 18 4C 8B 0C 08 4D 23 4A 28 4D 09 08 49 FF A2 88 00 00 00
*/
void OnUnitStateChanged_hook(C_BaseEntity* entity, C_BaseEntity* entity_, std::uint64_t* new_value)
{
const auto& data = *think_data_placeholder;
const auto old_value = entity->Member<std::uint64_t>(data.m_nUnitState64);
/* always preserve the flag even when the server changes the state */
*new_value |= (old_value & data.unit_state_flag);
return data.OnUnitStateChanged_original(entity, entity_, new_value);
}
auto assemble_OnUnitStateChanged_hook(const std::uint8_t* at, void* think_data_ptr)
{
auto result = std::to_array<std::uint8_t>({
/* 0 */ 0x4C, 0x8B, 0x15,
'?', '?', '?', '?',
0x41, 0x8B, 0x42, 0x18, 0x4C, 0x8B, 0x0C, 0x08, 0x4D, 0x23, 0x4A,
0x28, 0x4D, 0x09, 0x08, 0x49, 0xFF, 0xA2, 0x88, 0x00, 0x00, 0x00
});
std::ranges::copy(serialize_word<std::endian::little>
(disp32(at + 0x0 + 7, think_data_ptr)),
result.data() + 0x0 + 3);
return result;
}
constexpr auto OnUnitStateChanged_hook_code_size = std::tuple_size_v<decltype(assemble_OnUnitStateChanged_hook(nullptr, nullptr))>;
/*
48 89 5C 24 10 48 89 6C 24 18 57 48 83 EC 20 48 8B 1D ?? ?? ?? ?? 8B EA 48 8B F9 48 85 C9 74 3A
48 89 74 24 30 FF 53 50 0F B6 F0 84 C0 74 26 4C 8B 43 48 48 8B CF 41 FF D0 40 3A C6 75 08 48 8B
CF FF 53 68 EB 0F 8B 43 1C 83 3C 38 01 75 06 48 8B CF FF 53 60 48 8B 74 24 30 8B D5 48 8B CF 48
8B 43 78 48 8B 5C 24 38 48 8B 6C 24 40 48 83 C4 20 5F 48 FF E0
*/
void npc_think_hook(C_BaseEntity* entity, int unk)
{
const auto& data = *think_data_placeholder;
if (entity)
{
const auto local_team = data.get_local_team();
if (local_team != 0)
{
if (data.entity_get_team(entity) == local_team)
{
data.vbe_think(entity);
}
else
{
constexpr auto HERO = 1;
if (entity->Member<std::uint32_t>(data.m_iUnitType) == HERO)
{
data.illusion_think(entity);
}
}
}
}
return data.npc_think_original(entity, unk);
}
auto assemble_npc_think_hook(const std::uint8_t* at, void* think_data_ptr)
{
auto result = std::to_array<std::uint8_t>({
/* 0 */ 0x48, 0x89, 0x5C, 0x24, 0x10, 0x48, 0x89, 0x6C, 0x24, 0x18, 0x57, 0x48, 0x83, 0xEC, 0x20,
/* F */ 0x48, 0x8B, 0x1D,
'?', '?', '?', '?',
0x8B, 0xEA, 0x48, 0x8B, 0xF9, 0x48, 0x85, 0xC9, 0x74, 0x3A, 0x48, 0x89, 0x74, 0x24,
0x30, 0xFF, 0x53, 0x50, 0x0F, 0xB6, 0xF0, 0x84, 0xC0, 0x74, 0x26, 0x4C, 0x8B, 0x43,
0x48, 0x48, 0x8B, 0xCF, 0x41, 0xFF, 0xD0, 0x40, 0x3A, 0xC6, 0x75, 0x08, 0x48, 0x8B,
0xCF, 0xFF, 0x53, 0x68, 0xEB, 0x0F, 0x8B, 0x43, 0x1C, 0x83, 0x3C, 0x38, 0x01, 0x75,
0x06, 0x48, 0x8B, 0xCF, 0xFF, 0x53, 0x60, 0x48, 0x8B, 0x74, 0x24, 0x30, 0x8B, 0xD5,
0x48, 0x8B, 0xCF, 0x48, 0x8B, 0x43, 0x78, 0x48, 0x8B, 0x5C, 0x24, 0x38, 0x48, 0x8B,
0x6C, 0x24, 0x40, 0x48, 0x83, 0xC4, 0x20, 0x5F, 0x48, 0xFF, 0xE0
});
std::ranges::copy(serialize_word<std::endian::little>
(disp32(at + 0xF + 7, think_data_ptr)),
result.data() + 0xF + 3);
return result;
}
constexpr auto npc_think_hook_code_size = std::tuple_size_v<decltype(assemble_npc_think_hook(nullptr, nullptr))>;
}