mirror of
https://gitcode.com/GitHub_Trending/az/azerothcore-wotlk.git
synced 2026-10-10 07:06:38 +08:00
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:
co-authored by
offl
Giacomo Pozzoni
blinkysc
Claude Opus 5
parent
d56db46693
commit
f68a9759bd
+1
-1
@@ -149,7 +149,7 @@ go test -tags=e2e ./local/... -count=1 -v -timeout 30m -parallel 1
|
|||||||
| combat/vehicles | spellclick steed enter/exit | P2 | covered | — |
|
| 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/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/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/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/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 |
|
| 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)
|
||||||
|
}
|
||||||
@@ -15,34 +15,35 @@
|
|||||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "Group.h"
|
|
||||||
#include "Player.h"
|
|
||||||
#include "ScriptedCreature.h"
|
|
||||||
#include "SpellScript.h"
|
#include "SpellScript.h"
|
||||||
#include "SpellScriptLoader.h"
|
#include "SpellScriptLoader.h"
|
||||||
|
#include "Unit.h"
|
||||||
|
|
||||||
enum GordunniTrap
|
enum GordunniTrapSpells
|
||||||
{
|
{
|
||||||
GO_GORDUNNI_DIRT_MOUND = 144064,
|
SPELL_GORDUNNI_DIRT_MOUND_CHEST = 11756,
|
||||||
|
SPELL_GORDUNNI_DIRT_MOUND_JUNK = 19394
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 19395 - Gordunni Trap
|
||||||
class spell_gordunni_trap : public SpellScript
|
class spell_gordunni_trap : public SpellScript
|
||||||
{
|
{
|
||||||
PrepareSpellScript(spell_gordunni_trap);
|
PrepareSpellScript(spell_gordunni_trap);
|
||||||
|
|
||||||
void HandleDummy()
|
bool Validate(SpellInfo const* /*spellInfo*/) override
|
||||||
{
|
{
|
||||||
if (Unit* caster = GetCaster())
|
return ValidateSpellInfo({ SPELL_GORDUNNI_DIRT_MOUND_CHEST, SPELL_GORDUNNI_DIRT_MOUND_JUNK });
|
||||||
if (GameObject* chest = caster->SummonGameObject(GO_GORDUNNI_DIRT_MOUND, caster->GetPositionX(), caster->GetPositionY(), caster->GetPositionZ(), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0))
|
}
|
||||||
{
|
|
||||||
chest->SetSpellId(GetSpellInfo()->Id);
|
void HandleDummy(SpellEffIndex /*effIndex*/)
|
||||||
caster->RemoveGameObject(chest, false);
|
{
|
||||||
}
|
Unit* target = GetHitUnit();
|
||||||
|
target->CastSpell(target, urand(0, 1) ? SPELL_GORDUNNI_DIRT_MOUND_CHEST : SPELL_GORDUNNI_DIRT_MOUND_JUNK);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Register() override
|
void Register() override
|
||||||
{
|
{
|
||||||
OnCast += SpellCastFn(spell_gordunni_trap::HandleDummy);
|
OnEffectHitTarget += SpellEffectFn(spell_gordunni_trap::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user