Stop the Re-Sizer boar crash and the extra Exile camp copies.

This commit is contained in:
luis
2026-09-23 17:22:17 -03:00
parent f58e739dcc
commit ed4bc78023
4 changed files with 206 additions and 64 deletions
+4 -1
View File
@@ -262,7 +262,10 @@ void VehicleAI::UpdateAI(uint32 diff)
void VehicleAI::OnCharmed(bool /*isNew*/)
{
bool const charmed = me->IsCharmed();
if (!me->GetVehicleKit()->IsVehicleInUse() && !charmed && _hasConditions) // was used and has conditions
Vehicle* kit = me->GetVehicleKit();
// A creature scripted as VehicleAI can be charmed before a kit exists. The giant boar
// summon takes this path; dereferencing a missing kit took the worldserver down.
if (kit && !kit->IsVehicleInUse() && !charmed && _hasConditions) // was used and has conditions
{
_dismiss = true; // needs reset
}
@@ -206,6 +206,7 @@ enum ExileReachHelpers
SPELL_WANDERING_BOAR_MOB_CHARGE = 256089,
SPELL_WANDERING_BOAR_MOB_GORE = 256077,
SPELL_LINDIE_DESUMMON = 305756,
SPELL_PING_LINDIE = 305754,
SPELL_SUMMON_DARKMAUL_PLAINS_QUESTGIVERS = 305776,
SPELL_CANCEL_ALL_SKELETAL_ARMY_SPELLS = 305830,
SPELL_PING_GARRICK_TO_RIDE_THE_BOAR = 316984,
@@ -353,6 +354,7 @@ enum ExileReachHelpers
NPC_DARKMAUL_PLAINS_FIZZLEPOP_RESIZING_SUMMON = 167915,
NPC_DARKMAUL_PLAINS_SPRINGSTOCK_RESIZING_SUMMON = 156749,
NPC_DARKMAUL_PLAINS_WANDERING_BOAR = 156716,
NPC_DARKMAUL_PLAINS_RESIZED_BOAR = 156736,
NPC_DARKMAUL_PLAINS_WANDERING_BOAR_SUMMON = 156267,
NPC_DARKMAUL_PLAINS_GIANT_BOAR_VEHICLE = 156595,
NPC_OGRE_RUINS_TORGOK_FIGHTER_A = 156807,
@@ -686,6 +688,7 @@ enum ExileReachHelpers
SOUND_COPTER_SIZING_1 = 158017,
SOUND_COPTER_SIZING_2 = 157686,
SOUND_BOAR_SIZING = 157516,
SOUND_RESIZED_BOAR_GROW = 157469,
ACHIEVEMENT_NPE_SOLD_ITEM = 14221,
};
@@ -87,23 +87,36 @@ public:
// Remove summons if player abandons or rewards a quest
if (player->GetMapId() == MAP_NPE)
{
if (player->GetQuestStatus(questId) == QUEST_STATUS_NONE || player->GetQuestStatus(questId) == QUEST_STATUS_REWARDED)
QuestStatus const status = player->GetQuestStatus(questId);
// The copter scene summons a second Lindie on top of the camp. Once the quest is
// ready to turn in she has to go, or she stands beside the phase copy and the follower.
bool const copterSceneDone = status == QUEST_STATUS_COMPLETE
&& (questId == QUEST_THE_SCOUT_O_MATIC_5000 || questId == QUEST_H_THE_CHOPPY_BOOSTER_MK5);
if (status == QUEST_STATUS_NONE || status == QUEST_STATUS_REWARDED || copterSceneDone)
{
std::vector<uint32> activeSummons;
switch (questId)
{
case QUEST_H_THE_CHOPPY_BOOSTER_MK5:
activeSummons.push_back(NPC_DARKMAUL_PLAINS_CHOPPYBOOSTER_SUMMONER);
if (!copterSceneDone)
activeSummons.push_back(NPC_DARKMAUL_PLAINS_CHOPPYBOOSTER_OBSERVER);
activeSummons.push_back(NPC_DARKMAUL_PLAINS_FIZZLEPOP);
break;
case QUEST_THE_SCOUT_O_MATIC_5000:
activeSummons.push_back(NPC_DARKMAUL_PLAINS_SCOUTOMATIC_SUMMONER);
if (!copterSceneDone)
activeSummons.push_back(NPC_DARKMAUL_PLAINS_SCOUTOMATIC_OBSERVER);
activeSummons.push_back(NPC_DARKMAUL_PLAINS_SPRINGSTOCK);
break;
case QUEST_RE_SIZING_THE_SITUATION:
activeSummons.push_back(NPC_DARKMAUL_PLAINS_WANDERING_BOAR_SUMMON);
activeSummons.push_back(NPC_DARKMAUL_PLAINS_RESIZED_BOAR);
activeSummons.push_back(NPC_DARKMAUL_PLAINS_SPRINGSTOCK_RESIZING_SUMMON);
break;
case QUEST_H_RE_SIZING_THE_SITUATION:
activeSummons.push_back(NPC_DARKMAUL_PLAINS_RESIZED_BOAR);
activeSummons.push_back(NPC_DARKMAUL_PLAINS_FIZZLEPOP_RESIZING_SUMMON);
break;
}
@@ -267,7 +280,8 @@ struct npc_exiles_reach_darkmaul_plains_167905_156526 : public VehicleAI
{
if (timer <= diff)
{
if (Unit* passenger = me->GetVehicleKit()->GetPassenger(0))
Vehicle* kit = me->GetVehicleKit();
if (Unit* passenger = kit ? kit->GetPassenger(0) : nullptr)
{
if (Player* player = passenger->ToPlayer())
{
@@ -353,15 +367,13 @@ struct npc_exiles_reach_darkmaul_plains_156267 : public VehicleAI
{
if (Player* player = summoner->ToPlayer())
{
if (me->GetAreaId() == AREA_DARKMAUL_PLAINS)
if (me->GetAreaId() == AREA_DARKMAUL_PLAINS && me->GetVehicleKit())
{
if (Creature* garrick = player->GetSummonedCreatureByEntry(NPC_OGRE_RUINS_TORGOK_FIGHTER_A))
{
garrick->EnterVehicle(me, 1);
}
}
}
}
};
// Cork Fizzlepop, Lindie Springstock (Darkmaul Plains) (Re-Sizing the Situation)
@@ -382,19 +394,45 @@ struct npc_exiles_reach_darkmaul_plains_167915_156749 : public ScriptedAI
me->FollowTarget(player);
}
void SpellHit(WorldObject* /*caster*/, SpellInfo const* spellInfo) override
{
// 305754 is the ping the third Re-Sizer test sends. She runs back to camp once.
if (!spellInfo || spellInfo->Id != SPELL_PING_LINDIE)
return;
_events.ScheduleEvent(EVENT_RESIZING_RUN_HOME, 2s);
}
void MovementInform(uint32 type, uint32 id) override
{
if (type != POINT_MOTION_TYPE || id != POINT_RESIZING_HOME)
return;
if (Unit* owner = me->GetCharmerOrOwnerPlayerOrPlayerItself())
me->CastSpell(owner, SPELL_LINDIE_DESUMMON, true);
else
me->DespawnOrUnsummon();
}
void UpdateAI(uint32 diff) override
{
if (timer <= diff)
_events.Update(diff);
while (uint32 eventId = _events.ExecuteEvent())
{
if (me->GetPosition().IsInDist(ResizingTheSituationPositions[1], 1.f))
me->CastSpell(me->GetCharmerOrOwnerPlayerOrPlayerItself(), SPELL_LINDIE_DESUMMON, true);
if (eventId == EVENT_RESIZING_RUN_HOME)
me->GetMotionMaster()->MovePoint(POINT_RESIZING_HOME, ResizingTheSituationPositions[1]);
}
else
timer -= diff;
}
private:
uint32 timer = 1000;
enum ResizingGuardianEvents
{
EVENT_RESIZING_RUN_HOME = 1,
POINT_RESIZING_HOME = 0
};
EventMap _events;
};
// Wandering Boar (Darkmaul Plains) (Re-Sizing the Situation)
@@ -402,20 +440,51 @@ struct npc_exiles_reach_darkmaul_plains_156736_156716 : public ScriptedAI
{
npc_exiles_reach_darkmaul_plains_156736_156716(Creature* creature) : ScriptedAI(creature) {}
bool IsResizedBoar() const
{
return me->GetEntry() == NPC_DARKMAUL_PLAINS_RESIZED_BOAR;
}
void Reset() override
{
if (IsResizedBoar())
{
me->SetReactState(REACT_PASSIVE);
return;
}
_events.Reset();
_events.ScheduleEvent(1, 3s);
}
void JustAppeared() override
{
if (!IsResizedBoar())
{
CreatureAI::JustAppeared();
return;
}
// Do not follow the player. The boar grows, then walks to the camp and despawns.
me->SetReactState(REACT_PASSIVE);
_events.Reset();
_events.ScheduleEvent(EVENT_RESIZED_BOAR_GROW, 1s);
}
void JustEngagedWith(Unit* who) override
{
if (IsResizedBoar())
return;
me->CastSpell(who, SPELL_WANDERING_BOAR_MOB_CHARGE);
}
// creature shouldn't die at resizing the situation quest otherwise killcredit doubled
void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo = nullptr*/) override
{
if (IsResizedBoar())
return;
if (Player* player = attacker->ToPlayer())
{
if (damage > me->GetHealth() && (player->GetQuestStatus(QUEST_RE_SIZING_THE_SITUATION) == QUEST_STATUS_INCOMPLETE || player->GetQuestStatus(QUEST_H_RE_SIZING_THE_SITUATION) == QUEST_STATUS_INCOMPLETE))
@@ -426,32 +495,51 @@ struct npc_exiles_reach_darkmaul_plains_156736_156716 : public ScriptedAI
}
}
void IsSummonedBy(WorldObject* summoner) override
void MovementInform(uint32 type, uint32 id) override
{
Player* player = summoner->ToPlayer();
if (!player)
if (!IsResizedBoar() || type != POINT_MOTION_TYPE || id != POINT_RESIZED_BOAR_CAMP)
return;
me->CastSpell(me, SPELL_GROW_1);
me->GetMotionMaster()->MovePoint(0, ResizingTheSituationPositions[0].GetPositionX(), ResizingTheSituationPositions[0].GetPositionY(), ResizingTheSituationPositions[0].GetPositionZ(), true, 0.7f);
player->KilledMonsterCredit(156722);
me->SetFacingTo(0.785398f);
if (Creature* springstock = player->GetSummonedCreatureByEntry(NPC_DARKMAUL_PLAINS_SPRINGSTOCK_RESIZING_SUMMON))
springstock->GetMotionMaster()->MovePoint(0, ResizingTheSituationPositions[1].GetPositionX(), ResizingTheSituationPositions[1].GetPositionY(), ResizingTheSituationPositions[1].GetPositionZ(), true, 4.f);
Unit* summoner = nullptr;
if (TempSummon* summoned = me->ToTempSummon())
summoner = summoned->GetSummonerUnit();
if (!summoner)
summoner = me->GetOwner();
if (Creature* fizzlepop = player->GetSummonedCreatureByEntry(NPC_DARKMAUL_PLAINS_FIZZLEPOP_RESIZING_SUMMON))
fizzlepop->GetMotionMaster()->MovePoint(0, ResizingTheSituationPositions[1].GetPositionX(), ResizingTheSituationPositions[1].GetPositionY(), ResizingTheSituationPositions[1].GetPositionZ(), true, 4.f);
if (summoner)
if (Player* player = summoner->ToPlayer())
player->CastSpell(player, SPELL_UPDATE_PHASESHIFT, true);
me->DespawnOrUnsummon();
}
void UpdateAI(uint32 diff) override
{
if (timer <= diff)
if (IsResizedBoar())
{
if (me->GetPosition().IsInDist(ResizingTheSituationPositions[0], 1.f))
me->DespawnOrUnsummon();
_events.Update(diff);
while (uint32 eventId = _events.ExecuteEvent())
{
switch (eventId)
{
case EVENT_RESIZED_BOAR_GROW:
me->CastSpell(me, SPELL_GROW_1, true);
me->PlayDirectSound(SOUND_RESIZED_BOAR_GROW);
_events.ScheduleEvent(EVENT_RESIZED_BOAR_MOVE, 1s);
break;
case EVENT_RESIZED_BOAR_MOVE:
me->SetWalk(false);
me->GetMotionMaster()->MovePoint(POINT_RESIZED_BOAR_CAMP, ResizingTheSituationPositions[0]);
break;
default:
break;
}
}
return;
}
else
timer -= diff;
if (!UpdateVictim())
return;
@@ -476,8 +564,14 @@ struct npc_exiles_reach_darkmaul_plains_156736_156716 : public ScriptedAI
}
private:
enum ResizedBoarEvents
{
EVENT_RESIZED_BOAR_GROW = 2,
EVENT_RESIZED_BOAR_MOVE = 3,
POINT_RESIZED_BOAR_CAMP = 1
};
EventMap _events;
uint32 timer = 1000;
};
// Lightspawn (Darkmaul Plains)
@@ -700,20 +794,6 @@ public:
class spell_re_sizing_305716_AuraScript : public AuraScript
{
void OnApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
{
Unit* caster = GetCaster();
Unit* target = GetUnitOwner();
if (!caster || !target || !target->IsCreature())
return;
Creature* creature = caster->ToCreature();
if (creature->GetEntry() != NPC_DARKMAUL_PLAINS_WANDERING_BOAR)
return;
}
void HandleRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
{
Unit* caster = GetCaster();
@@ -730,30 +810,47 @@ public:
caster->SetChannelSpellId(0);
caster->SetChannelVisual({});
uint32 hitVariants[3] = { SPELL_RESIZER_HIT_TOSMALL, SPELL_RESIZER_HIT_EXPLODE, SPELL_RESIZER_OVERCHARGE };
uint32 selectedFailedSpell = hitVariants[rand() % 3];
// GetQuestStatus is an enum. Any non-zero status, including already turned in, used to count as "on the quest".
uint32 questId = 0;
if (player->GetQuestStatus(QUEST_RE_SIZING_THE_SITUATION) == QUEST_STATUS_INCOMPLETE)
questId = QUEST_RE_SIZING_THE_SITUATION;
else if (player->GetQuestStatus(QUEST_H_RE_SIZING_THE_SITUATION) == QUEST_STATUS_INCOMPLETE)
questId = QUEST_H_RE_SIZING_THE_SITUATION;
if (player->GetQuestStatus(QUEST_RE_SIZING_THE_SITUATION) || player->GetQuestStatus(QUEST_H_RE_SIZING_THE_SITUATION))
{
if (selectedFailedSpell == SPELL_RESIZER_HIT_EXPLODE && QuestAccepted(player, QUEST_H_THE_REDEATHER))
PlayExileConversation(player, CONVERSATION_REDEATHER_EXPLODE);
if (!questId)
return;
if (player->GetQuestObjectiveProgress(QUEST_RE_SIZING_THE_SITUATION, 0) < 2)
// 0 shrinks the boar, 1 bursts it, 2 grows the re-sized boar and sends Lindie home.
// Despawn is delayed so the aura owner is not deleted inside this hook. The old
// immediate despawn, plus a kill credit from the boar script, crashed the worldserver.
int32 const tests = player->GetQuestObjectiveProgress(questId, 0);
if (tests <= 0)
{
player->CastSpell(target, selectedFailedSpell, true);
player->CastSpell(creature, SPELL_RESIZER_HIT_TOSMALL, true);
creature->DespawnOrUnsummon(2s);
}
else if (tests == 1)
{
player->CastSpell(creature, SPELL_RESIZER_HIT_EXPLODE, true);
creature->DespawnOrUnsummon(2s);
}
else if (questId == QUEST_H_RE_SIZING_THE_SITUATION)
{
player->CastSpell(creature, SPELL_RESIZER_OVERCHARGE, true);
player->CastSpell(player, SPELL_PING_LINDIE, true);
creature->DespawnOrUnsummon(3s);
}
else
{
player->CastSpell(target, SPELL_RESIZER_SPARKS_AND_BOAR_SUMMON, true);
creature->DespawnOrUnsummon();
}
player->CastSpell(creature, SPELL_RESIZER_SPARKS_AND_BOAR_SUMMON, true);
player->CastSpell(player, SPELL_PING_LINDIE, true);
creature->DespawnOrUnsummon(2s);
}
}
void Register() override
{
OnEffectApply += AuraEffectApplyFn(spell_re_sizing_305716_AuraScript::OnApply, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY, AURA_EFFECT_HANDLE_REAL);
OnEffectRemove += AuraEffectRemoveFn(spell_re_sizing_305716_AuraScript::HandleRemove, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY, AURA_EFFECT_HANDLE_REAL);
AfterEffectRemove += AuraEffectRemoveFn(spell_re_sizing_305716_AuraScript::HandleRemove, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY, AURA_EFFECT_HANDLE_REAL);
}
};
@@ -941,12 +1038,27 @@ public:
{
PreventHitDefaultEffect(effIndex);
WorldLocation const* loc = GetExplTargetDest();
Unit* caster = GetCaster();
if (!caster)
return;
caster->GetMotionMaster()->MoveCharge(loc->GetPositionX(), loc->GetPositionY(), loc->GetPositionZ());
// The ride scene casts this at the boar with no destination. A null dest crashed the worldserver.
float x, y, z;
if (WorldLocation const* loc = GetExplTargetDest())
{
x = loc->GetPositionX();
y = loc->GetPositionY();
z = loc->GetPositionZ();
}
else
{
Position dest = caster->GetFirstCollisionPosition(20.f, 0.f);
x = dest.GetPositionX();
y = dest.GetPositionY();
z = dest.GetPositionZ();
}
caster->GetMotionMaster()->MoveCharge(x, y, z);
}
void Register()
@@ -64,7 +64,31 @@ public:
{
if (player->GetMapId() == MAP_EXILES_REACH)
{
switch (player->GetAreaId())
uint32 const area = player->GetAreaId();
if (area != AREA_MURLOC_HIDEAWAY)
{
// These turn on after Murloc Mania and were never removed. Three of them stay
// up together, so the same camp creatures (and the untargetable triggers in
// those phases) are drawn again at the Abandoned Camp and on Darkmaul Plains.
uint32 const shorePhases[] =
{
PHASE_GENERAL_SHORE,
PHASE_ALLIANCE_SHORE_GARRICK_AND_FRIENDS_INJURED,
PHASE_ALLIANCE_SHORE_GARRICK_AND_FRIENDS_HEALED,
PHASE_ALLIANCE_SHORE_12940,
PHASE_ALLIANCE_SHORE_13157,
PHASE_ALLIANCE_SHORE_13503,
PHASE_HORDE_SHORE_BREKA_AND_FRIENDS_INJURED,
PHASE_HORDE_SHORE_BREKA_AND_FRIENDS_HEALED,
};
for (uint32 phaseId : shorePhases)
if (player->GetPhaseShift().HasPhase(phaseId))
PhasingHandler::RemovePhase(player, phaseId, true);
return;
}
switch (area)
{
case AREA_MURLOC_HIDEAWAY:
{