mirror of
https://gitcode.com/GitHub_Trending/az/azerothcore-wotlk.git
synced 2026-10-10 07:06:38 +08:00
fix(Scripts/Dalaran): Eject only from the quarters (#27666)
This commit is contained in:
@@ -165,6 +165,7 @@ go test -tags=e2e ./local/... -count=1 -v -timeout 30m -parallel 1
|
||||
| instances/bind_reset | party tele; ritual summon | P2 | covered; post-reset summon `blocked-harness` (AcceptSummon after reset) | #10708 |
|
||||
| instances/classic/stratholme | Timmy remains hidden while a relevant Square Scarlet lives, then emerges after the area is clear | P2 | covered (`TestAC_26363_TimmyEmergesAfterSquareCleared`) | #26363 |
|
||||
| instances/ulduar | named tele; Freya wave interval; a Laughing Skull's Lunatic Gaze stops at the brain room's geometry instead of draining sanity through it; Elder Brightleaf's Unstable Sun Beams despawn on their own timer instead of outliving him, one lands under the player rather than all on the elder, and a wave stays capped however many players stand in range; Psychosis and Malady of the Mind stop picking a player once they are at 40 Sanity or less; Algalon holds still for 3s once his Big Bang cast lands instead of resuming melee and Quantum Strike on the next tick; a Writhing Lasher stays up past 62947's own 10s summon duration, because the despawn type its AI sets in `IsSummonedBy` is no longer overwritten on the way out of the summon call; a wipe to Thorim's melee with a death-persistent damage shield on the last victim spawns no Cache of Storms while his hard-reset despawn runs, and a lethal hit on the respawned Thorim still yields | P2 | covered (`TestAC_27095_*`, `TestAC_27602_*`, `TestAC_27590_*`, `TestAC_27539_*`, `TestUlduar_BrightleafSunBeams*`, `TestUlduar_FreyaWardLasherOutlivesSummonDuration`, `TestUlduar_ThorimEvadeDespawnDoesNotYield`); Kologarn Charge `blocked-harness` (bridge Z after Charge) | #26266 #27095 #27539 #27590 #27602 |
|
||||
| world/dalaran | Dalaran's faction guards eject a hostile player from the Silver Enclave and Sunreaver's Sanctuary, and from nowhere else — public mailboxes, the street, the sewers and the neutral inn are left alone. Both directions are asserted: only checking public ground would pass a build where the guards never fire. One case parks a pet inside a quarter with its owner outside, since every other case is a lone player and would not notice the guard reading the owner's position instead of the trespasser's | P2 | covered (`TestAC_4467_*`) | #4467 |
|
||||
| world/gameevents | Call to Arms banners at the Dalaran portals belong to the side they stand on, and the already-correct Warsong set is unchanged. **Wants an exclusive realm**: starting a holiday re-anchors its schedule in the running worldserver until restart; holidays already running are left alone | P2 | covered (`TestAC_24380_*`); Shattrath's 23 positions `gap` | #24380 |
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
//go:build e2e
|
||||
|
||||
package dalaran_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
|
||||
"github.com/azerothcore/AzerothGhost/client"
|
||||
"github.com/azerothcore/AzerothGhost/e2e/e2eharness"
|
||||
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
|
||||
)
|
||||
|
||||
const (
|
||||
dalaranMap uint32 = 571
|
||||
spellSummonImp = 688
|
||||
settleWindow = 9 * time.Second
|
||||
)
|
||||
|
||||
// Issue: https://github.com/azerothcore/azerothcore-wotlk/issues/4467
|
||||
//
|
||||
// Dalaran's faction guards used to decide "is this player trespassing" from how
|
||||
// close a fruit vendor was and which way the guard happened to face, which ejected
|
||||
// players standing on public ground — a mailbox, the street, the sewers — while
|
||||
// leaving most of each sanctum unwatched.
|
||||
//
|
||||
// They now eject on where the player is: the sanctum area ids, plus the WMO groups
|
||||
// that make up each quarter's buildings, since most of that ground reports the
|
||||
// plain Dalaran area id and cannot be told from neutral space by area alone.
|
||||
//
|
||||
// Both directions are asserted, because each alone is easy to satisfy wrongly.
|
||||
// Checking only that public ground is safe would pass a build where the guards
|
||||
// never fire; checking only the sanctums would pass the original bug untouched.
|
||||
type trespassCase struct {
|
||||
name string
|
||||
x, y, z float32
|
||||
wantEject bool
|
||||
why string
|
||||
}
|
||||
|
||||
func TestAC_4467_DalaranGuardsOnlyEjectFromRestrictedAreas(t *testing.T) {
|
||||
meta.Begin(t, meta.TestMeta{
|
||||
Tags: []string{"med", "world", "issue", "serial"},
|
||||
Runtime: "med",
|
||||
Issue: 4467,
|
||||
Category: "world/dalaran",
|
||||
})
|
||||
|
||||
horde := []trespassCase{
|
||||
{"MailboxByHerosWelcome", 5740.3, 680.9, 644.7, false, "public mailbox outside the inn"},
|
||||
// Deliberately a few yards off the trespasser destination (5758.79, 678.359,
|
||||
// 642.726): standing on it would make an ejection a zero-yard move that the
|
||||
// displacement oracle below could never see.
|
||||
{"StreetOutsideHerosWelcome", 5750.0, 678.8, 642.8, false, "the public street beside the spell's destination"},
|
||||
{"UnderbellySewers", 5759.2, 716.1, 618.6, false, "the public sewers"},
|
||||
{"SilverEnclaveThreshold", 5754.83, 718.36, 641.71, true, "past the guards into the Enclave"},
|
||||
{"SilverEnclaveCourtyard", 5740.3, 739.6, 641.9, true, "Alliance sanctum"},
|
||||
{"DeepSilverEnclave", 5671.43, 724.48, 653.41, true, "deep Alliance quarter"},
|
||||
{"InsideAHerosWelcome", 5725.674, 683.2177, 646.565, true, "the Alliance inn"},
|
||||
}
|
||||
alliance := []trespassCase{
|
||||
{"MailboxBySunreaverSide", 5893.2, 528.2, 641.4, false, "public mailbox on the Horde side"},
|
||||
{"LegerdemainLoungeAlliance", 5847.97, 635.43, 647.57, false, "neutral inn, 35.4y from a guard so really evaluated"},
|
||||
{"SunreaversSanctuary", 5862.0, 520.3, 655.5, true, "Horde sanctum"},
|
||||
{"InsideTheFilthyAnimal", 5892.3, 505.75, 641.65, true, "the Horde inn"},
|
||||
}
|
||||
|
||||
run := func(t *testing.T, race uint8, cases []trespassCase) {
|
||||
// GM mode must be off: npc_mageguard_dalaran skips IsGameMaster(), so a GM
|
||||
// is never ejected and every case would pass vacuously.
|
||||
bot := e2eharness.NewSolo(t, e2eharness.ScenarioOpts{
|
||||
Prefix: "Tres",
|
||||
Race: race,
|
||||
Class: e2eharness.ClassWarrior,
|
||||
Level: 80,
|
||||
})
|
||||
e2eharness.MustGM(t, bot.World, ".gm off")
|
||||
// The whole oracle rests on GM mode being off - IsGameMaster() short-circuits
|
||||
// the guard check - so ack it on the world thread rather than assume it took.
|
||||
bot.FlushWorld(t)
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
e2eharness.TeleportGo(t, bot.World, c.x, c.y, c.z, dalaranMap)
|
||||
|
||||
ejected := false
|
||||
deadline := time.Now().Add(settleWindow)
|
||||
for time.Now().Before(deadline) {
|
||||
ax, ay, az, _ := bot.Pos()
|
||||
if e2eharness.Distance3D(ax, ay, az, c.x, c.y, c.z) > 10 {
|
||||
ejected = true
|
||||
break
|
||||
}
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
}
|
||||
|
||||
switch {
|
||||
case c.wantEject && !ejected:
|
||||
e2eharness.Assertf(t, "%s (%.2f, %.2f, %.2f): a hostile player was not ejected from %s; "+
|
||||
"the restricted ground is unguarded", c.name, c.x, c.y, c.z, c.why)
|
||||
case !c.wantEject && ejected:
|
||||
e2eharness.Assertf(t, "%s (%.2f, %.2f, %.2f): a player standing on %s was teleported away; "+
|
||||
"this is the AC#4467 regression — public ground is not the faction quarter",
|
||||
c.name, c.x, c.y, c.z, c.why)
|
||||
default:
|
||||
t.Logf("PASS %-26s ejected=%-5v (%s)", c.name, ejected, c.why)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("Horde", func(t *testing.T) { run(t, e2eharness.RaceOrc, horde) })
|
||||
t.Run("Alliance", func(t *testing.T) { run(t, e2eharness.RaceHuman, alliance) })
|
||||
t.Run("PetInsideOwnerOutside", petInsideOwnerOutside)
|
||||
}
|
||||
|
||||
// The guard classifies and casts on who, not on who's owner, so a controlled unit
|
||||
// inside a quarter is ejected on its own footing even while its owner stands on
|
||||
// public ground. Every other case here is a lone player, where the two positions
|
||||
// are identical - this is the only one that would catch a regression reading the
|
||||
// owner instead.
|
||||
func petInsideOwnerOutside(t *testing.T) {
|
||||
bot := e2eharness.NewSolo(t, e2eharness.ScenarioOpts{
|
||||
Prefix: "TresP",
|
||||
Race: e2eharness.RaceOrc,
|
||||
Class: e2eharness.ClassWarlock,
|
||||
Level: 80,
|
||||
LearnAllClass: true,
|
||||
})
|
||||
|
||||
bot.Learn(t, spellSummonImp)
|
||||
_ = bot.CastOrGM(t, spellSummonImp, 0, 20*time.Second)
|
||||
pet := bot.WaitPlayerPet(t, 25*time.Second)
|
||||
|
||||
petPos := func() (float32, float32, float32, bool) {
|
||||
for _, u := range bot.World.GetNearbyUnits(200) {
|
||||
if u.GUID == pet {
|
||||
return u.PosX, u.PosY, u.PosZ, true
|
||||
}
|
||||
}
|
||||
return 0, 0, 0, false
|
||||
}
|
||||
|
||||
// Park owner and pet inside the Enclave under GM - the guard reads
|
||||
// IsGameMaster() from the owner, so nothing fires while positioning.
|
||||
e2eharness.MustGM(t, bot.World, ".gm on")
|
||||
e2eharness.TeleportGo(t, bot.World, 5740.3, 739.6, 641.9, dalaranMap)
|
||||
time.Sleep(3 * time.Second)
|
||||
stay := client.MakePetActionButton(client.PetCommandStay, client.PetActCommand)
|
||||
if err := bot.World.PetAction(pet, stay, 0); err != nil {
|
||||
e2eharness.HarnessFailf(t, "pet stay: %v", err)
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// Owner out to the public mailbox; the pet stays behind inside the quarter.
|
||||
e2eharness.TeleportGo(t, bot.World, 5740.3, 680.9, 644.7, dalaranMap)
|
||||
time.Sleep(3 * time.Second)
|
||||
px, py, pz, ok := petPos()
|
||||
if !ok {
|
||||
e2eharness.HarnessFailf(t, "pet is no longer tracked; it followed or despawned, so this case proves nothing")
|
||||
}
|
||||
if e2eharness.Distance3D(px, py, pz, 5740.3, 739.6, 641.9) > 10 {
|
||||
e2eharness.HarnessFailf(t, "pet did not hold position inside the quarter (%.1f, %.1f, %.1f)", px, py, pz)
|
||||
}
|
||||
|
||||
e2eharness.MustGM(t, bot.World, ".gm off")
|
||||
bot.FlushWorld(t)
|
||||
|
||||
// A stationary unit is never re-evaluated - MoveInLineOfSight fires on
|
||||
// relocation - so command Follow to make it move while still inside.
|
||||
follow := client.MakePetActionButton(client.PetCommandFollow, client.PetActCommand)
|
||||
if err := bot.World.PetAction(pet, follow, 0); err != nil {
|
||||
e2eharness.HarnessFailf(t, "pet follow: %v", err)
|
||||
}
|
||||
|
||||
ejected := false
|
||||
deadline := time.Now().Add(settleWindow)
|
||||
for time.Now().Before(deadline) {
|
||||
x, y, z, still := petPos()
|
||||
if !still {
|
||||
break
|
||||
}
|
||||
// Ejection lands on the trespasser destination, ~19y from the owner, so it
|
||||
// is distinguishable from the pet simply walking back.
|
||||
if e2eharness.Distance3D(x, y, z, 5758.79, 678.36, 642.73) < 5 {
|
||||
ejected = true
|
||||
break
|
||||
}
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
}
|
||||
|
||||
ox, oy, oz, _ := bot.Pos()
|
||||
if e2eharness.Distance3D(ox, oy, oz, 5740.3, 680.9, 644.7) > 10 {
|
||||
e2eharness.Assertf(t, "the owner was teleported off public ground at the mailbox; only the pet was trespassing")
|
||||
}
|
||||
if !ejected {
|
||||
e2eharness.Assertf(t, "a pet left inside the Silver Enclave was not ejected while its owner stood on public ground; "+
|
||||
"the guard is reading the owner's position instead of the trespasser's")
|
||||
}
|
||||
t.Logf("PASS PetInsideOwnerOutside pet ejected, owner untouched on public ground")
|
||||
}
|
||||
@@ -159,6 +159,7 @@ enum AreaTableIDs : uint32
|
||||
AREA_THE_CHILLED_QUAGMIRE = 4589,
|
||||
AREA_WESTSPARK_WORKSHOP = 4611,
|
||||
AREA_EASTSPARK_WORKSHOP = 4612,
|
||||
AREA_SUNREAVERS_SANCTUARY = 4616,
|
||||
AREA_VARGOTHS_RETREAT = 4637,
|
||||
AREA_FORMATION_GROUNDS = 4652,
|
||||
AREA_THE_CONSERVATORY_OF_LIFE = 4656,
|
||||
@@ -171,6 +172,7 @@ enum AreaTableIDs : uint32
|
||||
AREA_SUNREAVER_PAVILION = 4676,
|
||||
AREA_SILVER_COVENANT_PAVILION = 4677,
|
||||
AREA_ISLE_OF_CONQUEST = 4710,
|
||||
AREA_THE_SILVER_ENCLAVE = 4740,
|
||||
AREA_ICECROWN_CITADEL = 4812,
|
||||
AREA_HALLS_OF_REFLECTION = 4820,
|
||||
AREA_THE_FROZEN_THRONE = 4859,
|
||||
|
||||
@@ -17,12 +17,15 @@
|
||||
|
||||
#include "AreaDefines.h"
|
||||
#include "CreatureScript.h"
|
||||
#include "Map.h"
|
||||
#include "MoveSplineInit.h"
|
||||
#include "Player.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "ScriptedGossip.h"
|
||||
#include "TaskScheduler.h"
|
||||
#include "World.h"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
class npc_steam_powered_auctioneer : public CreatureScript
|
||||
{
|
||||
@@ -413,14 +416,52 @@ enum Spells
|
||||
SPELL_SILVER_COVENANT_DISGUISE_MALE = 70972,
|
||||
};
|
||||
|
||||
enum NPCs // All outdoor guards are within 35.0f of these NPCs
|
||||
enum NPCs
|
||||
{
|
||||
NPC_APPLEBOUGH_A = 29547,
|
||||
NPC_SWEETBERRY_H = 29715,
|
||||
NPC_SILVER_COVENANT_GUARDIAN_MAGE = 29254,
|
||||
NPC_SUNREAVER_GUARDIAN_MAGE = 29255,
|
||||
};
|
||||
|
||||
// The guards must notice a trespasser anywhere inside the quarter they watch, not
|
||||
// just beside themselves: no guard stands within interaction range of most of the
|
||||
// floor area of either sanctum.
|
||||
constexpr float GUARD_WATCH_RANGE = 40.0f;
|
||||
|
||||
// The area id only covers part of each sanctum. Most of the restricted ground -
|
||||
// the buildings, the inns, much of the interior - resolves to 4395 (Dalaran), the
|
||||
// same value the neutral Legerdemain Lounge reports, so an area check alone leaves
|
||||
// holes. WMOAreaTable.dbc does tell them apart, and it is what labels the building
|
||||
// on screen as you walk in, so match the WMO group as well.
|
||||
//
|
||||
// From WMOAreaTable.dbc for the Dalaran model (WMOID 5164): every group named
|
||||
// "The Silver Enclave" / "A Hero's Welcome", and "Sunreaver's Sanctuary" / "The
|
||||
// Filthy Animal". The neutral buildings - the Legerdemain Lounge, Sisters
|
||||
// Sorcerous, The Wonderworks, the Visitor Center - are in neither list, which is
|
||||
// what keeps a hostile visitor welcome in them. 25768 is The Beer Garden, a named
|
||||
// venue inside the Silver Enclave rather than a group named for the quarter, found
|
||||
// by sweeping the quarter and reading back the groups the server actually resolves.
|
||||
constexpr std::array<int32, 11> WMO_GROUPS_SILVER_ENCLAVE =
|
||||
{
|
||||
24537, 24704, 24713, 25067, 25177, 25367, 25368, 25369, 25370, 25371, 25768
|
||||
};
|
||||
constexpr std::array<int32, 7> WMO_GROUPS_SUNREAVERS_SANCTUARY =
|
||||
{
|
||||
24725, 25066, 25145, 25381, 25383, 25384, 25406
|
||||
};
|
||||
|
||||
template <std::size_t N>
|
||||
bool IsInsideWMOGroups(WorldObject const* who, std::array<int32, N> const& groups)
|
||||
{
|
||||
uint32 mogpFlags;
|
||||
int32 adtId, rootId, groupId;
|
||||
if (!who->GetMap()->GetAreaInfo(who->GetPhaseMask(),
|
||||
who->GetPositionX(), who->GetPositionY(), who->GetPositionZ(),
|
||||
mogpFlags, adtId, rootId, groupId))
|
||||
return false;
|
||||
|
||||
return std::find(groups.begin(), groups.end(), groupId) != groups.end();
|
||||
}
|
||||
|
||||
class npc_mageguard_dalaran : public CreatureScript
|
||||
{
|
||||
public:
|
||||
@@ -443,10 +484,10 @@ public:
|
||||
|
||||
void MoveInLineOfSight(Unit* who) override
|
||||
{
|
||||
if (!who || !who->IsInWorld()|| who->GetZoneId() != AREA_DALARAN || who->GetAreaId() == AREA_SEWER_EXIT_PIPE)
|
||||
if (!who || !who->IsInWorld() || who->GetZoneId() != AREA_DALARAN)
|
||||
return;
|
||||
|
||||
if (!me->IsWithinDist(who, 5.0f, false))
|
||||
if (!me->IsWithinDist(who, GUARD_WATCH_RANGE, false))
|
||||
return;
|
||||
|
||||
if (who->IsCreature() && who->GetCreatureType() == CREATURE_TYPE_NON_COMBAT_PET)
|
||||
@@ -459,31 +500,28 @@ public:
|
||||
player->HasAnyAuras(SPELL_SUNREAVER_DISGUISE_FEMALE, SPELL_SUNREAVER_DISGUISE_MALE, SPELL_SILVER_COVENANT_DISGUISE_FEMALE, SPELL_SILVER_COVENANT_DISGUISE_MALE))
|
||||
return;
|
||||
|
||||
// Eject on where the trespasser actually is, not on how it is standing
|
||||
// relative to the guard. Both halves are needed: the area ids cover each
|
||||
// sanctum's open courtyard, the WMO groups cover its buildings, which
|
||||
// report the plain zone id instead. Neutral streets, mailboxes, the sewer
|
||||
// pipe and the neutral inns are in neither.
|
||||
//
|
||||
// Position comes from who rather than its owner, so that a pet sent into
|
||||
// a quarter is ejected on its own footing - it is who that gets teleported.
|
||||
uint32 const areaId = who->GetAreaId();
|
||||
|
||||
switch (me->GetEntry())
|
||||
{
|
||||
case NPC_SILVER_COVENANT_GUARDIAN_MAGE:
|
||||
if (player->GetTeamId() == TEAM_HORDE) // Horde unit found in Alliance area
|
||||
{
|
||||
if (GetClosestCreatureWithEntry(me, NPC_APPLEBOUGH_A, 32.0f))
|
||||
{
|
||||
if (me->isInBackInMap(who, 12.0f)) // In my line of sight, "outdoors", and behind me
|
||||
DoCast(who, SPELL_TRESPASSER_A); // Teleport the Horde unit out
|
||||
}
|
||||
else // In my line of sight, and "indoors"
|
||||
DoCast(who, SPELL_TRESPASSER_A); // Teleport the Horde unit out
|
||||
}
|
||||
if (player->GetTeamId() == TEAM_HORDE
|
||||
&& (areaId == AREA_THE_SILVER_ENCLAVE || IsInsideWMOGroups(who, WMO_GROUPS_SILVER_ENCLAVE)))
|
||||
DoCast(who, SPELL_TRESPASSER_A);
|
||||
break;
|
||||
case NPC_SUNREAVER_GUARDIAN_MAGE:
|
||||
if (player->GetTeamId() == TEAM_ALLIANCE) // Alliance unit found in Horde area
|
||||
{
|
||||
if (GetClosestCreatureWithEntry(me, NPC_SWEETBERRY_H, 32.0f))
|
||||
{
|
||||
if (me->isInBackInMap(who, 12.0f)) // In my line of sight, "outdoors", and behind me
|
||||
DoCast(who, SPELL_TRESPASSER_H); // Teleport the Alliance unit out
|
||||
}
|
||||
else // In my line of sight, and "indoors"
|
||||
DoCast(who, SPELL_TRESPASSER_H); // Teleport the Alliance unit out
|
||||
}
|
||||
if (player->GetTeamId() == TEAM_ALLIANCE
|
||||
&& (areaId == AREA_SUNREAVERS_SANCTUARY
|
||||
|| IsInsideWMOGroups(who, WMO_GROUPS_SUNREAVERS_SANCTUARY)))
|
||||
DoCast(who, SPELL_TRESPASSER_H);
|
||||
break;
|
||||
}
|
||||
me->SetOrientation(me->GetHomePosition().GetOrientation());
|
||||
|
||||
Reference in New Issue
Block a user