An AzerothCore module scaffold. Drop your support-related behaviour into
src/ and wire it through Addmod_enhanced_supportScripts().
- Place this folder under
modules/in your AzerothCore source tree. - Re-run CMake and rebuild. The module is auto-discovered (no manual CMake edits required for a static build).
- Copy
conf/mod-enhanced-support.conf.distto your config directory asmod-enhanced-support.confand adjust as needed. If the file is absent the built-in defaults are used.
Blocks player-to-player mail whose subject or body contains any blocked
keyword (case-insensitive substring match). Intended to stop gold-seller and
scam advertising. Blocked mail (including its subject and body) is logged under
module.enhancedsupport.
Keywords live in the auth DB table enhanced_support_mail_keywords and are
managed with the .support keyword commands below; no rebuild or config edit is
needed to change them. Because the table is in the auth DB, the keyword list is
shared across all realms.
The Action option controls how matches are handled:
| Action | Behaviour |
|---|---|
0 |
Disabled (filter off) |
1 |
Block mail + notify the sender |
2 |
Block mail + kick the sender |
3 |
Block mail + permanently ban the sender's account |
4 |
Block mail + permanently ban the account and its IP |
Enable EnhancedSupport.MailFilter.SkipSameAccount to bypass the mail checks for
mail between two characters on the same account (sending to your own alt). It
skips both the keyword filter and the high-value gold logging, since a
self-transfer is never advertising or gold selling.
Scans player SAY, YELL, EMOTE, PARTY and WHISPER messages against the
same keyword list as the mail filter (case-insensitive substring match) and
applies the same escalating actions, configured separately via
EnhancedSupport.ChatFilter.Action. Blocked messages are logged under
module.enhancedsupport.
The SAY/YELL/EMOTE hook the core exposes runs before the message is
broadcast but cannot abort it, so a matched message has its text cleared rather
than being dropped outright. Party and whisper chat use separate hooks that do
abort the broadcast, so a matched message there is dropped entirely. With the
kick/ban actions the sender is removed regardless.
| Action | Behaviour |
|---|---|
0 |
Disabled (filter off) |
1 |
Block message + notify the sender |
2 |
Block message + kick the sender |
3 |
Block message + permanently ban the sender's account |
4 |
Block message + permanently ban the account and its IP |
Both the mail and chat filters match keywords as contiguous substrings, so
spammers evade them by spacing letters out (B U Y G O L D . C O M). Collapsing
whitespace before matching would catch that, but it also fuses perfectly normal
phrases: a group-finding message typed with spaces collapses into a single solid
token that can contain a blocked keyword as a substring, false-positiving
legitimate chat.
The aggressive pass (EnhancedSupport.AggressiveMaxLevel) runs the
whitespace-collapsed match for both mail and chat, additionally folding common
look-alike character substitutions back to letters so digit-disguised text reads
as its plain form. It runs only when both of these hold:
- the sender is at or below the configured level (gold bots are throwaway
low-level characters; real raiders advertising
LFG tank for Onyxiaare not), and - the text also contains a web marker (
.com,www,http,dot com, ...).
LFG tank for Onyxia from a level 70 fails the level gate; LFG tank for RFC
from a new player fails the web-marker gate; the spam line passes both. Matches
reuse each filter's own action (MailFilter.Action / ChatFilter.Action). Set
AggressiveMaxLevel to 0 to disable it.
Both passes above look at a single message, so spammers split an advertisement across several lines, padding it with unrelated chatter so that no single line is actionable on its own. No one line carries a complete URL, so neither the strict nor the aggressive pass fires.
The windowed pass keeps a short, time-bounded history of each sender's recent chat lines and re-runs the strict and aggressive matches over the joined text, reassembling the fragments so the match fires.
It is controlled by EnhancedSupport.ChatFilter.WindowSize (max lines kept per
sender) and EnhancedSupport.ChatFilter.WindowSeconds (how long a line stays in
the window). It only runs when WindowSize is at least 2, WindowSeconds is
set, and the sender is at or below EnhancedSupport.AggressiveMaxLevel, so it
inherits the same low-level + web-marker safeguards. Because the core hook for
SAY/YELL/EMOTE cannot recall lines already broadcast, only the final
fragment is blanked; the earlier lines have gone out, but the URL never completes
and the full joined text is logged and the configured action applied. The history
is pruned by age and size on each message and cleared on logout.
Spammers also create throwaway low-level characters and mass-invite strangers to a
group. A party invite carries no text to match, so this filter acts on the rate
instead: it blocks a sender's invite once more than RateCount invites from them
fall within the last RateSeconds. The core drops a blocked invite silently, so
the spammer sees no error and the target gets no popup. The same escalating actions
are available through a dedicated EnhancedSupport.InviteFilter.Action (separate
from the chat and mail filters).
To keep legitimate grouping clear, invites to a guildmate, to a player who has the
inviter on their friends list, or to a player on the same IP (same household or
multiboxer) are never counted. InviteFilter.MaxLevel
restricts the filter to low-level inviters (0 watches every level). The filter
runs only when InviteFilter.Action, RateCount and RateSeconds are all set.
Per-sender invite history is pruned on each invite and cleared on logout.
Logs when a character loots an item whose required level exceeds their own by at
least EnhancedSupport.LootFilter.LevelGap levels, for example a level-5
character pulling high-level gear from a world chest in Burning Steppes. Intended
to surface boosting or loot exploits. This is log-only: the loot is never
blocked. Items with no level requirement are ignored, as is anything pulled from
a fishing bobber or fishing hole (fish are caught above your level by design).
Entries are written under module.enhancedsupport (and relayed to Discord when
mod-chat-transmitter is available), and identify the looter, the item, the level
gap, the loot source (its type, template entry and DB spawn id), the looter's
zone/subarea and whether they were in a group at the time. In the Discord notice
the item and the source creature/object are linked to their aowow database pages.
EnhancedSupport.LootFilter.MaxLevel optionally caps the check to looters at or
below a given level (e.g. 10 to watch only characters up to level 10); 0
applies it to every level.
When a looter pulls several flagged items from one container or creature, each is
its own Discord notification by default. Set EnhancedSupport.LootFilter.BatchSeconds
to coalesce them into a single message, sent once no further item has arrived from
that source for that many seconds (or when the looter switches source or logs out).
This affects the Discord notification only; the server log keeps one line per item.
Logs when a newly created character belongs to an account whose email contains
any configured substring pattern (case-insensitive). Intended to surface
bot/gold-seller accounts that register with recognizable email patterns. This is
log-only: character creation is never blocked. Matches are written under
module.enhancedsupport and, when mod-chat-transmitter is available, relayed to
Discord (sharing the chat filter's channel), identifying the new character, its
account and the matched pattern.
Patterns live in the auth DB table enhanced_support_email_patterns, managed with
the .support emailpattern commands below and shared across all realms, like the
mail keywords. EnhancedSupport.EmailFilter.Enable is a master switch for the check
(default on); when enabled it runs only once at least one pattern is configured.
Records raw combat events from live arena matches into the characters DB table
enhanced_support_arena_events, for offline detection of input automation
(AHK-style interrupt/dispel bots, facing scripts). A single fast reaction proves
nothing - WotLK spell queueing and latency compensation make individual events
ambiguous - so nothing is judged in real time. Instead the raw events feed
offline statistical analysis across many matches, where automation shows up as:
- Interrupt reaction times (interrupt cast minus enemy cast-bar start, minus latency) with an inhumanly low floor and near-zero variance.
- Fake-cast responses: a reflex bot fires on every cast start, so it "answers" casts that were cancelled moments in, at the same fixed delay, match after match. Humans get juked.
- Dispel reactions keyed off dispellable-aura applications, including frame-perfect dispels of effects on targets the player wasn't focusing.
- CC-break reactions: loss-of-control effects (stuns, fears, roots, ...) landing on a player, answered by a removal or immunity cast (PvP trinket, Berserker Rage, Ice Block, ...). Classified by the spell's mechanic-removal/ immunity data, not by spell lists.
- Trinket-answer CC: an enemy's trinket/immunity cast answered with fresh CC on that enemy. Cast-time CC counts from its cast start, so a poly pre-cast in anticipation never matches; instant CC counts from its cast.
- DoT reapply timing: a periodic-damage aura expiring (or being dispelled) on a target, answered by the same caster reapplying that DoT. Informational only - expiry is predictable with timer addons, so near-zero reapplies are normal; the bot tell is a tight cluster, not a low floor.
- Cast cadence: successful casts per minute plus the spacing between consecutive casts (median and interquartile range, gaps over 10s excluded). Server-visible casts only, so figures are far below addon-style APM; compare players against each other, and look for near-zero gap spread (scripted chains have inhumanly regular timing).
- Failed casts from sniffing outbound
SMSG_CAST_FAILED: only verdicts the client cannot predict reach the server (nothing to dispel, line of sight, facing/range on macro casts), so a dispel bot firing blind stands out. Attempts the client rejects locally (range on a button press, cooldown) never produce a packet and stay invisible. - Facing behaviour from the periodic position/orientation samples plus the target's position/orientation captured on every cast row (for behind-arc abilities like Shred): facing scripts snap onto their attacker within one movement packet, so the victim's frontal arc never lapses while being circled.
Event rows carry the match (battleground instance id, map, arena type, rated
flag), a millisecond timestamp, the acting player (GUID and match side), the
persistent rated arena team id (arena_team_id, 0 for unrated matches), spell
id, target GUID, the actor's current latency (to normalize reaction times per
player), the actor's position/orientation and the target unit's
position/orientation at that instant (tgt_*, zero when there is no unit
target). actor_team is only the side within a single match (0/1) and resets
each match; use arena_team_id to follow a specific rated team across matches.
Event types:
| Type | Event | extra |
|---|---|---|
1 |
Cast start | cast time in ms (cast bar appeared) |
2 |
Cast cancel | 1 cancelled by the caster (fake cast), 0 interrupted/failed |
3 |
Cast fired | - |
4 |
Aura applied | dispel type (0 for non-dispellable CC such as stuns); dispellable and loss-of-control auras on players are recorded; actor is the aura target, target_guid the caster |
5 |
Position sample | - (target_guid is the player's current selection) |
6 |
Aura removed | AuraRemoveMode (3 dispelled, 4 expired, ...); same aura kinds as type 4, actor is the aura target, target_guid the caster |
7 |
Cast failed | SpellCastResult the server rejected the cast with (e.g. 86 nothing to dispel) |
GMs and spectators are never recorded, and the arena preparation phase (before
the gates open) is skipped. By default only rated matches are logged
(ArenaTelemetry.RatedOnly) and events are purged after
ArenaTelemetry.RetentionDays days on server startup. Volume is modest: a
3-minute 3v3 match produces a few thousand rows, dominated by the position
samples (ArenaTelemetry.PositionSampleMs, default 500ms, 0 to disable
sampling while keeping the cast/aura events).
Events are buffered in memory per match (a few hundred KB) and written in a single transaction of multi-row inserts when the match ends, on arena teardown, or on clean server shutdown - a running match causes no DB traffic. A server crash loses the in-flight matches' telemetry only.
Two tools run the reaction analysis on a recorded match. Responses are
classified by spell effect (interrupt / dispel), so no spell list needs
maintaining; each reaction is reduced by the player's latency at that moment. A
reaction counts as fast at or below ArenaTelemetry.Suspect.ReactionMs
(default 180ms - human reactions to visual cues sit around 200-500ms even at
high rating), and a player is flagged with at least
ArenaTelemetry.Suspect.MinEvents fast reactions making up at least
ArenaTelemetry.Suspect.Percent of their reactions in the match.
.support arena matches [count]lists recently recorded matches;.support arena team <arenaTeamId> [count]lists the recorded matches a given rated arena team played (unrated matches have no team id);.support arena check <matchId>prints per-player statistics for one match (interrupt, dispel, CC-break and trinket-answer reaction counts/min/median, DoT reapply times, cast cadence, failed casts, fake casts thrown and bitten, average latency) and marks flagged players. Only the four reaction categories feed the flag; DoT reapplies, cadence and failed casts are informational. Running matches are analyzed from the live buffer.- With
ArenaTelemetry.AutoCheckenabled, every recorded match is analyzed when it ends - on its in-memory events, before the DB write - and flagged players are logged undermodule.enhancedsupportand relayed to Discord via the ChatFilter alias (requires mod-chat-transmitter), like the chat filter notices.
Both are report-only: nobody is punished automatically. One match is never proof (spell queueing and lucky reactions exist); treat a flag as a pointer to compare that player's other matches, ideally with the offline analysis over a longer period.
This is log-only evidence gathering: no gameplay is altered and no automated punishment is issued. Client-side scanning cannot see AHK (it only sends keystrokes), which is why behavioural telemetry is the detection layer here.
When enabled, posts a decorated Discord message once the world server has
started, showing the git revision (hash, branch and build date) the server is
running on. This uses mod-chat-transmitter as the transport, so that module
must be installed and enabled; without it the notice
is skipped and a warning is logged. The notice is scheduled (via
TaskScheduler) a few seconds after startup so the relay's WebSocket has come
up first; the delay is configurable.
All commands live under .support and work in-game and from the server console.
| Command | Security | Description |
|---|---|---|
.support info |
Game Master | Shows active settings: enabled state, mail and chat filter actions, keyword and email-pattern counts, ban author, message. |
.support action <0-4> |
Administrator | Overrides the mail filter action at runtime (not saved; reverts on .support reload or restart). |
.support reload |
Administrator | Reloads this module's config and keywords, independent of the global .reload config. |
.support keyword add <keyword> |
Administrator | Adds a blocked keyword (stored lowercased). |
.support keyword remove <keyword> |
Administrator | Removes a blocked keyword. |
.support list keywords |
Game Master | Lists the blocked keywords. |
.support emailpattern add <pattern> |
Administrator | Adds an email substring pattern (stored lowercased). |
.support emailpattern remove <pattern> |
Administrator | Removes an email pattern. |
.support list emailpatterns |
Game Master | Lists the configured email patterns. |
.support list bans [count] [author] |
Game Master | Lists the most recent account bans (newest first). count defaults to 10, max 50; author filters by the ban author substring and defaults to EnhancedSupport.MailFilter.BanAuthor, so a bare call shows the module's own bans. |
.support arena matches [count] |
Game Master | Lists the most recently recorded arena matches (id, size, start, duration, event count). count defaults to 10, max 50. |
.support arena team <arenaTeamId> [count] |
Game Master | Lists the recorded matches a given rated arena team took part in, newest first. count defaults to 10, max 50. Only rated matches carry a team id. |
.support arena check <matchId> |
Game Master | Analyzes one recorded match and prints per-player reaction statistics, marking players who cross the suspect thresholds. |
Examples: .support keyword add wowgold, .support list keywords,
.support list bans (module bans), .support list bans 50 GM_Name.
| Option | Default | Description |
|---|---|---|
EnhancedSupport.Enable |
1 |
Master switch for the module's behaviour |
EnhancedSupport.MailFilter.Action |
0 |
Match handling (0-4, see table above); also gated by EnhancedSupport.Enable |
EnhancedSupport.MailFilter.Message |
(see conf) | System message shown to the sender when Action = 1; empty to send none |
EnhancedSupport.MailFilter.BanAuthor |
SupportModule |
Author recorded on Action 3/4 bans; also the default .support list bans search term |
EnhancedSupport.LootFilter.LevelGap |
0 |
Log loot whose required level exceeds the looter's level by at least this gap; 0 disables |
EnhancedSupport.LootFilter.MaxLevel |
0 |
Cap the loot check to looters at or below this level; 0 applies to all levels |
EnhancedSupport.EmailFilter.Enable |
1 |
Master switch for the account email-pattern check at character creation; runs once at least one pattern is configured |
EnhancedSupport.ArenaTelemetry.Enable |
0 |
Record arena combat telemetry (casts, cancels, failed casts, aura applies/removes, position samples) to enhanced_support_arena_events for offline cheat detection |
EnhancedSupport.ArenaTelemetry.RatedOnly |
1 |
Record rated matches only; 0 also records skirmishes |
EnhancedSupport.ArenaTelemetry.PositionSampleMs |
500 |
Interval between position/orientation samples (min 100); 0 disables sampling, cast/aura events remain |
EnhancedSupport.ArenaTelemetry.RetentionDays |
30 |
Purge events older than this on startup; 0 keeps everything |
EnhancedSupport.ArenaTelemetry.AutoCheck |
0 |
Analyze each match at its end and report flagged players to the log and Discord (ChatFilter alias) |
EnhancedSupport.ArenaTelemetry.Suspect.ReactionMs |
180 |
Latency-adjusted reaction at/below this is "fast" |
EnhancedSupport.ArenaTelemetry.Suspect.MinEvents |
4 |
Minimum fast reactions in a match to flag a player |
EnhancedSupport.ArenaTelemetry.Suspect.Percent |
60 |
Minimum share of the player's reactions that must be fast to flag |
EnhancedSupport.StartupNotice.Enable |
0 |
Post a Discord notice with the git revision on server start (needs mod-chat-transmitter) |
EnhancedSupport.StartupNotice.Message |
Server restarted! |
Headline for the startup notice; the full version line is shown below it in a code block |
EnhancedSupport.StartupNotice.DelaySeconds |
5 |
Seconds to wait after startup before sending, so the relay's WebSocket is up |
src/mod_enhanced_support_loader.cpp— entry point invoked by the core; calls the module'sAdd*Scriptsregistrars.src/mod_enhanced_support.cpp— world/player scripts (config cache, mail filter).src/cs_enhanced_support.cpp— the.supportchat commands.src/arena_telemetry.cpp— the arena telemetry recorder (cheat detection).src/EnhancedSupport.h— shared config/keyword API used by the.cppfiles.conf/mod-enhanced-support.conf.dist— distributed config template.data/sql/db-auth,data/sql/db-world,data/sql/db-characters—base/updatesSQL applied automatically by the module DB updater. The mail keyword and email pattern tables live indb-auth/updates; the.supportcommand help rows indb-world/updates; the arena telemetry event table indb-characters/updates.
Released under the GNU AGPL v3, consistent with AzerothCore.