## Summary Migrates the Quest system gumps from legacy `Gump` to `DynamicGump` / `StaticGump<T>`. **Renames** `Engines/ML Quests/Gumps/BaseQuestGump` to **`BaseMLQuestGump`** to disambiguate from the Core quest abstract (`Engines/Quests/Core/QuestSystem.cs`). Both abstracts now extend `DynamicGump`. **Abstract bases**: - `Server.Engines.Quests.BaseQuestGump` (Core) - now `abstract DynamicGump`. Holds constants and a static `AddHtmlObject(ref DynamicGumpBuilder, ...)` helper. Concrete subclasses each provide their own `BuildLayout`. - `Server.Engines.MLQuests.Gumps.BaseMLQuestGump` (ML, renamed) - now `abstract DynamicGump` with a `protected abstract BuildContent(ref DynamicGumpBuilder)` hook. The base's `BuildLayout` draws shared chrome (background art, frame, label header) and then invokes `BuildContent`; subclasses use `BuildPage`, `SetTitle`, `RegisterButton`, `SetPageCount`, and content helpers (`AddDescription`, `AddObjectives`, `AddObjectivesProgress`, `AddRewardsPage`, `AddRewards`, `AddConversation`). **Concrete Core gumps** migrated to `DynamicGump`: - `QuestCancelGump`, `QuestOfferGump` (Core), `QuestObjectivesGump`, `QuestConversationsGump`, `QuestLogUpdatedGump`, `QuestItemInfoGump`, `SheetMusicOfferGump` (Impresario), `PaintedImageGump` (renamed from `PaintedImage.InternalGump`). **Concrete ML gumps** migrated to `DynamicGump` (extending `BaseMLQuestGump` or directly): - `InfoNPCGump`, `QuestConversationGump`, `QuestLogDetailedGump`, `QuestLogGump`, `QuestOfferGump` (ML), `QuestReportBackGump`, `QuestRewardGump`, `QuestCancelConfirmGump`, `RaceChangeConfirmGump`. **`StaticGump<T>` migrations**: - `ScrollOfAbraxusGump` (Dark Tides). Its layout is a single hard-coded cliloc (1060116) with no per-instance dynamic content - safe to cache. **Cliloc rule**: Every other quest dialog bakes per-instance cliloc IDs into its layout (quest titles, NPC names, race-specific prompts, era-conditional progress messages, escort destinations). Per the cliloc rule, baking different cliloc numbers into a cached layout would defeat `StaticGump<T>` caching - so all of these are `DynamicGump`. **Signature changes** to support the migration: - `QuestObjective.RenderMessage`/`RenderProgress` now take `ref DynamicGumpBuilder builder` (15 overrides updated across Collector, Solen Matriarch, Ambitious Solen Queen, Study of the Solen Hive, Terrible Hatchlings, The Summoning, Uzeraan Turmoil, Witch Apprentice, Emino's Undertaking). - ML `BaseObjective.WriteToGump` / `BaseObjectiveInstance.WriteToGump` / `BaseReward.WriteToGump` now take `ref DynamicGumpBuilder` (KillObjective, GainSkillObjective, EscortObjective, CollectObjective, DeliverObjective, BaseReward). **Empty-gump and `Singleton` rules**: All concrete gumps now have private constructors with static `DisplayTo` entry points that null-check the player NetState before allocation. All gumps that shouldn't stack are `Singleton => true`. **External callers updated**: `MLQuest.SendOffer`/`OnRefuse`, `MLQuestEntry.SendProgressGump`/`SendRewardGump`/`SendReportBackGump`, `MLQuestSystem.QuestGumpRequest` and `ViewQuestsCommand`, `BoonCollector` (Darius/Nedrick), `SirHelper.OnDoubleClick` (no longer caches a single shared `InfoNPCGump` instance - `DisplayTo` constructs one per click and the gump's `Singleton => true` handles deduplication), `RaceChangeDeed.OnDoubleClick`, `PaintedImage.OnDoubleClick`, `ScrollOfAbraxus.OnDoubleClick`, `Impresario.OnTalk`, and `PlayerMobile`'s `BaseQuestGump` alias is now `BaseMLQuestGump`. **Concrete subclasses found beyond the listed entry points**: `SheetMusicOfferGump` (in Impresario.cs), `PaintedImageGump` (was `PaintedImage.InternalGump`), `QuestObjectivesGump`, `QuestConversationsGump`, `QuestLogUpdatedGump`, `QuestItemInfoGump` (the Core base has these embedded across `QuestSystem.cs`/`QuestObjective.cs`/`QuestConversation.cs`/`QuestItemInfo.cs`). **Code-standards cleanup**: Renamed legacy `m_X` private fields to `_x` in rewritten files; braces on all control flow.
171 lines
5.5 KiB
C#
171 lines
5.5 KiB
C#
using Server.Gumps;
|
|
using Server.Items;
|
|
using Server.Mobiles;
|
|
|
|
namespace Server.Engines.Quests.Doom
|
|
{
|
|
public class CollectBonesObjective : QuestObjective
|
|
{
|
|
public override int Message => 1050026;
|
|
|
|
public override int MaxProgress => 1000;
|
|
|
|
public override void OnComplete()
|
|
{
|
|
var victoria = ((TheSummoningQuest)System).Victoria;
|
|
|
|
if (victoria == null)
|
|
{
|
|
System.From.SendMessage("Internal error: unable to find Victoria. Quest unable to continue.");
|
|
System.Cancel();
|
|
}
|
|
else
|
|
{
|
|
var altar = victoria.Altar;
|
|
|
|
if (altar == null)
|
|
{
|
|
System.From.SendMessage("Internal error: unable to find summoning altar. Quest unable to continue.");
|
|
System.Cancel();
|
|
}
|
|
else if (altar.Daemon?.Alive != true)
|
|
{
|
|
System.AddConversation(new VanquishDaemonConversation());
|
|
}
|
|
else
|
|
{
|
|
victoria.SayTo(
|
|
System.From,
|
|
"The devourer has already been summoned. Return when the devourer has been slain and I will summon it for you."
|
|
);
|
|
((TheSummoningQuest)System).WaitForSummon = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void RenderMessage(ref DynamicGumpBuilder builder)
|
|
{
|
|
if (CurProgress > 0 && CurProgress < MaxProgress)
|
|
{
|
|
// Victoria has accepted the Daemon bones, but the requirement is not yet met.
|
|
BaseQuestGump.AddHtmlObject(ref builder, 70, 130, 300, 100, 1050028, BaseQuestGump.Blue, false, false);
|
|
}
|
|
else
|
|
{
|
|
base.RenderMessage(ref builder);
|
|
}
|
|
}
|
|
|
|
public override void RenderProgress(ref DynamicGumpBuilder builder)
|
|
{
|
|
if (CurProgress > 0 && CurProgress < MaxProgress)
|
|
{
|
|
// Number of bones collected:
|
|
BaseQuestGump.AddHtmlObject(ref builder, 70, 260, 270, 100, 1050019, BaseQuestGump.Blue, false, false);
|
|
|
|
builder.AddLabel(70, 280, 100, $"{CurProgress}");
|
|
builder.AddLabel(100, 280, 100, "/");
|
|
builder.AddLabel(130, 280, 100, $"{MaxProgress}");
|
|
}
|
|
else
|
|
{
|
|
base.RenderProgress(ref builder);
|
|
}
|
|
}
|
|
}
|
|
|
|
public class VanquishDaemonObjective : QuestObjective
|
|
{
|
|
private BoneDemon m_Daemon;
|
|
|
|
public VanquishDaemonObjective(BoneDemon daemon) => m_Daemon = daemon;
|
|
|
|
// Serialization
|
|
public VanquishDaemonObjective()
|
|
{
|
|
}
|
|
|
|
public Corpse CorpseWithSkull { get; set; }
|
|
|
|
public override int Message => 1050037;
|
|
|
|
public override void CheckProgress()
|
|
{
|
|
if (m_Daemon?.Alive != true)
|
|
{
|
|
Complete();
|
|
}
|
|
}
|
|
|
|
public override void OnComplete()
|
|
{
|
|
var victoria = ((TheSummoningQuest)System).Victoria;
|
|
|
|
var altar = victoria?.Altar;
|
|
|
|
altar?.CheckDaemon();
|
|
|
|
var from = System.From;
|
|
|
|
if (!from.Alive)
|
|
{
|
|
// The devourer lies dead, unfortunately so do you. You cannot claim your reward while dead. You will need to face him again.
|
|
from.SendLocalizedMessage(1050033);
|
|
((TheSummoningQuest)System).WaitForSummon = true;
|
|
}
|
|
else
|
|
{
|
|
var hasRights = false;
|
|
|
|
if (m_Daemon != null)
|
|
{
|
|
var lootingRights =
|
|
BaseCreature.GetLootingRights(m_Daemon.DamageEntries, m_Daemon.HitsMax);
|
|
|
|
for (var i = 0; i < lootingRights.Count; ++i)
|
|
{
|
|
var ds = lootingRights[i];
|
|
|
|
if (ds.m_HasRight && ds.m_Mobile == from)
|
|
{
|
|
hasRights = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!hasRights)
|
|
{
|
|
// The devourer lies dead. Unfortunately you did not sufficiently prove your worth in combating the devourer. Victoria shall summon another incarnation of the devourer to the circle of stones. Try again noble adventurer.
|
|
from.SendLocalizedMessage(1050034);
|
|
((TheSummoningQuest)System).WaitForSummon = true;
|
|
}
|
|
else
|
|
{
|
|
from.SendLocalizedMessage(1050035); // The devourer lies dead. Search his corpse to claim your prize!
|
|
|
|
if (m_Daemon != null)
|
|
{
|
|
CorpseWithSkull = m_Daemon.Corpse as Corpse;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void ChildDeserialize(IGenericReader reader)
|
|
{
|
|
var version = reader.ReadEncodedInt();
|
|
|
|
m_Daemon = reader.ReadEntity<BoneDemon>();
|
|
CorpseWithSkull = reader.ReadEntity<Corpse>();
|
|
}
|
|
|
|
public override void ChildSerialize(IGenericWriter writer)
|
|
{
|
|
writer.WriteEncodedInt(0); // version
|
|
|
|
writer.Write(m_Daemon);
|
|
writer.Write(CorpseWithSkull);
|
|
}
|
|
}
|
|
}
|