fix(Scripts/Feralas): summon the Gordunni trap mound through its own spells (#27632)

Co-authored-by: offl <[email protected]>
Co-authored-by: Giacomo Pozzoni <[email protected]>
Co-authored-by: blinkysc <[email protected]>
Co-authored-by: Claude Opus 5 <[email protected]>
This commit is contained in:
Andrew
2026-09-15 19:09:25 -03:00
committed by GitHub
co-authored by offl Giacomo Pozzoni blinkysc Claude Opus 5
parent d56db46693
commit f68a9759bd
3 changed files with 176 additions and 14 deletions
+1 -1
View File
@@ -149,7 +149,7 @@ go test -tags=e2e ./local/... -count=1 -v -timeout 30m -parallel 1
| combat/vehicles | spellclick steed enter/exit | P2 | covered | — |
| spells/aura | apply/query; CC broken by damage; mount persist; paladin same-aura per-caster + Aura Mastery | P1 | covered (`TestAC_26130_*`, `TestAC_25765_*`) | #26130 #25765 |
| spells/cast | Charge on dummy; fail path; stance; Raise Dead + ghoul | P1 | covered (`TestAC_27061_*`) | #27061 |
| spells/effects | Charge / grounding totem / Sweeping Strikes Execute; forced cast summons at the forced caster, not at the unit that forced it | P1 | covered (`TestAC_26997_*`, `TestEffects_ForceCastDestination`); dummy-summon `blocked-harness` (engineering dummy lifetime) | #26774 #26997 #27621 |
| spells/effects | Charge / grounding totem / Sweeping Strikes Execute; forced cast summons at the forced caster, not at the unit that forced it; a tripped Gordunni Trap rolls out both dirt mounds | P1 | covered (`TestAC_26997_*`, `TestEffects_ForceCastDestination`, `TestEffects_GordunniTrapRollsBothMounds`); dummy-summon `blocked-harness` (engineering dummy lifetime) | #26774 #26997 #27621 |
| social/group | form / leave / leader / loot method / disband | P2 | covered | — |
| social/loot | need/greed / master loot; below-half kill; a roll that ends on the timer names the roll in its terminating packet, and the member who never voted gets one PASS | P1 | covered (`TestAC_26862_*`, `TestAC_27299_*`); chest mid-roll `blocked-harness` (GO 194821 UseGameObject); pass-on-loot delete `blocked-harness` (item-survive after ALL_PASSED) | #26894 #26862 #27299 #22000 |
| social/trade | item+gold accept; cancel; walk-OOR TARGET_TO_FAR | P1 | covered | #25723 |
@@ -0,0 +1,161 @@
//go:build e2e
package effects_test
import (
"fmt"
"math"
"math/rand"
"testing"
"time"
_ "github.com/go-sql-driver/mysql"
"github.com/azerothcore/AzerothGhost/e2e/e2eharness"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
)
const (
gordunniTrap uint32 = 144050 // GO, type TRAP, casts 19395 on whoever disturbs it
gordunniMoundChest uint32 = 144064 // summoned by 11756, holds the Gordunni Cobalt
gordunniMoundJunk uint32 = 177681 // summoned by 19394, holds the junk loot
// Widest gap between the trap firing and the summoned mound reaching the cache.
moundWindow = 10 * time.Second
// Trips are spread around a ring, so every trip is >20yd from every other while none
// of them wanders far from the isolation pad. That separation is what keeps the radius
// below matching only the mound this trip produced: a mound lives for the summon
// spell's 120s duration, which outlasts the whole run, so without the spacing an
// earlier trip's mound would satisfy every later one.
tripRingRadius float32 = 70
// The mound lands at TARGET_DEST_CASTER_FRONT, ~3yd ahead of the bot -- but snapped
// to the ground, which is why the bot is placed on the terrain below the pad rather
// than on the pad itself: from the pad it would drop ~105yd and fall out of range.
moundSearchRadius float32 = 8
// Half-width of the per-run displacement of the ring centre.
ringJitter = 150
// spell_gordunni_trap rolls chest or junk 50/50, so one trip proves nothing about
// the roll. Twenty leave a 2*(1/2)^20 chance, about 0.0002%, of missing either face on
// a fixed core; the loop exits as soon as both have been seen, which takes 3 trips on
// average, so the extra headroom costs nothing in the normal case.
maxTrips = 20
)
// The Gordunni Trap casts 19395 on the player who disturbs it, and spell_gordunni_trap
// turns that into one of the two mound summon spells: 11756 (chest) or 19394 (junk).
//
// The script used to summon GO 144064 directly, so the junk mound never appeared at all
// and the only loot a trap could ever yield was the chest's. Seeing 177681 come out of a
// trap is therefore impossible on the old script and is the oracle here; the chest is
// asserted alongside it so a roll stuck on one face fails rather than passing on half
// the behaviour.
//
// PR: https://github.com/azerothcore/azerothcore-wotlk/pull/27632
// Upstream: https://github.com/TrinityCore/TrinityCore/commit/9d6c4e3d931875008cd9ca15754b3edb1aef2a01
func TestEffects_GordunniTrapRollsBothMounds(t *testing.T) {
meta.Begin(t, meta.TestMeta{
Tags: []string{"short", "spells", "serial"},
Runtime: "short",
Category: "spells/effects",
})
bot := e2eharness.NewSolo(t, e2eharness.ScenarioOpts{Prefix: "Gord"})
pad := e2eharness.PackagePad(t)
bot.TeleportPad(t, pad)
// Mounds from earlier runs are still standing exactly where those runs put them, and
// would let this one pass without the trap producing anything. Offsetting the ring
// per run keeps every trip on ground no previous run has used.
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
centreX := pad.X + float32(rng.Intn(2*ringJitter)-ringJitter)
centreY := pad.Y + float32(rng.Intn(2*ringJitter)-ringJitter)
seen := map[uint64]bool{}
var sawChest, sawJunk, trips int
for trip := 1; trip <= maxTrips && (sawChest == 0 || sawJunk == 0); trip++ {
trips = trip
angle := 2 * math.Pi * float64(trip) / float64(maxTrips)
// .go xyz without a z lands on max(ground, water level), so the bot and the
// mound it summons end up at the same height.
e2eharness.MustGMTeleport(t, bot.World, fmt.Sprintf(".go xyz %.3f %.3f",
centreX+tripRingRadius*float32(math.Cos(angle)),
centreY+tripRingRadius*float32(math.Sin(angle))))
if spawn := bot.SpawnGameObject(t, gordunniTrap); spawn == 0 {
e2eharness.Preconditionf(t, "trip %d/%d: no spawn id resolved for the Gordunni Trap (%d); "+
"without it the trap cannot be cleaned up and the run would litter the pad",
trip, maxTrips, gordunniTrap)
}
// SpawnGameObject leaves the bot in GM mode. 19395 is a negative spell cast at
// the player, so SpellInfo::CheckTarget answers SPELL_FAILED_BM_OR_INVISGOD
// while the bot is GM-invisible and the trap fires into nothing.
e2eharness.MustGM(t, bot.World, ".gm off")
bot.FlushWorld(t)
trap := bot.WaitGameObject(t, gordunniTrap, 15*time.Second)
// A mound from an earlier run can still be standing inside this trip's radius.
// Bank it before tripping the trap so only a fresh summon can satisfy the search.
for _, entry := range []uint32{gordunniMoundChest, gordunniMoundJunk} {
if g := bot.World.FindGameObjectByEntry(entry, moundSearchRadius); g != 0 {
seen[g] = true
}
}
bot.GameObjectUse(t, trap)
// A guid this run has already credited cannot stand in for a fresh summon.
chest, junk := uint64(0), uint64(0)
deadline := time.Now().Add(moundWindow)
for time.Now().Before(deadline) {
if g := bot.World.FindGameObjectByEntry(gordunniMoundChest, moundSearchRadius); g != 0 && !seen[g] {
chest = g
}
if g := bot.World.FindGameObjectByEntry(gordunniMoundJunk, moundSearchRadius); g != 0 && !seen[g] {
junk = g
}
if chest != 0 || junk != 0 {
break
}
time.Sleep(50 * time.Millisecond)
}
if chest != 0 {
seen[chest] = true
}
if junk != 0 {
seen[junk] = true
}
switch {
case chest != 0:
sawChest++
t.Logf("trip %d/%d: chest mound (%d) guid=0x%X", trip, maxTrips, gordunniMoundChest, chest)
case junk != 0:
sawJunk++
t.Logf("trip %d/%d: junk mound (%d) guid=0x%X", trip, maxTrips, gordunniMoundJunk, junk)
default:
e2eharness.Assertf(t, "trip %d/%d: the Gordunni Trap (%d) produced neither mound (%d, %d) "+
"within %s of CMSG_GAMEOBJ_USE; 19395 never reached spell_gordunni_trap, or the summon "+
"spell it picked did not resolve",
trip, maxTrips, gordunniTrap, gordunniMoundChest, gordunniMoundJunk, moundWindow)
}
}
if sawJunk == 0 {
e2eharness.Assertf(t, "the junk mound (%d) never appeared in %d trips (chest %d times); "+
"spell_gordunni_trap is summoning the chest gameobject directly instead of casting "+
"11756 or 19394, so half the trap's loot is unreachable",
gordunniMoundJunk, trips, sawChest)
}
if sawChest == 0 {
e2eharness.Assertf(t, "the chest mound (%d) never appeared in %d trips (junk %d times); "+
"11756 is not being cast or its summon is not resolving",
gordunniMoundChest, trips, sawJunk)
}
t.Logf("PASS both mounds rolled out of the trap: chest %d, junk %d", sawChest, sawJunk)
}