mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-12 12:57:37 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7ccc11309 | ||
|
|
bab31b5f7b | ||
|
|
e0e24bab34 |
@@ -133,7 +133,7 @@ In the Frame rows, a name that starts with `:` is a method of the widget that th
|
||||
| [TradeSkillUI](docs/API.md#tradeskillui) | `C_TradeSkillUI.GetTradeSkillListLink`, `C_TradeSkillUI.GetCraftListLink`, `C_TradeSkillUI.GetTradeSkillListRecipes`, `GetTradeSkillDescription`, `GetTradeSkillRecipeLink`, `IsTradeSkillLinked` |
|
||||
| [Trainer](docs/API.md#trainer) | `GetTrainerServiceItemLink` |
|
||||
| [UIColor](docs/API.md#uicolor) | `C_UIColor.GetColors` |
|
||||
| [Unit](docs/API.md#unit) | `CanBeRaidTarget`, `ClosestUnitPosition`, `GetUnitSpeed`, `IsUnitToken`, `SetUnitCursorTexture`, `UnitClassBase`, `UnitCreatedBySpell`, `UnitCreatureFamilyID`, `UnitCreatureID`, `UnitCreatureTypeID`, `UnitDistanceSquared`, `UnitGUID`, `UnitHealthMissing`, `UnitInLineOfSight`, `UnitInRange`, `UnitIsAFK`, `UnitIsDND`, `UnitIsFeignDeath`, `UnitIsInMyGuild`, `UnitIsMinion`, `UnitIsOtherPlayersPet`, `UnitIsPet`, `UnitIsPossessed`, `UnitOwnerGUID`, `UnitPosition`, `UnitPower`, `UnitPowerMax`, `UnitPowerMissing`, `UnitPowerType`, `UnitRaceBase`, `UnitSelectionColor`, `UnitSelectionType`, `UnitSpellHaste`, `UnitSpellTargetName`, `UnitStandState`, `UnitSubName`, `UnitTokenFromGUID`, `UnitTokenFromName` |
|
||||
| [Unit](docs/API.md#unit) | `CanBeRaidTarget`, `ClosestUnitPosition`, `GetUnitSpeed`, `IsUnitToken`, `SetUnitCursorTexture`, `UnitClassBase`, `UnitCreatedBySpell`, `UnitCreatureFamilyID`, `UnitCreatureID`, `UnitCreatureTypeID`, `UnitDistanceSquared`, `UnitGUID`, `UnitHasIncomingResurrection`, `UnitHealthMissing`, `UnitInLineOfSight`, `UnitInRange`, `UnitIsAFK`, `UnitIsDND`, `UnitIsFeignDeath`, `UnitIsInMyGuild`, `UnitIsMinion`, `UnitIsOtherPlayersPet`, `UnitIsPet`, `UnitIsPossessed`, `UnitOwnerGUID`, `UnitPosition`, `UnitPower`, `UnitPowerMax`, `UnitPowerMissing`, `UnitPowerType`, `UnitRaceBase`, `UnitSelectionColor`, `UnitSelectionType`, `UnitSpellHaste`, `UnitSpellTargetName`, `UnitStandState`, `UnitSubName`, `UnitTokenFromGUID`, `UnitTokenFromName` |
|
||||
| [UnitAuras](docs/API.md#unitauras) | `C_UnitAuras.GetAuraDataByAuraInstanceID`, `C_UnitAuras.GetAuraDataByIndex`, `C_UnitAuras.GetAuraDataBySlot`, `C_UnitAuras.GetAuraDataBySpellName`, `C_UnitAuras.GetAuraDispelTypeColor`, `C_UnitAuras.GetAuraSlots`, `C_UnitAuras.GetBuffDataByIndex`, `C_UnitAuras.GetDebuffDataByIndex`, `C_UnitAuras.GetPlayerAuraBySpellID`, `C_UnitAuras.GetUnitAuraBySpellID`, `C_UnitAuras.GetUnitAuras`, `C_UnitAuras.RegisterAuraDurationModifierByTrigger`, `C_UnitAuras.RegisterComboDuration`, `C_UnitAuras.UnitAura`, `C_UnitAuras.UnitAuraBySlot`, `C_UnitAuras.UnitBuff`, `C_UnitAuras.UnitDebuff` |
|
||||
| [VoiceChat](docs/API.md#voicechat) | `C_VoiceChat.GetTtsVoices`, `C_VoiceChat.GetRemoteTtsVoices`, `C_VoiceChat.SpeakText`, `C_VoiceChat.StopSpeakingText`, `C_TTSSettings.GetSpeechRate`, `C_TTSSettings.GetSpeechVolume`, `C_TTSSettings.GetSpeechVoiceID`, `C_TTSSettings.GetVoiceOptionName`, `C_TTSSettings.SetSpeechRate`, `C_TTSSettings.SetSpeechVolume`, `C_TTSSettings.SetVoiceOption`, `C_TTSSettings.SetVoiceOptionByName`, `C_TTSSettings.SetDefaultSettings`, `C_TTSSettings.RefreshVoices` |
|
||||
| [XMLUtil](docs/API.md#xmlutil) | `C_XMLUtil.DoesTemplateExist`, `C_XMLUtil.GetTemplateInfo`, `C_XMLUtil.GetTemplates` |
|
||||
@@ -279,6 +279,7 @@ when launching with `-console`), not as Lua functions. See the
|
||||
| `GLOBAL_MOUSE_DOWN` | `button` |
|
||||
| `GLOBAL_MOUSE_UP` | `button` |
|
||||
| `HEARTHSTONE_BOUND` | *(none)* |
|
||||
| `INCOMING_RESURRECT_CHANGED` | `unitTarget` |
|
||||
| `INVENTORY_SEARCH_UPDATE` | *(none)* |
|
||||
| `ITEM_DATA_LOAD_RESULT` | `itemID, success` |
|
||||
| `LEARNED_SPELL_IN_SKILL_LINE` | `spellID, skillLineIndex, isGuildPerkSpell` |
|
||||
|
||||
+82
-9
@@ -223,6 +223,7 @@ build instructions.
|
||||
- [`EQUIPMENT_SWAP_FINISHED` event](#equipment_swap_finished-event)
|
||||
- [`WEAR_EQUIPMENT_SET` event](#wear_equipment_set-event)
|
||||
- [`FACTION_STANDING_CHANGED` event](#faction_standing_changed-event)
|
||||
- [`INCOMING_RESURRECT_CHANGED` event](#incoming_resurrect_changed-event)
|
||||
- [`INVENTORY_SEARCH_UPDATE` event](#inventory_search_update-event)
|
||||
- [`LOOT_HISTORY_ROLL_CHANGED` / `LOOT_HISTORY_ROLL_COMPLETE` / `LOOT_HISTORY_FULL_UPDATE` events](#loot_history_roll_changed--loot_history_roll_complete--loot_history_full_update-events)
|
||||
- [`LEARNED_SPELL_IN_SKILL_LINE` event](#learned_spell_in_skill_line-event)
|
||||
@@ -644,7 +645,7 @@ build instructions.
|
||||
- [`GetMacroIcons` / `GetMacroItemIcons` / `GetLooseMacroIcons` / `GetLooseMacroItemIcons`](#getmacroicons--getmacroitemicons--getloosemacroicons--getloosemacroitemicons)
|
||||
- [`C_Macro.CreateMacro` / `C_Macro.EditMacro`](#c_macrocreatemacro--c_macroeditmacro)
|
||||
- [`C_Macro.GetMacroIcon(macroSlot)`](#c_macrogetmacroiconmacroslot)
|
||||
- [`C_Macro.SetMacroDisplay(macroSlot, value)`](#c_macrosetmacrodisplaymacroslot-value)
|
||||
- [`C_Macro.SetMacroDisplay(macroSlot, value [, unit])`](#c_macrosetmacrodisplaymacroslot-value--unit)
|
||||
|
||||
- [Mail](#mail)
|
||||
- [`GetSendMailItemLink([attachmentIndex])`](#getsendmailitemlinkattachmentindex)
|
||||
@@ -1011,6 +1012,7 @@ build instructions.
|
||||
- [`UnitPowerType(unit)`](#unitpowertypeunit)
|
||||
- [`UnitSpellHaste(unit)`](#unitspellhasteunit)
|
||||
- [`UnitSpellTargetName(unit)`](#unitspelltargetnameunit)
|
||||
- [`UnitHasIncomingResurrection(unit)`](#unithasincomingresurrectionunit)
|
||||
- [`CanBeRaidTarget(unit)`](#canberaidtargetunit)
|
||||
|
||||
- [UnitAuras](#unitauras)
|
||||
@@ -5297,6 +5299,25 @@ forwarding, which [`C_Reputation.GetLastStandingChange`](#c_reputationgetlaststa
|
||||
exposes — so addons can read the structured payload from inside the
|
||||
chat event without re-parsing the localized string.
|
||||
|
||||
### `INCOMING_RESURRECT_CHANGED` event
|
||||
|
||||
Fires when a resurrection cast at a unit starts or stops. `arg1` is the unit
|
||||
token. The event gives no value.
|
||||
[`UnitHasIncomingResurrection`](#unithasincomingresurrectionunit) gives the
|
||||
new value.
|
||||
|
||||
The event fires one time for each token that names the unit, for example
|
||||
`"party1"` and `"target"`. If the cast is at the corpse of a released player,
|
||||
`arg1` names that player.
|
||||
|
||||
```lua
|
||||
local f = CreateFrame("Frame")
|
||||
f:RegisterEvent("INCOMING_RESURRECT_CHANGED")
|
||||
f:SetScript("OnEvent", function()
|
||||
print(arg1, UnitHasIncomingResurrection(arg1))
|
||||
end)
|
||||
```
|
||||
|
||||
### `INVENTORY_SEARCH_UPDATE` event
|
||||
|
||||
Fires with no payload when
|
||||
@@ -14968,7 +14989,7 @@ to.
|
||||
If SuperCleveRoidMacros is loaded, it controls macro display, and ClassicAPI
|
||||
does not evaluate `#showtooltip`. A build that hands its own results over
|
||||
through
|
||||
[`C_Macro.SetMacroDisplay`](#c_macrosetmacrodisplaymacroslot-value) keeps
|
||||
[`C_Macro.SetMacroDisplay`](#c_macrosetmacrodisplaymacroslot-value--unit) keeps
|
||||
`#showtooltip` working for the macros it does not claim.
|
||||
|
||||
A macro that names a condition
|
||||
@@ -15342,7 +15363,7 @@ mark, and the macro's own icon otherwise.
|
||||
selector edits. Use this function when you want the icon a macro shows, and
|
||||
`GetMacroInfo` when you want the one the player picked.
|
||||
|
||||
### `C_Macro.SetMacroDisplay(macroSlot, value)`
|
||||
### `C_Macro.SetMacroDisplay(macroSlot, value [, unit])`
|
||||
|
||||
Tells the client what a macro is about, so the action button shows it. For
|
||||
an addon that parses macros itself and wants the button to follow, instead
|
||||
@@ -15350,9 +15371,10 @@ of replacing `GetActionTexture`, `GetActionCooldown`, `IsUsableAction` and
|
||||
the rest in Lua.
|
||||
|
||||
```lua
|
||||
C_Macro.SetMacroDisplay(1, "Frostbolt") -- show this
|
||||
C_Macro.SetMacroDisplay(1, false) -- mine, nothing matched, show ?
|
||||
C_Macro.SetMacroDisplay(1, nil) -- released, `#showtooltip` resumes
|
||||
C_Macro.SetMacroDisplay(1, "Frostbolt") -- show this
|
||||
C_Macro.SetMacroDisplay(1, "Flash Heal", "mouseover") -- show this, range to the mouseover
|
||||
C_Macro.SetMacroDisplay(1, false) -- mine, nothing matched, show ?
|
||||
C_Macro.SetMacroDisplay(1, nil) -- released, `#showtooltip` resumes
|
||||
```
|
||||
|
||||
`macroSlot` is the macro index, the same one
|
||||
@@ -15366,6 +15388,31 @@ it, the same as a directive that names its own value.
|
||||
|
||||
Returns `true` when the value named a spell or an item.
|
||||
|
||||
**The range unit.** `unit` is optional. It names the unit that the button's
|
||||
range check uses in place of your target. Usually it is the `[@unit]` of the
|
||||
clause that your parser matched. It takes any unit token or GUID string that
|
||||
`UnitExists` takes, for example `"mouseover"`, `"focus"` or
|
||||
`"party2target"`. If you give no `unit`, or `nil`, the range check uses your
|
||||
target.
|
||||
|
||||
The client finds the unit again each time it checks the button's range. So
|
||||
if `"mouseover"` moves to a different unit, the range follows it with no new
|
||||
call. The same is true for `"focus"`, `"target"` and every other token. This
|
||||
is different from the `target` of
|
||||
[`SetMacroSpell`](#setmacrospellmacro-spell--target--setmacroitemmacro-item--target),
|
||||
which is found once, when you call it.
|
||||
|
||||
If the token points at no unit at that time, for example `"mouseover"` with
|
||||
nothing under the cursor, the range check uses your target. If the unit is
|
||||
too far away for the client to see it, for example a party member in another
|
||||
zone, the button has no range state.
|
||||
|
||||
The function ignores `unit` when `value` is `false`, `nil` or an empty
|
||||
string, because the macro then aims at nothing. A call with the same `value`
|
||||
and a different `unit` changes the unit. If `unit` is not a unit token, the
|
||||
function raises the error `Unknown unit name: <unit>`, the same error that
|
||||
`UnitExists` gives. Then it publishes nothing.
|
||||
|
||||
**What the button picks up.** The spell is written where the client keeps
|
||||
each macro's spell, which is the field its own buttons read. So cooldown,
|
||||
range, usable, out-of-mana greying, the current-cast highlight and
|
||||
@@ -15379,8 +15426,8 @@ button, on the cursor while you drag the macro, and in the macro window.
|
||||
The icon replaces the macro's own only when that is the question mark, which
|
||||
is the same rule `#showtooltip` follows. A macro with a chosen icon keeps it.
|
||||
|
||||
**Call it whenever your answer changes.** Nothing is re-evaluated for you.
|
||||
The value stands until you publish another, and it survives the client
|
||||
**Call it whenever your answer changes.** Nothing is re-evaluated for you,
|
||||
except the unit that a `unit` token points at. The value stands until you publish another, and it survives the client
|
||||
re-reading the macro, so an edit elsewhere cannot quietly replace it.
|
||||
|
||||
Publishing takes the macro over: `#showtooltip` is not parsed for it while
|
||||
@@ -20560,7 +20607,7 @@ is the number that `[stance:N]` and `[form:N]` test.
|
||||
|
||||
```lua
|
||||
local index = GetShapeshiftForm()
|
||||
if index > 0 then
|
||||
if index > 0 and index <= GetNumShapeshiftForms() then
|
||||
local icon, name = GetShapeshiftFormInfo(index)
|
||||
print("active:", name)
|
||||
end
|
||||
@@ -23142,6 +23189,32 @@ The player's target lands with the confirming packet, about one round-trip
|
||||
after the cast starts. So `UnitSpellTargetName("player")` can read `nil` for
|
||||
the first part of your own cast, then return the name.
|
||||
|
||||
### `UnitHasIncomingResurrection(unit)`
|
||||
|
||||
```
|
||||
hasIncomingResurrection = UnitHasIncomingResurrection(unit)
|
||||
```
|
||||
|
||||
Returns true while somebody casts a resurrection spell at `unit`. Otherwise
|
||||
it returns false. If `unit` is a released player, the function also includes
|
||||
casts at the corpse of that player.
|
||||
|
||||
```lua
|
||||
if UnitHasIncomingResurrection("party1") then
|
||||
print(UnitName("party1"), "has an incoming resurrection")
|
||||
end
|
||||
```
|
||||
|
||||
The value changes to true when the cast starts. It changes to false when the
|
||||
cast stops, for any reason. This includes a completed cast that the target did
|
||||
not accept yet.
|
||||
|
||||
You see a cast only when its caster is near you. If the caster goes out of
|
||||
range during the cast, the value changes to false.
|
||||
|
||||
[`INCOMING_RESURRECT_CHANGED`](#incoming_resurrect_changed-event) fires at
|
||||
each change.
|
||||
|
||||
### `CanBeRaidTarget(unit)`
|
||||
|
||||
Returns true if you can put a raid target icon on the unit. Otherwise it
|
||||
|
||||
@@ -3396,6 +3396,13 @@ enum Offsets {
|
||||
SMSG_SPELL_CHANNEL_UPDATE = 0x13A,
|
||||
SMSG_SPELL_DELAYED = 0x1E2,
|
||||
SMSG_SPELL_FAILED_OTHER = 0x2A6,
|
||||
// SpellCastTargets mask bits that carry a packed object GUID right after
|
||||
// the u16 mask in SMSG_SPELL_START / SMSG_SPELL_GO: unit 0x2, PvP corpse
|
||||
// 0x200, game object 0x800, corpse 0x8000 — one GUID for whichever is
|
||||
// set (the client's target reader 0x007E4ED0; tortoise-wow
|
||||
// SpellCastTargets::write). A resurrect aimed at a released player
|
||||
// carries the corpse.
|
||||
SPELL_TARGET_FLAGS_OBJECT = 0x8A02,
|
||||
// Melee swing result broadcast. Body (verified against the server's own
|
||||
// writer, tortoise-wow Unit::SendAttackStateUpdate): u32 hitInfo,
|
||||
// packedGuid attacker, packedGuid victim, u32 totalDamage, u8 subCount,
|
||||
|
||||
+8
-10
@@ -42,6 +42,7 @@
|
||||
#include "combatlog/RecentCasts.h"
|
||||
#include "net/PacketDispatch.h"
|
||||
#include "object/Resolve.h"
|
||||
#include "spell/Lookup.h"
|
||||
#include "unit/Identity.h"
|
||||
|
||||
#include <cstdint>
|
||||
@@ -54,13 +55,9 @@ using Net::CDataStore;
|
||||
using Net::Read;
|
||||
using Net::ReadPackedGuid;
|
||||
|
||||
constexpr uint32_t SMSG_SPELL_START = 0x131;
|
||||
constexpr uint32_t SMSG_SPELL_GO = 0x132;
|
||||
|
||||
// The cast flag that keeps a cast out of the combat log (both servers'
|
||||
// CAST_FLAG_HIDDEN_COMBATLOG); 4.3.4 tests the same bit.
|
||||
constexpr uint16_t CAST_FLAG_HIDDEN_COMBATLOG = 0x1;
|
||||
constexpr uint16_t TARGET_FLAG_OBJECT = 0x8A02;
|
||||
constexpr uint8_t SPELL_MISS_REFLECT = 11;
|
||||
|
||||
// 4.3.4's SpellHasOpenLockEffect tests 33 alone; 1.12's success line also
|
||||
@@ -85,7 +82,7 @@ int CastingTime(void *unit, int spellId) {
|
||||
|
||||
// The rules both cast builders share.
|
||||
bool Logged(const uint8_t *spell) {
|
||||
return !Hidden(spell) && !HasEffect(spell, SPELL_EFFECT_LEARN_SPELL) &&
|
||||
return !Hidden(spell) && !Spell::Lookup::HasEffect(spell, SPELL_EFFECT_LEARN_SPELL) &&
|
||||
(Game::Read<uint32_t>(spell, Offsets::OFF_SPELL_RECORD_ATTRIBUTES_EX4) &
|
||||
ATTR_EX4_NO_CAST_LOG) == 0;
|
||||
}
|
||||
@@ -127,7 +124,8 @@ void SpellGo(CDataStore *p) {
|
||||
Read<uint8_t>(p);
|
||||
}
|
||||
const uint16_t targetMask = Read<uint16_t>(p);
|
||||
const uint64_t target = (targetMask & TARGET_FLAG_OBJECT) ? ReadPackedGuid(p) : 0;
|
||||
const uint64_t target =
|
||||
(targetMask & Offsets::SPELL_TARGET_FLAGS_OBJECT) ? ReadPackedGuid(p) : 0;
|
||||
if (Overrun(p))
|
||||
return;
|
||||
RecentCasts::Record(caster, static_cast<uint32_t>(spellId), hitList, hits);
|
||||
@@ -135,17 +133,17 @@ void SpellGo(CDataStore *p) {
|
||||
return;
|
||||
void *unit = Object::ByGuid(Offsets::TYPEMASK_UNIT, caster);
|
||||
const uint8_t *spell = SpellRecord(spellId);
|
||||
if (unit == nullptr || !Logged(spell) || HasEffect(spell, SPELL_EFFECT_OPEN_LOCK) ||
|
||||
CastingTime(unit, spellId) != 0)
|
||||
if (unit == nullptr || !Logged(spell) ||
|
||||
Spell::Lookup::HasEffect(spell, SPELL_EFFECT_OPEN_LOCK) || CastingTime(unit, spellId) != 0)
|
||||
return;
|
||||
Entry e = Begin(caster, target, SPELL_CAST_SUCCESS, spell);
|
||||
Queue(e);
|
||||
}
|
||||
|
||||
void OnPacket(uint32_t opcode, CDataStore *p) {
|
||||
if (opcode == SMSG_SPELL_START)
|
||||
if (opcode == Offsets::SMSG_SPELL_START)
|
||||
SpellStart(p);
|
||||
else if (opcode == SMSG_SPELL_GO)
|
||||
else if (opcode == Offsets::SMSG_SPELL_GO)
|
||||
SpellGo(p);
|
||||
}
|
||||
|
||||
|
||||
@@ -609,16 +609,6 @@ bool Ranged(const uint8_t *spell) {
|
||||
|
||||
uint32_t SchoolMask(uint32_t school) { return school < kSchoolCount ? 1u << school : 0; }
|
||||
|
||||
bool HasEffect(const uint8_t *spell, int effect) {
|
||||
if (spell == nullptr)
|
||||
return false;
|
||||
for (int i = 0; i < Offsets::SPELL_RECORD_EFFECT_COUNT; ++i) {
|
||||
if (Game::Read<int>(spell, Offsets::OFF_SPELL_RECORD_EFFECT + i * 4) == effect)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HasAuraEffect(const uint8_t *spell, int auraName) {
|
||||
if (spell == nullptr)
|
||||
return false;
|
||||
|
||||
@@ -158,9 +158,6 @@ bool Ranged(const uint8_t *spell);
|
||||
// A school index (Spell.dbc School, the packets' school) as a school mask.
|
||||
uint32_t SchoolMask(uint32_t school);
|
||||
|
||||
// Whether one of the spell's three effects is `effect`.
|
||||
bool HasEffect(const uint8_t *spell, int effect);
|
||||
|
||||
// Whether one of the spell's three effects applies the aura `auraName`
|
||||
// (EffectApplyAuraName).
|
||||
bool HasAuraEffect(const uint8_t *spell, int auraName);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "combatlog/RecentCasts.h"
|
||||
|
||||
#include "combatlog/Entry.h"
|
||||
#include "spell/Lookup.h"
|
||||
#include "time/Clock.h"
|
||||
|
||||
#include <cstdint>
|
||||
@@ -80,7 +81,7 @@ const uint8_t *SpellByEffect(uint64_t caster, uint64_t target, int effectA, int
|
||||
if (c.caster != caster || !Hit(c, target))
|
||||
return false;
|
||||
const uint8_t *spell = SpellRecord(static_cast<int>(c.spellId));
|
||||
if (!HasEffect(spell, effectA) && !HasEffect(spell, effectB))
|
||||
if (!Spell::Lookup::HasEffect(spell, effectA) && !Spell::Lookup::HasEffect(spell, effectB))
|
||||
return false;
|
||||
found = spell;
|
||||
return true;
|
||||
|
||||
+20
-3
@@ -11,7 +11,7 @@
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// `C_Macro.SetMacroDisplay(macroSlot, value)` — the Lua door onto
|
||||
// `C_Macro.SetMacroDisplay(macroSlot, value [, unit])` — the Lua door onto
|
||||
// `Macro::ShowTooltip::Publish`, for an addon that has its own macro parser
|
||||
// and wants the display done properly rather than by replacing the action
|
||||
// globals. See the ShowTooltip header for what publishing buys.
|
||||
@@ -27,6 +27,12 @@
|
||||
// item-before-spell precedence, `item:N`, links, inventory slots and
|
||||
// `bag slot` for free.
|
||||
//
|
||||
// `unit` is the `[@unit]` the publisher's clause matched: the unit the
|
||||
// button's range check uses. Unlike `SetMacroSpell`'s target it stays a
|
||||
// token, resolved at every lookup (`PublishOptions::liveUnit`), so a
|
||||
// publisher whose clause still matches need not republish when the
|
||||
// mouseover, focus or target moves.
|
||||
//
|
||||
// `SetMacroSpell(macro, spell [, target])` and `SetMacroItem(macro, item
|
||||
// [, target])` are 4.3.4's narrower doors onto the same publish: a spellbook
|
||||
// spell only, or an item only, aimed at `target` (the unit the button's range
|
||||
@@ -37,6 +43,7 @@
|
||||
#include "Offsets.h"
|
||||
#include "macro/Arg.h"
|
||||
#include "macro/ShowTooltip.h"
|
||||
#include "unit/Identity.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
@@ -46,7 +53,7 @@ namespace {
|
||||
|
||||
int __fastcall Script_C_Macro_SetMacroDisplay(void *L) {
|
||||
if (!Game::Lua::IsNumber(L, 1)) {
|
||||
Game::Lua::Error(L, "Usage: C_Macro.SetMacroDisplay(macroSlot, value)");
|
||||
Game::Lua::Error(L, "Usage: C_Macro.SetMacroDisplay(macroSlot, value [, unit])");
|
||||
return 0;
|
||||
}
|
||||
const int macroSlot = static_cast<int>(Game::Lua::ToNumber(L, 1));
|
||||
@@ -63,7 +70,17 @@ int __fastcall Script_C_Macro_SetMacroDisplay(void *L) {
|
||||
}
|
||||
|
||||
const char *value = Game::Lua::IsString(L, 2) ? Game::Lua::ToString(L, 2) : nullptr;
|
||||
Game::Lua::PushBoolean(L, Macro::ShowTooltip::Publish(macroSlot, value) ? 1 : 0);
|
||||
Macro::ShowTooltip::PublishOptions options;
|
||||
// A value that names nothing aims at nothing, so its unit is not read.
|
||||
if (value != nullptr && value[0] != '\0' && Game::Lua::IsString(L, 3)) {
|
||||
options.unit = Game::Lua::ToString(L, 3);
|
||||
options.liveUnit = true;
|
||||
// The engine's resolver vets the token: anything that is not one
|
||||
// raises its "Unknown unit name" error, exactly as UnitExists does,
|
||||
// before anything is published.
|
||||
Unit::Identity::GuidForToken(options.unit);
|
||||
}
|
||||
Game::Lua::PushBoolean(L, Macro::ShowTooltip::Publish(macroSlot, value, options) ? 1 : 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -160,6 +160,9 @@ struct Entry {
|
||||
uint32_t isPet = 0;
|
||||
int itemID = 0;
|
||||
uint64_t unit = 0; // Info::unit
|
||||
// A published `PublishOptions::liveUnit` token, resolved afresh by every
|
||||
// lookup in place of `unit`. Empty for none.
|
||||
char unitToken[kOptionsMax] = {};
|
||||
uint32_t lastEvalMs = 0;
|
||||
};
|
||||
|
||||
@@ -1233,7 +1236,8 @@ bool LookupEntries(uint32_t macroID, Info *out) {
|
||||
out->spellID = e.spellID;
|
||||
out->isPet = e.isPet;
|
||||
out->itemID = e.itemID;
|
||||
out->unit = e.unit;
|
||||
out->unit = e.unitToken[0] != '\0' ? ::Unit::Identity::GuidForToken(e.unitToken)
|
||||
: e.unit;
|
||||
// A publisher re-publishes the moment its answer changes, so a claimed
|
||||
// macro never needs the polling refresh a condition of ours does.
|
||||
out->conditional = false;
|
||||
@@ -1327,7 +1331,13 @@ bool Publish(int macroSlot, const char *value, const PublishOptions &options) {
|
||||
ResolveValue(value, &r, /*explicitValue=*/true,
|
||||
/*itemOnly=*/options.accept == Accept::Item);
|
||||
}
|
||||
r.unit = UnitGuid(options.unit);
|
||||
// Set on every publish, so one that changes only the unit still lands:
|
||||
// `ApplyResolution` stores the unit before its unchanged-value early out.
|
||||
if (options.liveUnit && options.unit != nullptr)
|
||||
std::snprintf(e.unitToken, sizeof(e.unitToken), "%s", options.unit);
|
||||
else
|
||||
e.unitToken[0] = '\0';
|
||||
r.unit = options.liveUnit ? 0 : UnitGuid(options.unit);
|
||||
|
||||
BusyScope busy;
|
||||
ApplyResolution(L, e, r, matched);
|
||||
|
||||
+10
-2
@@ -72,8 +72,10 @@ struct Info {
|
||||
// from it has to keep re-reading while the cursor rests on the button.
|
||||
bool conditional;
|
||||
// The unit the macro's spell is aimed at, 4.3.4's MACRONODE::m_target: the
|
||||
// `[@unit]` of the clause that matched, or the target a `SetMacroSpell` /
|
||||
// `SetMacroItem` named. 0 means the current target, as for any action.
|
||||
// `[@unit]` of the clause that matched, the target a `SetMacroSpell` /
|
||||
// `SetMacroItem` named, or whoever the token a `SetMacroDisplay` named
|
||||
// points at as of this lookup. 0 means the current target, as for any
|
||||
// action.
|
||||
uint64_t unit;
|
||||
};
|
||||
|
||||
@@ -86,6 +88,12 @@ struct PublishOptions {
|
||||
Accept accept = Accept::Any;
|
||||
// A unit token or a name the spell is aimed at; null for the current target.
|
||||
const char *unit = nullptr;
|
||||
// Resolve `unit` at every lookup rather than once, here, so the macro
|
||||
// keeps following whoever the token names (a new mouseover, focus or
|
||||
// target) with no republish. `unit` must then be a token the engine's
|
||||
// resolver accepts: one that resolves once never raises later, since the
|
||||
// resolver tells a token's family from its spelling alone.
|
||||
bool liveUnit = false;
|
||||
// The claim ends when the macro's body is edited, as 4.3.4's SetMacroSpell
|
||||
// and SetMacroItem last only until the macro is parsed again. Otherwise it
|
||||
// lasts until `Release` or a reload.
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU General Public License as published by the Free Software Foundation, either
|
||||
// version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
// PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "object/Corpse.h"
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "object/Resolve.h"
|
||||
|
||||
namespace Object::Corpse {
|
||||
|
||||
namespace {
|
||||
|
||||
// `ClntObjMgrEnumVisibleObjects(cb, ctx)`; the callback's dummy EDX slot
|
||||
// puts the trailing GUID where the engine passes it. Return non-zero to keep
|
||||
// iterating.
|
||||
using EnumCallback_t = int(__fastcall *)(void *ctx, void *unusedEdx, uint64_t guid);
|
||||
using EnumVisibleObjects_t = int(__fastcall *)(EnumCallback_t cb, void *ctx);
|
||||
|
||||
const uint8_t *Fields(const void *corpse) {
|
||||
return Game::Read<const uint8_t *>(corpse, Offsets::OFF_UNIT_DESCRIPTOR);
|
||||
}
|
||||
|
||||
uint64_t Owner(const void *corpse) {
|
||||
const uint8_t *fields = Fields(corpse);
|
||||
return fields != nullptr ? Game::Read<uint64_t>(fields, Offsets::OFF_CORPSE_FIELD_OWNER) : 0;
|
||||
}
|
||||
|
||||
struct Scan {
|
||||
uint64_t owner;
|
||||
void *corpse;
|
||||
};
|
||||
|
||||
// Bones keep their owner (tortoise-wow Map.cpp, the corpse-to-bones
|
||||
// conversion), so every earlier death leaves one beside the live corpse;
|
||||
// 4.3.4 skips them.
|
||||
int __fastcall ScanCallback(Scan *scan, void * /*unusedEdx*/, uint64_t guid) {
|
||||
void *obj = Object::ByGuid(Offsets::TYPEMASK_CORPSE, guid, nullptr, 0);
|
||||
if (obj == nullptr || Owner(obj) != scan->owner ||
|
||||
(Game::Read<uint32_t>(Fields(obj), Offsets::OFF_CORPSE_FIELD_FLAGS) &
|
||||
Offsets::CORPSE_FLAG_BONES) != 0)
|
||||
return 1;
|
||||
scan->corpse = obj;
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void *ByOwner(uint64_t owner) {
|
||||
if (owner == 0 || !Object::RegistryReady())
|
||||
return nullptr;
|
||||
Scan scan{owner, nullptr};
|
||||
reinterpret_cast<EnumVisibleObjects_t>(Offsets::FUN_CLNT_OBJ_MGR_ENUM_VISIBLE_OBJECTS)(
|
||||
reinterpret_cast<EnumCallback_t>(&ScanCallback), &scan);
|
||||
return scan.corpse;
|
||||
}
|
||||
|
||||
uint64_t OwnerOf(uint64_t guid) {
|
||||
const void *obj = Object::ByGuid(Offsets::TYPEMASK_CORPSE, guid);
|
||||
return obj != nullptr ? Owner(obj) : 0;
|
||||
}
|
||||
|
||||
} // namespace Object::Corpse
|
||||
@@ -0,0 +1,30 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU General Public License as published by the Free Software Foundation, either
|
||||
// version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
// PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// Player corpses (`TYPEMASK_CORPSE`) and the player each belongs to, read
|
||||
// from the corpse's CORPSE_FIELD_OWNER.
|
||||
namespace Object::Corpse {
|
||||
|
||||
// The visible corpse `owner` left, not counting bones, or nullptr — 4.3.4's
|
||||
// ClntObjMgrFindCorpseByOwner, over the visible objects.
|
||||
void *ByOwner(uint64_t owner);
|
||||
|
||||
// The player who left the corpse `guid`, or 0 when `guid` is not a visible
|
||||
// corpse.
|
||||
uint64_t OwnerOf(uint64_t guid);
|
||||
|
||||
} // namespace Object::Corpse
|
||||
@@ -88,6 +88,7 @@
|
||||
#include "time/Clock.h"
|
||||
#include "unit/Focus.h"
|
||||
#include "unit/Identity.h"
|
||||
#include "unit/IncomingResurrection.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
@@ -691,6 +692,7 @@ void ParseChannelUpdate(Net::CDataStore *packet) {
|
||||
void HandleCastAborted(uint64_t guid, int spellID, bool castSlotOnly = false) {
|
||||
if (guid == 0 || spellID == 0)
|
||||
return;
|
||||
Unit::IncomingResurrection::OnCastEnded(guid, spellID);
|
||||
// Remote units: invalidate the cached cast/channel so
|
||||
// UnitCastingInfo/UnitChannelInfo stop reporting it this tick.
|
||||
for (auto &e : g_remoteCasts) {
|
||||
|
||||
@@ -52,6 +52,16 @@ bool IsFitToFamily(const uint8_t *spellRecord, uint32_t family,
|
||||
return (flags & flagMask) != 0;
|
||||
}
|
||||
|
||||
bool HasEffect(const uint8_t *spellRecord, int effect) {
|
||||
if (spellRecord == nullptr)
|
||||
return false;
|
||||
for (int i = 0; i < Offsets::SPELL_RECORD_EFFECT_COUNT; ++i) {
|
||||
if (Game::Read<int>(spellRecord, Offsets::OFF_SPELL_RECORD_EFFECT + i * 4) == effect)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int SpellbookSlotToID(int slot1Based, int bookType) {
|
||||
const int slot = slot1Based - 1;
|
||||
if (slot < 0 || slot >= Offsets::SPELLBOOK_MAX_SLOTS)
|
||||
|
||||
@@ -31,6 +31,10 @@ const uint8_t *RecordForID(int spellID);
|
||||
bool IsFitToFamily(const uint8_t *spellRecord, uint32_t family,
|
||||
uint64_t flagMask);
|
||||
|
||||
// Whether one of the spell's three effects is `effect` — 4.3.4's
|
||||
// SpellHasEffect. False for a null record.
|
||||
bool HasEffect(const uint8_t *spellRecord, int effect);
|
||||
|
||||
// Resolves a 1-based Lua-facing spellbook slot to the spellID stored
|
||||
// there. `bookType` is `0` for the player spellbook, `1` for the pet
|
||||
// spellbook (matches the engine's encoding from `Script_GetSpellName`).
|
||||
|
||||
+2
-25
@@ -15,6 +15,7 @@
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "object/Corpse.h"
|
||||
#include "object/Resolve.h"
|
||||
#include "spell/Lookup.h"
|
||||
#include "unit/Position.h"
|
||||
@@ -114,8 +115,6 @@ constexpr int32_t TARGET_PET = 5;
|
||||
|
||||
using Relation_t = bool(__thiscall *)(void *caster, void *other);
|
||||
using CreatureType_t = int(__thiscall *)(void *unit);
|
||||
using EnumCallback_t = int(__fastcall *)(void *ctx, void *unusedEdx, uint64_t guid);
|
||||
using EnumVisibleObjects_t = int(__fastcall *)(EnumCallback_t cb, void *ctx);
|
||||
|
||||
bool Relation(uintptr_t fn, void *caster, void *other) {
|
||||
return reinterpret_cast<Relation_t>(fn)(caster, other);
|
||||
@@ -181,28 +180,6 @@ uint32_t TargetWord(const uint8_t *rec, bool &aimsAtPet) {
|
||||
return word;
|
||||
}
|
||||
|
||||
struct CorpseScan {
|
||||
uint64_t owner;
|
||||
void *corpse;
|
||||
};
|
||||
|
||||
int __fastcall CorpseScanCallback(CorpseScan *scan, void * /*unusedEdx*/, uint64_t guid) {
|
||||
void *obj = Object::ByGuid(Offsets::TYPEMASK_CORPSE, guid, nullptr, 0);
|
||||
if (obj == nullptr ||
|
||||
Game::Read<uint64_t>(Fields(obj), Offsets::OFF_CORPSE_FIELD_OWNER) != scan->owner)
|
||||
return 1;
|
||||
scan->corpse = obj;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 4.3.4's ClntObjMgrFindCorpseByOwner, over the visible objects.
|
||||
void *CorpseOf(uint64_t owner) {
|
||||
CorpseScan scan{owner, nullptr};
|
||||
reinterpret_cast<EnumVisibleObjects_t>(Offsets::FUN_CLNT_OBJ_MGR_ENUM_VISIBLE_OBJECTS)(
|
||||
reinterpret_cast<EnumCallback_t>(&CorpseScanCallback), &scan);
|
||||
return scan.corpse;
|
||||
}
|
||||
|
||||
// A unit target (0x006E4511..0x006E469A).
|
||||
bool AdmitsUnit(void *caster, const uint8_t *rec, uint32_t word, void *unit) {
|
||||
const uint8_t *fields = Fields(unit);
|
||||
@@ -281,7 +258,7 @@ void *AdmittedTarget(void *caster, const uint8_t *rec, void *target) {
|
||||
if (info != nullptr &&
|
||||
(Game::Read<uint32_t>(info, Offsets::OFF_PLAYER_INFO_FLAGS) &
|
||||
Offsets::PLAYER_FLAG_GHOST) != 0) {
|
||||
if (void *corpse = CorpseOf(Game::Read<uint64_t>(Head(target), 0)))
|
||||
if (void *corpse = Object::Corpse::ByOwner(Game::Read<uint64_t>(Head(target), 0)))
|
||||
return corpse;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU General Public License as published by the Free Software Foundation, either
|
||||
// version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
// PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// `UnitHasIncomingResurrection(unit)` and `INCOMING_RESURRECT_CHANGED` —
|
||||
// 4.3.4's CGIncomingResurrection (Ui/IncomingResurrection.cpp) and the two
|
||||
// calls its spell code makes into it:
|
||||
// - SMSG_SPELL_START: a spell with a resurrect effect and an object target
|
||||
// adds {caster, spell} under that target
|
||||
// (PendingSpellCast::OnSpellStartPacket, 0x008ACD80);
|
||||
// - the cast's end removes it (FinishPendingSpellCast, 0x008AF840, which
|
||||
// SPELL_GO and every failure run).
|
||||
// Each change signals INCOMING_RESURRECT_CHANGED to every token naming the
|
||||
// target, or the corpse's owner when the target is a corpse
|
||||
// (Script_SendUnitSignal). The query answers for the unit itself, then for
|
||||
// its corpse (HasIncomingResurrection, 0x009FCF80).
|
||||
//
|
||||
// Where 1.12 differs:
|
||||
// - Resurrect effects. 4.3.4 tests effect 18 alone. 1.12's class
|
||||
// resurrections (Resurrection, Redemption, Ancestral Spirit, Rebirth,
|
||||
// every rank) carry 113, RESURRECT_NEW, and 18 only item and NPC spells
|
||||
// (Spell.dbc), so both count.
|
||||
// - The cast's end. 1.12 keeps no pending cast. Its end is the caster's
|
||||
// SMSG_SPELL_GO, or CGUnit_C::ClearCastingSpell and the failure packets,
|
||||
// which `Spell::Cast` owns and reports through OnCastEnded. Turtle sends
|
||||
// observers no failure packet on an interrupt; SuperWoW calls
|
||||
// ClearCastingSpell instead (docs/notes/cast-channel.md).
|
||||
// - Lifetime. 4.3.4 empties the table on entering and leaving the world. A
|
||||
// cast whose caster leaves view never reports its end, so an entry whose
|
||||
// caster is no longer visible ends on the next world tick, which covers
|
||||
// a world change as well.
|
||||
// - One cast per caster, as the unit's single precast slot (+0xC8C) holds:
|
||||
// a caster's new resurrection replaces its last.
|
||||
|
||||
#include "unit/IncomingResurrection.h"
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "event/Custom.h"
|
||||
#include "net/PacketDispatch.h"
|
||||
#include "object/Corpse.h"
|
||||
#include "object/Resolve.h"
|
||||
#include "spell/Lookup.h"
|
||||
#include "tick/WorldTick.h"
|
||||
#include "unit/Identity.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace Unit::IncomingResurrection {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int SPELL_EFFECT_RESURRECT = 18;
|
||||
constexpr int SPELL_EFFECT_RESURRECT_NEW = 113;
|
||||
|
||||
const Game::Doc::Field kChangedPayload[] = {
|
||||
Game::Doc::Req("unitTarget", "UnitToken", "The unit, or the player whose corpse it is."),
|
||||
};
|
||||
const Game::Doc::Event kChangedDoc{
|
||||
"Unit", "A resurrection cast at the unit began or ended. Read UnitHasIncomingResurrection.",
|
||||
kChangedPayload};
|
||||
const Event::Custom::AutoReserve kChanged{"INCOMING_RESURRECT_CHANGED", &kChangedDoc};
|
||||
|
||||
struct Entry {
|
||||
uint64_t target; // the cast's object target: a unit or a corpse
|
||||
uint64_t unit; // who hears the change: the corpse's owner, else target
|
||||
uint64_t caster;
|
||||
int spellID;
|
||||
};
|
||||
|
||||
std::vector<Entry> g_entries;
|
||||
|
||||
void Signal(uint64_t unit) {
|
||||
const int slot = kChanged.Slot();
|
||||
if (!Event::Custom::HasListeners(slot))
|
||||
return;
|
||||
char tokens[16][32];
|
||||
const int n = Unit::Identity::TokensForGUID(unit, tokens, 16);
|
||||
for (int i = 0; i < n; ++i)
|
||||
Event::Custom::Fire(slot, "%s", tokens[i]);
|
||||
}
|
||||
|
||||
// Ends `caster`'s resurrection: of `spellID`, or any for 0.
|
||||
void End(uint64_t caster, int spellID) {
|
||||
for (auto it = g_entries.begin(); it != g_entries.end(); ++it) {
|
||||
if (it->caster == caster && (spellID == 0 || it->spellID == spellID)) {
|
||||
const uint64_t unit = it->unit;
|
||||
g_entries.erase(it);
|
||||
Signal(unit);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Begin(uint64_t target, uint64_t caster, int spellID) {
|
||||
for (const Entry &e : g_entries) {
|
||||
if (e.caster == caster && e.target == target && e.spellID == spellID)
|
||||
return;
|
||||
}
|
||||
End(caster, 0);
|
||||
const uint64_t owner = Object::Corpse::OwnerOf(target);
|
||||
const uint64_t unit = owner != 0 ? owner : target;
|
||||
g_entries.push_back({target, unit, caster, spellID});
|
||||
Signal(unit);
|
||||
}
|
||||
|
||||
bool Targeted(uint64_t target) {
|
||||
for (const Entry &e : g_entries) {
|
||||
if (e.target == target)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HasIncomingResurrection(uint64_t guid) {
|
||||
if (guid == 0 || g_entries.empty())
|
||||
return false;
|
||||
if (Targeted(guid))
|
||||
return true;
|
||||
const void *corpse = Object::Corpse::ByOwner(guid);
|
||||
return corpse != nullptr && Targeted(Unit::Identity::GuidForObject(corpse));
|
||||
}
|
||||
|
||||
bool IsResurrect(int spellID) {
|
||||
const uint8_t *rec = Spell::Lookup::RecordForID(spellID);
|
||||
return Spell::Lookup::HasEffect(rec, SPELL_EFFECT_RESURRECT) ||
|
||||
Spell::Lookup::HasEffect(rec, SPELL_EFFECT_RESURRECT_NEW);
|
||||
}
|
||||
|
||||
// SMSG_SPELL_START / SMSG_SPELL_GO: packed item or caster, packed caster,
|
||||
// spell, then for SPELL_START u16 flags, u32 cast time and the targets.
|
||||
void OnPacket(uint32_t opcode, Net::CDataStore *p) {
|
||||
if (opcode != Offsets::SMSG_SPELL_START && opcode != Offsets::SMSG_SPELL_GO)
|
||||
return;
|
||||
if (opcode == Offsets::SMSG_SPELL_GO && g_entries.empty())
|
||||
return;
|
||||
Net::ReadPackedGuid(p);
|
||||
const uint64_t caster = Net::ReadPackedGuid(p);
|
||||
const int spellID = static_cast<int>(Net::Read<uint32_t>(p));
|
||||
if (opcode == Offsets::SMSG_SPELL_GO) {
|
||||
if (p->m_read <= p->m_size)
|
||||
End(caster, spellID);
|
||||
return;
|
||||
}
|
||||
Net::Read<uint16_t>(p);
|
||||
Net::Read<uint32_t>(p);
|
||||
const uint16_t targetMask = Net::Read<uint16_t>(p);
|
||||
const uint64_t target =
|
||||
(targetMask & Offsets::SPELL_TARGET_FLAGS_OBJECT) ? Net::ReadPackedGuid(p) : 0;
|
||||
if (p->m_read > p->m_size || caster == 0 || target == 0 || !IsResurrect(spellID))
|
||||
return;
|
||||
Begin(target, caster, spellID);
|
||||
}
|
||||
|
||||
const Net::PacketDispatch::AutoSubscribe _sub{&OnPacket};
|
||||
|
||||
void OnWorldTick() {
|
||||
for (size_t i = 0; i < g_entries.size();) {
|
||||
if (Object::ByGuid(Offsets::TYPEMASK_UNIT, g_entries[i].caster) != nullptr) {
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
const uint64_t unit = g_entries[i].unit;
|
||||
g_entries.erase(g_entries.begin() + static_cast<std::ptrdiff_t>(i));
|
||||
Signal(unit);
|
||||
}
|
||||
}
|
||||
|
||||
const Tick::WorldTick::AutoSubscribe _tick{&OnWorldTick};
|
||||
|
||||
int __fastcall Script_UnitHasIncomingResurrection(void *L) {
|
||||
if (!Game::Lua::IsString(L, 1)) {
|
||||
Game::Lua::Error(L, "Usage: UnitHasIncomingResurrection(\"unit\")");
|
||||
return 0;
|
||||
}
|
||||
Game::Lua::PushBool(L, HasIncomingResurrection(
|
||||
Unit::Identity::GuidForToken(Game::Lua::ToString(L, 1))));
|
||||
return 1;
|
||||
}
|
||||
|
||||
const Game::Doc::Field kArgs[] = {
|
||||
Game::Doc::Req("unit", "UnitToken"),
|
||||
};
|
||||
const Game::Doc::Field kRets[] = {
|
||||
Game::Doc::Req("hasIncomingResurrection", "bool"),
|
||||
};
|
||||
const Game::Doc::Function kUnitHasIncomingResurrection{
|
||||
"Whether a resurrection is being cast at the unit, or at the corpse of a released player.",
|
||||
kArgs, kRets, "UnitGlobals"};
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterGlobalFunction("UnitHasIncomingResurrection",
|
||||
&Script_UnitHasIncomingResurrection,
|
||||
&kUnitHasIncomingResurrection);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
} // namespace
|
||||
|
||||
void OnCastEnded(uint64_t caster, int spellID) {
|
||||
if (caster != 0 && spellID != 0 && !g_entries.empty())
|
||||
End(caster, spellID);
|
||||
}
|
||||
|
||||
} // namespace Unit::IncomingResurrection
|
||||
@@ -0,0 +1,28 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU General Public License as published by the Free Software Foundation, either
|
||||
// version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
// PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// 4.3.4's CGIncomingResurrection: the resurrections being cast at each unit
|
||||
// or corpse. Backs `UnitHasIncomingResurrection` and
|
||||
// `INCOMING_RESURRECT_CHANGED`.
|
||||
namespace Unit::IncomingResurrection {
|
||||
|
||||
// `caster` stopped casting `spellID`: CGUnit_C::ClearCastingSpell or a
|
||||
// failure packet, both owned by `Spell::Cast`. Ends the resurrection it was
|
||||
// casting, if that is one.
|
||||
void OnCastEnded(uint64_t caster, int spellID);
|
||||
|
||||
} // namespace Unit::IncomingResurrection
|
||||
Reference in New Issue
Block a user