v0.0.1-Alpha (#42)

This commit is contained in:
Kamron Batman 2019-08-02 18:16:11 -07:00 • committed by GitHub
parent d30f93c454
commit a36796c2c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3552 changed files with 2476 additions and 15223 deletions

View file

@ -0,0 +1,66 @@
using Server.Items;
namespace Server.Mobiles
{
public class Actor : BaseCreature
{
[Constructible]
public Actor() : base(AIType.AI_Animal, FightMode.None, 10, 1, 0.2, 0.4)
{
InitStats(31, 41, 51);
SpeechHue = Utility.RandomDyedHue();
Hue = Race.Human.RandomSkinHue();
if (Female = Utility.RandomBool())
{
Body = 0x191;
Name = NameList.RandomName("female");
AddItem(new FancyDress(Utility.RandomDyedHue()));
Title = "the actress";
}
else
{
Body = 0x190;
Name = NameList.RandomName("male");
AddItem(new LongPants(Utility.RandomNeutralHue()));
AddItem(new FancyShirt(Utility.RandomDyedHue()));
Title = "the actor";
}
AddItem(new Boots(Utility.RandomNeutralHue()));
Utility.AssignRandomHair(this);
Container pack = new Backpack();
pack.DropItem(new Gold(250, 300));
pack.Movable = false;
AddItem(pack);
}
public Actor(Serial serial) : base(serial)
{
}
public override bool ClickTitle => false;
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
}
}
}

View file

@ -0,0 +1,72 @@
using Server.Items;
namespace Server.Mobiles
{
public class Artist : BaseCreature
{
[Constructible]
public Artist()
: base(AIType.AI_Animal, FightMode.None, 10, 1, 0.2, 0.4)
{
InitStats(31, 41, 51);
SetSkill(SkillName.Healing, 36, 68);
SpeechHue = Utility.RandomDyedHue();
Title = "the artist";
Hue = Race.Human.RandomSkinHue();
if (Female = Utility.RandomBool())
{
Body = 0x191;
Name = NameList.RandomName("female");
}
else
{
Body = 0x190;
Name = NameList.RandomName("male");
}
AddItem(new Doublet(Utility.RandomDyedHue()));
AddItem(new Sandals(Utility.RandomNeutralHue()));
AddItem(new ShortPants(Utility.RandomNeutralHue()));
AddItem(new HalfApron(Utility.RandomDyedHue()));
Utility.AssignRandomHair(this);
Container pack = new Backpack();
pack.DropItem(new Gold(250, 300));
pack.Movable = false;
AddItem(pack);
}
public Artist(Serial serial)
: base(serial)
{
}
public override bool CanTeach => true;
public override bool ClickTitle => false;
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
}
}
}

View file

@ -0,0 +1,429 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Server.Accounting;
using Server.ContextMenus;
using Server.Items;
using Server.Network;
namespace Server.Mobiles
{
public class Banker : BaseVendor
{
private List<SBInfo> m_SBInfos = new List<SBInfo>();
[Constructible]
public Banker() : base("the banker")
{
}
public Banker(Serial serial) : base(serial)
{
}
protected override List<SBInfo> SBInfos => m_SBInfos;
public override NpcGuild NpcGuild => NpcGuild.MerchantsGuild;
public override void InitSBInfo()
{
m_SBInfos.Add(new SBBanker());
}
public static int GetBalance(Mobile m)
{
long balance = 0;
if (AccountGold.Enabled && m.Account != null)
{
balance = m.Account.GetTotalGold();
if (balance >= int.MaxValue) return int.MaxValue;
}
Container bank = m.FindBankNoCreate();
if (bank != null)
{
List<Gold> gold = bank.FindItemsByType<Gold>();
List<BankCheck> checks = bank.FindItemsByType<BankCheck>();
balance += gold.Aggregate(0L, (c, t) => c + t.Amount);
if (balance >= int.MaxValue)
return int.MaxValue;
balance += checks.Aggregate(0L, (c, t) => c + t.Worth);
}
return Math.Max(0, (int)Math.Min(int.MaxValue, balance));
}
public static int GetBalance(Mobile m, out Item[] gold, out Item[] checks)
{
long balance = 0;
if (AccountGold.Enabled && m.Account != null)
{
balance = m.Account.GetTotalGold();
if (balance > int.MaxValue)
{
gold = checks = new Item[0];
return int.MaxValue;
}
}
Container bank = m.FindBankNoCreate();
if (bank != null)
{
gold = bank.FindItemsByType(typeof(Gold));
checks = bank.FindItemsByType(typeof(BankCheck));
balance += gold.OfType<Gold>().Aggregate(0L, (c, t) => c + t.Amount);
if (balance >= int.MaxValue) return int.MaxValue;
balance += checks.OfType<BankCheck>().Aggregate(0L, (c, t) => c + t.Worth);
}
else
{
gold = checks = new Item[0];
}
return Math.Max(0, (int)Math.Min(int.MaxValue, balance));
}
public static bool Withdraw(Mobile from, int amount)
{
// If for whatever reason the TOL checks fail, we should still try old methods for withdrawing currency.
if (AccountGold.Enabled && from.Account != null && from.Account.WithdrawGold(amount)) return true;
int balance = GetBalance(from, out Item[] gold, out Item[] checks);
if (balance < amount) return false;
for (int i = 0; amount > 0 && i < gold.Length; ++i)
if (gold[i].Amount <= amount)
{
amount -= gold[i].Amount;
gold[i].Delete();
}
else
{
gold[i].Amount -= amount;
amount = 0;
}
for (int i = 0; amount > 0 && i < checks.Length; ++i)
{
BankCheck check = (BankCheck)checks[i];
if (check.Worth <= amount)
{
amount -= check.Worth;
check.Delete();
}
else
{
check.Worth -= amount;
amount = 0;
}
}
return true;
}
public static bool Deposit(Mobile from, int amount)
{
// If for whatever reason the TOL checks fail, we should still try old methods for depositing currency.
if (AccountGold.Enabled && from.Account != null && from.Account.DepositGold(amount)) return true;
BankBox box = from.FindBankNoCreate();
if (box == null) return false;
List<Item> items = new List<Item>();
while (amount > 0)
{
Item item;
if (amount < 5000)
{
item = new Gold(amount);
amount = 0;
}
else if (amount <= 1000000)
{
item = new BankCheck(amount);
amount = 0;
}
else
{
item = new BankCheck(1000000);
amount -= 1000000;
}
if (box.TryDropItem(from, item, false))
{
items.Add(item);
}
else
{
item.Delete();
foreach (Item curItem in items) curItem.Delete();
return false;
}
}
return true;
}
public static int DepositUpTo(Mobile from, int amount)
{
// If for whatever reason the TOL checks fail, we should still try old methods for depositing currency.
if (AccountGold.Enabled && from.Account != null && from.Account.DepositGold(amount)) return amount;
BankBox box = from.FindBankNoCreate();
if (box == null) return 0;
int amountLeft = amount;
while (amountLeft > 0)
{
Item item;
int amountGiven;
if (amountLeft < 5000)
{
item = new Gold(amountLeft);
amountGiven = amountLeft;
}
else if (amountLeft <= 1000000)
{
item = new BankCheck(amountLeft);
amountGiven = amountLeft;
}
else
{
item = new BankCheck(1000000);
amountGiven = 1000000;
}
if (box.TryDropItem(from, item, false))
{
amountLeft -= amountGiven;
}
else
{
item.Delete();
break;
}
}
return amount - amountLeft;
}
public static void Deposit(Container cont, int amount)
{
while (amount > 0)
{
Item item;
if (amount < 5000)
{
item = new Gold(amount);
amount = 0;
}
else if (amount <= 1000000)
{
item = new BankCheck(amount);
amount = 0;
}
else
{
item = new BankCheck(1000000);
amount -= 1000000;
}
cont.DropItem(item);
}
}
public override bool HandlesOnSpeech(Mobile from)
{
if (from.InRange(Location, 12))
return true;
return base.HandlesOnSpeech(from);
}
public override void OnSpeech(SpeechEventArgs e)
{
if (!e.Handled && e.Mobile.InRange(Location, 12))
for (int i = 0; i < e.Keywords.Length; ++i)
{
int keyword = e.Keywords[i];
switch (keyword)
{
case 0x0000: // *withdraw*
{
e.Handled = true;
if (e.Mobile.Criminal)
{
Say(500389); // I will not do business with a criminal!
break;
}
string[] split = e.Speech.Split(' ');
if (split.Length >= 2)
{
Container pack = e.Mobile.Backpack;
if (!int.TryParse(split[1], out int amount))
break;
if (!Core.ML && amount > 5000 || Core.ML && amount > 60000)
{
Say(500381); // Thou canst not withdraw so much at one time!
}
else if (pack?.Deleted != false || !(pack.TotalWeight < pack.MaxWeight) ||
!(pack.TotalItems < pack.MaxItems))
{
Say(1048147); // Your backpack can't hold anything else.
}
else if (amount > 0)
{
BankBox box = e.Mobile.FindBankNoCreate();
if (box == null || !Withdraw(e.Mobile, amount))
{
Say(500384); // Ah, art thou trying to fool me? Thou hast not so much gold!
}
else
{
pack.DropItem(new Gold(amount));
Say(1010005); // Thou hast withdrawn gold from thy account.
}
}
}
break;
}
case 0x0001: // *balance*
{
e.Handled = true;
if (e.Mobile.Criminal)
{
Say(500389); // I will not do business with a criminal!
break;
}
if (AccountGold.Enabled && e.Mobile.Account != null)
Say(1155855,
$"{e.Mobile.Account.TotalPlat:#,0}\t{e.Mobile.Account.TotalGold:#,0}"); // Thy current bank balance is ~1_AMOUNT~ platinum and ~2_AMOUNT~ gold.
else
Say(1042759,
GetBalance(e.Mobile).ToString("#,0")); // Thy current bank balance is ~1_AMOUNT~ gold.
break;
}
case 0x0002: // *bank*
{
e.Handled = true;
if (e.Mobile.Criminal)
{
Say(500378); // Thou art a criminal and cannot access thy bank box.
break;
}
e.Mobile.BankBox.Open();
break;
}
case 0x0003: // *check*
{
e.Handled = true;
if (AccountGold.Enabled)
break;
if (e.Mobile.Criminal)
{
Say(500389); // I will not do business with a criminal!
break;
}
string[] split = e.Speech.Split(' ');
if (split.Length >= 2)
{
if (!int.TryParse(split[1], out int amount))
break;
if (amount < 5000)
{
Say(1010006); // We cannot create checks for such a paltry amount of gold!
}
else if (amount > 1000000)
{
Say(1010007); // Our policies prevent us from creating checks worth that much!
}
else
{
BankCheck check = new BankCheck(amount);
BankBox box = e.Mobile.BankBox;
if (!box.TryDropItem(e.Mobile, check, false))
{
Say(500386); // There's not enough room in your bankbox for the check!
check.Delete();
}
else if (!box.ConsumeTotal(typeof(Gold), amount))
{
Say(500384); // Ah, art thou trying to fool me? Thou hast not so much gold!
check.Delete();
}
else
{
Say(1042673, AffixType.Append, amount.ToString(),
""); // Into your bank box I have placed a check in the amount of:
}
}
}
break;
}
}
}
base.OnSpeech(e);
}
public override void AddCustomContextEntries(Mobile from, List<ContextMenuEntry> list)
{
if (from.Alive)
list.Add(new OpenBankEntry(from, this));
base.AddCustomContextEntries(from, list);
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
}
}
}

View file

@ -0,0 +1,743 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Server.ContextMenus;
using Server.Engines.MLQuests;
using Server.Engines.MLQuests.Definitions;
using Server.Engines.MLQuests.Objectives;
using Server.Items;
using Server.Misc;
using Server.Regions;
using EDI = Server.Mobiles.EscortDestinationInfo;
namespace Server.Mobiles
{
public class BaseEscortable : BaseCreature
{
public static readonly TimeSpan EscortDelay = TimeSpan.FromMinutes(5.0);
public static readonly TimeSpan AbandonDelay =
MLQuestSystem.Enabled ? TimeSpan.FromMinutes(1.0) : TimeSpan.FromMinutes(2.0);
public static readonly TimeSpan DeleteTime =
MLQuestSystem.Enabled ? TimeSpan.FromSeconds(100) : TimeSpan.FromSeconds(30);
// Classic list
// Used when: !MLQuestSystem.Enabled && !Core.ML
private static string[] m_TownNames =
{
"Cove", "Britain", "Jhelom",
"Minoc", "Ocllo", "Trinsic",
"Vesper", "Yew", "Skara Brae",
"Nujel'm", "Moonglow", "Magincia"
};
// ML list, pre-ML quest system
// Used when: !MLQuestSystem.Enabled && Core.ML
private static string[] m_MLTownNames =
{
"Cove", "Serpent's Hold", "Jhelom",
"Nujel'm"
};
// ML quest system general list
// Used when: MLQuestSystem.Enabled && !Region.IsPartOf( "Haven Island" )
private static Type[] m_MLQuestTypes =
{
typeof(EscortToYew),
typeof(EscortToVesper),
typeof(EscortToTrinsic),
typeof(EscortToSkaraBrae),
typeof(EscortToSerpentsHold),
typeof(EscortToNujelm),
typeof(EscortToMoonglow),
typeof(EscortToMinoc),
typeof(EscortToMagincia),
typeof(EscortToJhelom),
typeof(EscortToCove),
typeof(EscortToBritain)
// Ocllo was removed in pub 56
//typeof( EscortToOcllo )
};
// ML quest system New Haven list
// Used when: MLQuestSystem.Enabled && Region.IsPartOf( "Haven Island" )
private static Type[] m_MLQuestTypesNH =
{
typeof(EscortToNHAlchemist),
typeof(EscortToNHBard),
typeof(EscortToNHWarrior),
typeof(EscortToNHTailor),
typeof(EscortToNHCarpenter),
typeof(EscortToNHMapmaker),
typeof(EscortToNHMage),
typeof(EscortToNHInn),
// Farm destination was removed
//typeof( EscortToNHFarm ),
typeof(EscortToNHDocks),
typeof(EscortToNHBowyer),
typeof(EscortToNHBank)
};
private bool m_DeleteCorpse;
private DateTime m_DeleteTime;
private Timer m_DeleteTimer;
private EDI m_Destination;
private string m_DestinationString;
private DateTime m_LastSeenEscorter;
private MLQuest m_MLQuest;
[Constructible]
public BaseEscortable()
: base(AIType.AI_Melee, FightMode.Aggressor, 22, 1, 0.2, 1.0)
{
InitBody();
InitOutfit();
Fame = 200;
Karma = 4000;
}
public BaseEscortable(Serial serial)
: base(serial)
{
}
public override bool StaticMLQuester => false; // Suppress automatic quest registration on creation/deserialization
public override bool CanShout => !Controlled && !IsBeingDeleted;
public bool IsBeingDeleted => m_DeleteTimer != null;
public override bool Commandable => false; // Our master cannot boss us around!
public override bool DeleteCorpseOnDeath => m_DeleteCorpse;
[CommandProperty(AccessLevel.GameMaster)]
public string Destination
{
get => m_Destination?.Name;
set
{
m_DestinationString = value;
m_Destination = EDI.Find(value);
}
}
public static Dictionary<Mobile, BaseEscortable> EscortTable{ get; } = new Dictionary<Mobile, BaseEscortable>();
protected override List<MLQuest> ConstructQuestList()
{
if (m_MLQuest == null)
{
Region reg = Region;
Type[] list = reg.IsPartOf("Haven Island") ? m_MLQuestTypesNH : m_MLQuestTypes;
int randomIdx = Utility.Random(list.Length);
for (int i = 0; i < list.Length; ++i)
{
Type questType = list[randomIdx];
MLQuest quest = MLQuestSystem.FindQuest(questType);
if (quest != null)
{
bool okay = true;
foreach (BaseObjective obj in quest.Objectives)
if (obj is EscortObjective objective && objective.Destination.Contains(reg))
{
okay = false; // We're already there!
break;
}
if (okay)
{
m_MLQuest = quest;
break;
}
}
else if (MLQuestSystem.Debug)
{
Console.WriteLine("Warning: Escortable cannot be assigned quest type '{0}', it is not registered",
questType.Name);
}
randomIdx = (randomIdx + 1) % list.Length;
}
if (m_MLQuest == null)
{
if (MLQuestSystem.Debug)
Console.WriteLine("Warning: No suitable quest found for escort {0}", Serial);
return null;
}
}
List<MLQuest> result = new List<MLQuest> { m_MLQuest };
return result;
}
public override void Shout(PlayerMobile pm)
{
/*
* 1072301 - You there! Care to hear how to earn some easy gold?
* 1072302 - Adventurer! I have an offer for you.
* 1072303 - Wait! I have an opportunity for you to make some gold!
*/
MLQuestSystem.Tell(this, pm, Utility.Random(1072301, 3));
}
public virtual void InitBody()
{
SetStr(90, 100);
SetDex(90, 100);
SetInt(15, 25);
Hue = Race.Human.RandomSkinHue();
if (Female = Utility.RandomBool())
{
Body = 401;
Name = NameList.RandomName("female");
}
else
{
Body = 400;
Name = NameList.RandomName("male");
}
}
public virtual void InitOutfit()
{
AddItem(new FancyShirt(Utility.RandomNeutralHue()));
AddItem(new ShortPants(Utility.RandomNeutralHue()));
AddItem(new Boots(Utility.RandomNeutralHue()));
Utility.AssignRandomHair(this);
PackGold(200, 250);
}
public virtual bool SayDestinationTo(Mobile m)
{
EDI dest = GetDestination();
if (dest == null || !m.Alive)
return false;
Mobile escorter = GetEscorter();
if (escorter == null)
{
Say("I am looking to go to {0}, will you take me?",
dest.Name == "Ocllo" && m.Map == Map.Trammel ? "Haven" : dest.Name);
return true;
}
if (escorter == m)
{
Say("Lead on! Payment will be made when we arrive in {0}.",
dest.Name == "Ocllo" && m.Map == Map.Trammel ? "Haven" : dest.Name);
return true;
}
return false;
}
public virtual bool AcceptEscorter(Mobile m)
{
EDI dest = GetDestination();
if (dest == null)
return false;
if (GetEscorter() != null || !m.Alive)
return false;
if (EscortTable.TryGetValue(m, out BaseEscortable escortable) && escortable?.Deleted == false && escortable.GetEscorter() == m)
{
Say("I see you already have an escort.");
return false;
}
if (m is PlayerMobile mobile && mobile.LastEscortTime + EscortDelay >= DateTime.UtcNow)
{
int minutes =
(int)Math.Ceiling((mobile.LastEscortTime + EscortDelay - DateTime.UtcNow).TotalMinutes);
Say("You must rest {0} minute{1} before we set out on this journey.", minutes, minutes == 1 ? "" : "s");
return false;
}
if (SetControlMaster(m))
{
m_LastSeenEscorter = DateTime.UtcNow;
if (m is PlayerMobile playerMobile)
playerMobile.LastEscortTime = DateTime.UtcNow;
Say("Lead on! Payment will be made when we arrive in {0}.",
dest.Name == "Ocllo" && m.Map == Map.Trammel ? "Haven" : dest.Name);
EscortTable[m] = this;
StartFollow();
return true;
}
return false;
}
public override bool HandlesOnSpeech(Mobile from)
{
return !MLQuestSystem.Enabled && (from.InRange(Location, 3) || base.HandlesOnSpeech(from));
}
public override void OnSpeech(SpeechEventArgs e)
{
base.OnSpeech(e);
if (GetDestination() == null || e.Handled || !e.Mobile.InRange(Location, 3))
return;
if (e.HasKeyword(0x1D)) // *destination*
e.Handled = SayDestinationTo(e.Mobile);
else if (e.HasKeyword(0x1E)) // *i will take thee*
e.Handled = AcceptEscorter(e.Mobile);
}
public override void OnAfterDelete()
{
m_DeleteTimer?.Stop();
m_DeleteTimer = null;
base.OnAfterDelete();
}
public override void OnThink()
{
base.OnThink();
CheckAtDestination();
}
protected override bool OnMove(Direction d)
{
if (!base.OnMove(d))
return false;
CheckAtDestination();
return true;
}
// TODO: Pre-ML methods below, might be mergeable with the ML methods in EscortObjective
public virtual void StartFollow()
{
StartFollow(GetEscorter());
}
public virtual void StartFollow(Mobile escorter)
{
if (escorter == null)
return;
ActiveSpeed = 0.1;
PassiveSpeed = 0.2;
ControlOrder = OrderType.Follow;
ControlTarget = escorter;
if (IsPrisoner && CantWalk) CantWalk = false;
CurrentSpeed = 0.1;
}
public virtual void StopFollow()
{
ActiveSpeed = 0.2;
PassiveSpeed = 1.0;
ControlOrder = OrderType.None;
ControlTarget = null;
CurrentSpeed = 1.0;
}
public virtual Mobile GetEscorter()
{
if (!Controlled)
return null;
Mobile master = ControlMaster;
if (MLQuestSystem.Enabled || master == null)
return master;
if (master.Deleted || master.Map != Map || !master.InRange(Location, 30) || !master.Alive)
{
StopFollow();
TimeSpan lastSeenDelay = DateTime.UtcNow - m_LastSeenEscorter;
if (lastSeenDelay >= AbandonDelay)
{
master.SendLocalizedMessage(1042473); // You have lost the person you were escorting.
Say(1005653); // Hmmm. I seem to have lost my master.
SetControlMaster(null);
EscortTable.Remove(master);
Timer.DelayCall(TimeSpan.FromSeconds(5.0), Delete);
return null;
}
ControlOrder = OrderType.Stay;
return master;
}
if (ControlOrder != OrderType.Follow)
StartFollow(master);
m_LastSeenEscorter = DateTime.UtcNow;
return master;
}
public virtual void BeginDelete()
{
m_DeleteTimer?.Stop();
m_DeleteTime = DateTime.UtcNow + DeleteTime;
m_DeleteTimer = new DeleteTimer(this, m_DeleteTime - DateTime.UtcNow);
m_DeleteTimer.Start();
}
public virtual bool CheckAtDestination()
{
if (MLQuestSystem.Enabled)
return false;
EDI dest = GetDestination();
if (dest == null)
return false;
Mobile escorter = GetEscorter();
if (escorter == null)
return false;
if (dest.Contains(Location))
{
Say(1042809,
escorter.Name); // We have arrived! I thank thee, ~1_PLAYER_NAME~! I have no further need of thy services. Here is thy pay.
// not going anywhere
m_Destination = null;
m_DestinationString = null;
Container cont = escorter.Backpack ?? escorter.BankBox;
Gold gold = new Gold(500, 1000);
if (!cont.TryDropItem(escorter, gold, false))
gold.MoveToWorld(escorter.Location, escorter.Map);
StopFollow();
SetControlMaster(null);
EscortTable.Remove(escorter);
BeginDelete();
Titles.AwardFame(escorter, 10, true);
bool gainedPath = false;
if (escorter is PlayerMobile pm)
{
if (pm.CompassionGains > 0 && DateTime.UtcNow > pm.NextCompassionDay)
{
pm.NextCompassionDay = DateTime.MinValue;
pm.CompassionGains = 0;
}
if (pm.CompassionGains >= 5) // have already gained 5 times in one day, can gain no more
{
pm.SendLocalizedMessage(
1053004); // You must wait about a day before you can gain in compassion again.
}
else if (VirtueHelper.Award(pm, VirtueName.Compassion, IsPrisoner ? 400 : 200, ref gainedPath))
{
if (gainedPath)
pm.SendLocalizedMessage(1053005); // You have achieved a path in compassion!
else
pm.SendLocalizedMessage(1053002); // You have gained in compassion.
pm.NextCompassionDay =
DateTime.UtcNow + TimeSpan.FromDays(1.0); // in one day CompassionGains gets reset to 0
++pm.CompassionGains;
if (pm.CompassionGains >= 5)
pm.SendLocalizedMessage(
1053004); // You must wait about a day before you can gain in compassion again.
}
else
{
pm.SendLocalizedMessage(
1053003); // You have achieved the highest path of compassion and can no longer gain any further.
}
}
return true;
}
return false;
}
public override bool OnBeforeDeath()
{
m_DeleteCorpse = Controlled || IsBeingDeleted;
return base.OnBeforeDeath();
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(1); // version
EDI dest = GetDestination();
writer.Write(dest != null);
if (dest != null)
writer.Write(dest.Name);
writer.Write(m_DeleteTimer != null);
if (m_DeleteTimer != null)
writer.WriteDeltaTime(m_DeleteTime);
MLQuestSystem.WriteQuestRef(writer, StaticMLQuester ? null : m_MLQuest);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
if (reader.ReadBool())
m_DestinationString =
reader.ReadString(); // NOTE: We cannot EDI.Find here, regions have not yet been loaded :-(
if (reader.ReadBool())
{
m_DeleteTime = reader.ReadDeltaTime();
m_DeleteTimer = new DeleteTimer(this, m_DeleteTime - DateTime.UtcNow);
m_DeleteTimer.Start();
}
if (version >= 1)
{
MLQuest quest = MLQuestSystem.ReadQuestRef(reader);
if (MLQuestSystem.Enabled && quest != null && !StaticMLQuester)
m_MLQuest = quest;
}
}
public override bool CanBeRenamedBy(Mobile from)
{
return from.AccessLevel >= AccessLevel.GameMaster;
}
public override void AddCustomContextEntries(Mobile from, List<ContextMenuEntry> list)
{
if (from.Alive)
{
Mobile escorter = GetEscorter();
if (!MLQuestSystem.Enabled && GetDestination() != null)
{
if (escorter == null || escorter == from)
list.Add(new AskDestinationEntry(this, from));
if (escorter == null)
list.Add(new AcceptEscortEntry(this, from));
}
if (escorter == from)
list.Add(new AbandonEscortEntry(this));
}
base.AddCustomContextEntries(from, list);
}
public virtual string[] GetPossibleDestinations()
{
return Core.ML ? m_MLTownNames : m_TownNames;
}
public virtual string PickRandomDestination()
{
if (Map.Felucca.Regions.Count == 0 || Map == null || Map == Map.Internal || Location == Point3D.Zero)
return null; // Not yet fully initialized
string[] possible = GetPossibleDestinations();
string picked = null;
while (picked == null)
{
picked = possible[Utility.Random(possible.Length)];
EDI test = EDI.Find(picked);
if (test.Contains(Location))
picked = null;
}
return picked;
}
public EDI GetDestination()
{
if (MLQuestSystem.Enabled)
return null;
if (m_DestinationString == null && m_DeleteTimer == null)
m_DestinationString = PickRandomDestination();
if (m_Destination != null && m_Destination.Name == m_DestinationString)
return m_Destination;
if (Map.Felucca.Regions.Count > 0)
return m_Destination = EDI.Find(m_DestinationString);
return m_Destination = null;
}
private class DeleteTimer : Timer
{
private Mobile m_Mobile;
public DeleteTimer(Mobile m, TimeSpan delay)
: base(delay)
{
m_Mobile = m;
Priority = TimerPriority.OneSecond;
}
protected override void OnTick()
{
m_Mobile.Delete();
}
}
}
public class EscortDestinationInfo
{
private static Dictionary<string, EscortDestinationInfo> m_Table;
public EscortDestinationInfo(string name, Region region)
{
Name = name;
Region = region;
}
public string Name{ get; }
public Region Region{ get; }
public bool Contains(Point3D p)
{
return Region.Contains(p);
}
public static void LoadTable()
{
ICollection list = Map.Felucca.Regions.Values;
if (list.Count == 0)
return;
m_Table = new Dictionary<string, EscortDestinationInfo>();
foreach (Region r in list)
{
if (r.Name != null && (r is DungeonRegion || r is TownRegion))
m_Table[r.Name] = new EscortDestinationInfo(r.Name, r);
}
}
public static EDI Find(string name)
{
if (m_Table == null)
LoadTable();
if (name == null || m_Table == null)
return null;
m_Table.TryGetValue(name, out EscortDestinationInfo info);
return info;
}
}
public class AskDestinationEntry : ContextMenuEntry
{
private Mobile m_From;
private BaseEscortable m_Mobile;
public AskDestinationEntry(BaseEscortable m, Mobile from)
: base(6100, 3)
{
m_Mobile = m;
m_From = from;
}
public override void OnClick()
{
m_Mobile.SayDestinationTo(m_From);
}
}
public class AcceptEscortEntry : ContextMenuEntry
{
private Mobile m_From;
private BaseEscortable m_Mobile;
public AcceptEscortEntry(BaseEscortable m, Mobile from)
: base(6101, 3)
{
m_Mobile = m;
m_From = from;
}
public override void OnClick()
{
m_Mobile.AcceptEscorter(m_From);
}
}
public class AbandonEscortEntry : ContextMenuEntry
{
private BaseEscortable m_Mobile;
public AbandonEscortEntry(BaseEscortable m)
: base(6102, 3)
{
m_Mobile = m;
}
public override void OnClick()
{
m_Mobile.Delete(); // OSI just seems to delete instantly
}
}
}

View file

@ -0,0 +1,77 @@
using Server.Items;
namespace Server.Mobiles
{
public class BrideGroom : BaseEscortable
{
[Constructible]
public BrideGroom()
{
if (Female)
Title = "the bride";
else
Title = "the groom";
}
public BrideGroom(Serial serial) : base(serial)
{
}
public override bool CanTeach => true;
public override bool ClickTitle => false; // Do not display 'the groom' when single-clicking
private static int GetRandomHue()
{
switch (Utility.Random(6))
{
default:
case 0: return 0;
case 1: return Utility.RandomBlueHue();
case 2: return Utility.RandomGreenHue();
case 3: return Utility.RandomRedHue();
case 4: return Utility.RandomYellowHue();
case 5: return Utility.RandomNeutralHue();
}
}
public override void InitOutfit()
{
if (Female)
AddItem(new FancyDress());
else
AddItem(new FancyShirt());
int lowHue = GetRandomHue();
AddItem(new LongPants(lowHue));
if (Female)
AddItem(new Shoes(lowHue));
else
AddItem(new Boots(lowHue));
if (Utility.RandomBool())
HairItemID = 0x203B;
else
HairItemID = 0x203C;
HairHue = Race.RandomHairHue();
PackGold(200, 250);
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
}
}
}

View file

@ -0,0 +1,71 @@
using Server.Items;
namespace Server.Mobiles
{
public class EscortableMage : BaseEscortable
{
[Constructible]
public EscortableMage()
{
Title = "the mage";
SetSkill(SkillName.EvalInt, 80.0, 100.0);
SetSkill(SkillName.Inscribe, 80.0, 100.0);
SetSkill(SkillName.Magery, 80.0, 100.0);
SetSkill(SkillName.Meditation, 80.0, 100.0);
SetSkill(SkillName.MagicResist, 80.0, 100.0);
}
public EscortableMage(Serial serial) : base(serial)
{
}
public override bool CanTeach => true;
public override bool ClickTitle => false; // Do not display 'the mage' when single-clicking
private static int GetRandomHue()
{
switch (Utility.Random(5))
{
default:
case 0: return Utility.RandomBlueHue();
case 1: return Utility.RandomGreenHue();
case 2: return Utility.RandomRedHue();
case 3: return Utility.RandomYellowHue();
case 4: return Utility.RandomNeutralHue();
}
}
public override void InitOutfit()
{
AddItem(new Robe(GetRandomHue()));
int lowHue = GetRandomHue();
AddItem(new ShortPants(lowHue));
if (Female)
AddItem(new ThighBoots(lowHue));
else
AddItem(new Boots(lowHue));
Utility.AssignRandomHair(this);
PackGold(200, 250);
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
}
}
}

View file

@ -0,0 +1,77 @@
using Server.Items;
namespace Server.Mobiles
{
public class Gypsy : BaseCreature
{
[Constructible]
public Gypsy()
: base(AIType.AI_Animal, FightMode.None, 10, 1, 0.2, 0.4)
{
InitStats(31, 41, 51);
SpeechHue = Utility.RandomDyedHue();
SetSkill(SkillName.Cooking, 65, 88);
SetSkill(SkillName.Snooping, 65, 88);
SetSkill(SkillName.Stealing, 65, 88);
Hue = Race.Human.RandomSkinHue();
if (Female = Utility.RandomBool())
{
Body = 0x191;
Name = NameList.RandomName("female");
AddItem(new Kilt(Utility.RandomDyedHue()));
AddItem(new Shirt(Utility.RandomDyedHue()));
AddItem(new ThighBoots());
Title = "the gypsy";
}
else
{
Body = 0x190;
Name = NameList.RandomName("male");
AddItem(new ShortPants(Utility.RandomNeutralHue()));
AddItem(new Shirt(Utility.RandomDyedHue()));
AddItem(new Sandals());
Title = "the gypsy";
}
AddItem(new Bandana(Utility.RandomDyedHue()));
AddItem(new Dagger());
Utility.AssignRandomHair(this);
Container pack = new Backpack();
pack.DropItem(new Gold(250, 300));
pack.Movable = false;
AddItem(pack);
}
public Gypsy(Serial serial)
: base(serial)
{
}
public override bool CanTeach => true;
public override bool ClickTitle => false;
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
}
}
}

View file

@ -0,0 +1,74 @@
using Server.Items;
namespace Server.Mobiles
{
public class HarborMaster : BaseCreature
{
[Constructible]
public HarborMaster()
: base(AIType.AI_Animal, FightMode.None, 10, 1, 0.2, 0.4)
{
InitStats(31, 41, 51);
SetSkill(SkillName.Mining, 36, 68);
SpeechHue = Utility.RandomDyedHue();
Hue = Race.Human.RandomSkinHue();
Blessed = true;
if (Female = Utility.RandomBool())
{
Body = 0x191;
Name = NameList.RandomName("female");
Title = "the Harbor Mistress";
}
else
{
Body = 0x190;
Name = NameList.RandomName("male");
Title = "the Harbor Master";
}
AddItem(new Shirt(Utility.RandomDyedHue()));
AddItem(new Boots());
AddItem(new LongPants(Utility.RandomNeutralHue()));
AddItem(new QuarterStaff());
Utility.AssignRandomHair(this);
Container pack = new Backpack();
pack.DropItem(new Gold(250, 300));
pack.Movable = false;
AddItem(pack);
}
public HarborMaster(Serial serial)
: base(serial)
{
}
public override bool CanTeach => false;
public override bool ClickTitle => false;
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
}
}
}

View file

@ -0,0 +1,80 @@
using Server.Items;
namespace Server.Mobiles
{
public class Merchant : BaseEscortable
{
[Constructible]
public Merchant()
{
Title = "the merchant";
SetSkill(SkillName.ItemID, 55.0, 78.0);
SetSkill(SkillName.ArmsLore, 55, 78);
}
public Merchant(Serial serial)
: base(serial)
{
}
public override bool CanTeach => true;
public override bool ClickTitle => false; // Do not display 'the merchant' when single-clicking
private static int GetRandomHue()
{
switch (Utility.Random(6))
{
default:
case 0: return 0;
case 1: return Utility.RandomBlueHue();
case 2: return Utility.RandomGreenHue();
case 3: return Utility.RandomRedHue();
case 4: return Utility.RandomYellowHue();
case 5: return Utility.RandomNeutralHue();
}
}
public override void InitOutfit()
{
if (Female)
AddItem(new PlainDress());
else
AddItem(new Shirt(GetRandomHue()));
int lowHue = GetRandomHue();
AddItem(new ThighBoots());
if (Female)
AddItem(new FancyDress(lowHue));
else
AddItem(new FancyShirt(lowHue));
AddItem(new LongPants(lowHue));
if (!Female)
AddItem(new BodySash(lowHue));
//if ( !Female )
//AddItem( new Longsword() );
Utility.AssignRandomHair(this);
PackGold(200, 250);
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
}
}
}

View file

@ -0,0 +1,72 @@
using Server.Items;
namespace Server.Mobiles
{
public class Messenger : BaseEscortable
{
[Constructible]
public Messenger()
{
Title = "the messenger";
}
public Messenger(Serial serial) : base(serial)
{
}
public override bool CanTeach => true;
public override bool ClickTitle => false; // Do not display 'the messenger' when single-clicking
private static int GetRandomHue()
{
switch (Utility.Random(6))
{
default:
case 0: return 0;
case 1: return Utility.RandomBlueHue();
case 2: return Utility.RandomGreenHue();
case 3: return Utility.RandomRedHue();
case 4: return Utility.RandomYellowHue();
case 5: return Utility.RandomNeutralHue();
}
}
public override void InitOutfit()
{
if (Female)
AddItem(new PlainDress());
else
AddItem(new Shirt(GetRandomHue()));
int lowHue = GetRandomHue();
AddItem(new ShortPants(lowHue));
if (Female)
AddItem(new Boots(lowHue));
else
AddItem(new Shoes(lowHue));
int randomHair = Utility.Random(4);
HairItemID = randomHair == 4 ? 0x203B : 0x2048 + randomHair;
HairHue = Race.RandomHairHue();
PackGold(200, 250);
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
}
}
}

View file

@ -0,0 +1,31 @@
namespace Server.Mobiles
{
public class Minter : Banker
{
[Constructible]
public Minter()
{
Title = "the minter";
}
public Minter(Serial serial) : base(serial)
{
}
public override NpcGuild NpcGuild => NpcGuild.MerchantsGuild;
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
}
}
}

View file

@ -0,0 +1,76 @@
using Server.Items;
namespace Server.Mobiles
{
public class Ninja : BaseCreature
{
[Constructible]
public Ninja() : base(AIType.AI_Melee, FightMode.Aggressor, 10, 1, 0.2, 0.4)
{
Title = "the ninja";
InitStats(100, 100, 25);
SetSkill(SkillName.Fencing, 64.0, 80.0);
SetSkill(SkillName.Macing, 64.0, 80.0);
SetSkill(SkillName.Ninjitsu, 60.0, 80.0);
SetSkill(SkillName.Parry, 64.0, 80.0);
SetSkill(SkillName.Tactics, 64.0, 85.0);
SetSkill(SkillName.Swords, 64.0, 85.0);
SpeechHue = Utility.RandomDyedHue();
Hue = Race.Human.RandomSkinHue();
if (Female = Utility.RandomBool())
{
Body = 0x191;
Name = NameList.RandomName("female");
}
else
{
Body = 0x190;
Name = NameList.RandomName("male");
}
if (!Female)
AddItem(new LeatherNinjaHood());
AddItem(new LeatherNinjaPants());
AddItem(new LeatherNinjaBelt());
AddItem(new LeatherNinjaJacket());
AddItem(new NinjaTabi());
int hairHue = Utility.RandomNondyedHue();
Utility.AssignRandomHair(this, hairHue);
if (Utility.Random(7) != 0)
Utility.AssignRandomFacialHair(this, hairHue);
PackGold(250, 300);
}
public Ninja(Serial serial) : base(serial)
{
}
public override bool CanTeach => true;
public override bool ClickTitle => false;
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.WriteEncodedInt(0); // version
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadEncodedInt();
}
}
}

View file

@ -0,0 +1,81 @@
using Server.Items;
namespace Server.Mobiles
{
public class Noble : BaseEscortable
{
[Constructible]
public Noble()
{
Title = "the noble";
SetSkill(SkillName.Parry, 80.0, 100.0);
SetSkill(SkillName.Swords, 80.0, 100.0);
SetSkill(SkillName.Tactics, 80.0, 100.0);
}
public Noble(Serial serial) : base(serial)
{
}
public override bool CanTeach => true;
public override bool ClickTitle => false; // Do not display 'the noble' when single-clicking
private static int GetRandomHue()
{
switch (Utility.Random(6))
{
default:
case 0: return 0;
case 1: return Utility.RandomBlueHue();
case 2: return Utility.RandomGreenHue();
case 3: return Utility.RandomRedHue();
case 4: return Utility.RandomYellowHue();
case 5: return Utility.RandomNeutralHue();
}
}
public override void InitOutfit()
{
if (Female)
AddItem(new FancyDress());
else
AddItem(new FancyShirt(GetRandomHue()));
int lowHue = GetRandomHue();
AddItem(new ShortPants(lowHue));
if (Female)
AddItem(new ThighBoots(lowHue));
else
AddItem(new Boots(lowHue));
if (!Female)
AddItem(new BodySash(lowHue));
AddItem(new Cloak(GetRandomHue()));
if (!Female)
AddItem(new Longsword());
Utility.AssignRandomHair(this);
PackGold(200, 250);
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
}
}
}

View file

@ -0,0 +1,77 @@
using Server.Items;
namespace Server.Mobiles
{
public class Peasant : BaseEscortable
{
[Constructible]
public Peasant()
{
Title = "the peasant";
}
public Peasant(Serial serial) : base(serial)
{
}
public override bool CanTeach => true;
public override bool ClickTitle => false; // Do not display 'the peasant' when single-clicking
private static int GetRandomHue()
{
switch (Utility.Random(6))
{
default:
case 0: return 0;
case 1: return Utility.RandomBlueHue();
case 2: return Utility.RandomGreenHue();
case 3: return Utility.RandomRedHue();
case 4: return Utility.RandomYellowHue();
case 5: return Utility.RandomNeutralHue();
}
}
public override void InitOutfit()
{
if (Female)
AddItem(new PlainDress());
else
AddItem(new Shirt(GetRandomHue()));
int lowHue = GetRandomHue();
AddItem(new ShortPants(lowHue));
if (Female)
AddItem(new Boots(lowHue));
else
AddItem(new Shoes(lowHue));
//if ( !Female )
//AddItem( new BodySash( lowHue ) );
//AddItem( new Cloak( GetRandomHue() ) );
//if ( !Female )
//AddItem( new Longsword() );
Utility.AssignRandomHair(this);
PackGold(200, 250);
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
}
}
}

View file

@ -0,0 +1,82 @@
using Server.Engines.MLQuests;
using Server.Items;
namespace Server.Mobiles.Townfolk
{
public class Prisoner : BaseEscortable
{
[Constructible]
public Prisoner()
{
Title = Female ? "the noblewoman" : "the nobleman";
CantWalk = true;
IsPrisoner = true;
}
public Prisoner(Serial serial)
: base(serial)
{
}
public override bool CanTeach => true;
public override bool ClickTitle => false;
public override void InitOutfit()
{
if (Female)
{
AddItem(new FancyDress(Utility.RandomNondyedHue()));
}
else
{
AddItem(new FancyShirt(Utility.RandomNondyedHue()));
AddItem(new LongPants(Utility.RandomNondyedHue()));
}
if (Utility.RandomBool())
AddItem(new Boots());
else
AddItem(new ThighBoots());
Utility.AssignRandomHair(this);
Utility.AssignRandomFacialHair(this, HairHue);
}
public override void Shout(PlayerMobile pm)
{
/*
* 502261 - HELP!
* 502262 - Help me!
* 502263 - Canst thou aid me?!
* 502264 - Help a poor prisoner!
* 502265 - Help! Please!
* 502266 - Aaah! Help me!
* 502267 - Go and get some help!
*/
MLQuestSystem.Tell(this, pm, Utility.Random(502261, 7));
}
public override void OnMovement(Mobile m, Point3D oldLocation)
{
base.OnMovement(m, oldLocation);
if (CantWalk && InRange(m, 1) && !InRange(oldLocation, 1) && (!m.Hidden || m.AccessLevel == AccessLevel.Player))
Say(502268); // Quickly, I beg thee! Unlock my chains! If thou dost look at me close thou canst see them.
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
}
}
}

View file

@ -0,0 +1,112 @@
using Server.Items;
namespace Server.Mobiles
{
public class Samurai : BaseCreature
{
[Constructible]
public Samurai() : base(AIType.AI_Melee, FightMode.Aggressor, 10, 1, 0.2, 0.4)
{
Title = "the samurai";
InitStats(100, 100, 25);
SetSkill(SkillName.ArmsLore, 64.0, 80.0);
SetSkill(SkillName.Bushido, 64.0, 85.0);
SetSkill(SkillName.Parry, 64.0, 80.0);
SetSkill(SkillName.Swords, 64.0, 85.0);
SpeechHue = Utility.RandomDyedHue();
Hue = Race.Human.RandomSkinHue();
if (Female = Utility.RandomBool())
{
Body = 0x191;
Name = NameList.RandomName("female");
}
else
{
Body = 0x190;
Name = NameList.RandomName("male");
}
switch (Utility.Random(3))
{
case 0:
AddItem(new Lajatang());
break;
case 1:
AddItem(new Wakizashi());
break;
case 2:
AddItem(new NoDachi());
break;
}
switch (Utility.Random(3))
{
case 0:
AddItem(new LeatherSuneate());
break;
case 1:
AddItem(new PlateSuneate());
break;
case 2:
AddItem(new StuddedHaidate());
break;
}
switch (Utility.Random(4))
{
case 0:
AddItem(new LeatherJingasa());
break;
case 1:
AddItem(new ChainHatsuburi());
break;
case 2:
AddItem(new HeavyPlateJingasa());
break;
case 3:
AddItem(new DecorativePlateKabuto());
break;
}
AddItem(new LeatherDo());
AddItem(new LeatherHiroSode());
AddItem(new SamuraiTabi(Utility.RandomNondyedHue())); // TODO: Hue
int hairHue = Utility.RandomNondyedHue();
Utility.AssignRandomHair(this, hairHue);
if (Utility.Random(7) != 0)
Utility.AssignRandomFacialHair(this, hairHue);
PackGold(250, 300);
}
public Samurai(Serial serial) : base(serial)
{
}
public override bool CanTeach => true;
public override bool ClickTitle => false;
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.WriteEncodedInt(0); // version
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadEncodedInt();
}
}
}

View file

@ -0,0 +1,66 @@
using Server.Items;
namespace Server.Mobiles
{
public class Sculptor : BaseCreature
{
[Constructible]
public Sculptor()
: base(AIType.AI_Animal, FightMode.None, 10, 1, 0.2, 0.4)
{
InitStats(31, 41, 51);
SpeechHue = Utility.RandomDyedHue();
Title = "the sculptor";
Hue = Race.Human.RandomSkinHue();
if (Female = Utility.RandomBool())
{
Body = 0x191;
Name = NameList.RandomName("female");
AddItem(new Kilt(Utility.RandomNeutralHue()));
}
else
{
Body = 0x190;
Name = NameList.RandomName("male");
AddItem(new LongPants(Utility.RandomNeutralHue()));
}
AddItem(new Doublet(Utility.RandomNeutralHue()));
AddItem(new HalfApron());
Utility.AssignRandomHair(this);
Container pack = new Backpack();
pack.DropItem(new Gold(250, 300));
pack.Movable = false;
AddItem(pack);
}
public Sculptor(Serial serial)
: base(serial)
{
}
public override bool ClickTitle => false;
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
}
}
}

View file

@ -0,0 +1,95 @@
using Server.Items;
namespace Server.Mobiles
{
public class SeekerOfAdventure : BaseEscortable
{
private static string[] m_Dungeons =
{
"Covetous", "Deceit", "Despise",
"Destard", "Hythloth", "Shame", // Old Code for Pre-ML shards.
"Wrong"
};
private static string[] m_MLDestinations =
{
"Cove", "Serpent's Hold", "Jhelom", // ML List
"Nujel'm"
};
[Constructible]
public SeekerOfAdventure()
{
Title = "the seeker of adventure";
}
public SeekerOfAdventure(Serial serial) : base(serial)
{
}
public override bool ClickTitle => false; // Do not display 'the seeker of adventure' when single-clicking
public override string[] GetPossibleDestinations()
{
if (Core.ML)
return m_MLDestinations;
return m_Dungeons;
}
private static int GetRandomHue()
{
switch (Utility.Random(6))
{
default:
case 0: return 0;
case 1: return Utility.RandomBlueHue();
case 2: return Utility.RandomGreenHue();
case 3: return Utility.RandomRedHue();
case 4: return Utility.RandomYellowHue();
case 5: return Utility.RandomNeutralHue();
}
}
public override void InitOutfit()
{
if (Female)
AddItem(new FancyDress(GetRandomHue()));
else
AddItem(new FancyShirt(GetRandomHue()));
int lowHue = GetRandomHue();
AddItem(new ShortPants(lowHue));
if (Female)
AddItem(new ThighBoots(lowHue));
else
AddItem(new Boots(lowHue));
if (!Female)
AddItem(new BodySash(lowHue));
AddItem(new Cloak(GetRandomHue()));
AddItem(new Longsword());
Utility.AssignRandomHair(this);
PackGold(100, 150);
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
}
}
}

View file

@ -0,0 +1,537 @@
using System;
using System.Collections.Generic;
using System.Text;
using Server.Commands;
using Server.Gumps;
using Server.Items;
using Server.Network;
using Server.Prompts;
namespace Server.Mobiles
{
public interface ITownCrierEntryList
{
List<TownCrierEntry> Entries{ get; }
TownCrierEntry GetRandomEntry();
TownCrierEntry AddEntry(string[] lines, TimeSpan duration);
void RemoveEntry(TownCrierEntry entry);
}
public class GlobalTownCrierEntryList : ITownCrierEntryList
{
private static GlobalTownCrierEntryList m_Instance;
public static GlobalTownCrierEntryList Instance => m_Instance ?? (m_Instance = new GlobalTownCrierEntryList());
public bool IsEmpty => Entries == null || Entries.Count == 0;
public List<TownCrierEntry> Entries{ get; private set; }
public TownCrierEntry GetRandomEntry()
{
if (Entries == null || Entries.Count == 0)
return null;
for (int i = Entries.Count - 1; Entries != null && i >= 0; --i)
{
if (i >= Entries.Count)
continue;
TownCrierEntry tce = Entries[i];
if (tce.Expired)
RemoveEntry(tce);
}
if (Entries == null || Entries.Count == 0)
return null;
return Entries[Utility.Random(Entries.Count)];
}
public TownCrierEntry AddEntry(string[] lines, TimeSpan duration)
{
if (Entries == null)
Entries = new List<TownCrierEntry>();
TownCrierEntry tce = new TownCrierEntry(lines, duration);
Entries.Add(tce);
List<TownCrier> instances = TownCrier.Instances;
for (int i = 0; i < instances.Count; ++i)
instances[i].ForceBeginAutoShout();
return tce;
}
public void RemoveEntry(TownCrierEntry tce)
{
if (Entries == null)
return;
Entries.Remove(tce);
if (Entries.Count == 0)
Entries = null;
}
public static void Initialize()
{
CommandSystem.Register("TownCriers", AccessLevel.GameMaster, TownCriers_OnCommand);
}
[Usage("TownCriers")]
[Description("Manages the global town crier list.")]
public static void TownCriers_OnCommand(CommandEventArgs e)
{
e.Mobile.SendGump(new TownCrierGump(e.Mobile, Instance));
}
}
public class TownCrierEntry
{
public TownCrierEntry(string[] lines, TimeSpan duration)
{
Lines = lines;
if (duration < TimeSpan.Zero)
duration = TimeSpan.Zero;
else if (duration > TimeSpan.FromDays(365.0))
duration = TimeSpan.FromDays(365.0);
ExpireTime = DateTime.UtcNow + duration;
}
public string[] Lines{ get; }
public DateTime ExpireTime{ get; }
public bool Expired => DateTime.UtcNow >= ExpireTime;
}
public class TownCrierDurationPrompt : Prompt
{
private ITownCrierEntryList m_Owner;
public TownCrierDurationPrompt(ITownCrierEntryList owner)
{
m_Owner = owner;
}
public override void OnResponse(Mobile from, string text)
{
if (!TimeSpan.TryParse(text, out TimeSpan ts))
{
from.SendMessage("Value was not properly formatted. Use: <hours:minutes:seconds>");
from.SendGump(new TownCrierGump(from, m_Owner));
return;
}
if (ts < TimeSpan.Zero)
ts = TimeSpan.Zero;
from.SendMessage("Duration set to: {0}", ts);
from.SendMessage("Enter the first line to shout:");
from.Prompt = new TownCrierLinesPrompt(m_Owner, null, new List<string>(), ts);
}
public override void OnCancel(Mobile from)
{
from.SendLocalizedMessage(502980); // Message entry cancelled.
from.SendGump(new TownCrierGump(from, m_Owner));
}
}
public class TownCrierLinesPrompt : Prompt
{
private TimeSpan m_Duration;
private TownCrierEntry m_Entry;
private List<string> m_Lines;
private ITownCrierEntryList m_Owner;
public TownCrierLinesPrompt(ITownCrierEntryList owner, TownCrierEntry entry, List<string> lines, TimeSpan duration)
{
m_Owner = owner;
m_Entry = entry;
m_Lines = lines;
m_Duration = duration;
}
public override void OnResponse(Mobile from, string text)
{
m_Lines.Add(text);
from.SendMessage("Enter the next line to shout, or press <ESC> if the message is finished.");
from.Prompt = new TownCrierLinesPrompt(m_Owner, m_Entry, m_Lines, m_Duration);
}
public override void OnCancel(Mobile from)
{
if (m_Entry != null)
m_Owner.RemoveEntry(m_Entry);
if (m_Lines.Count > 0)
{
m_Owner.AddEntry(m_Lines.ToArray(), m_Duration);
from.SendMessage("Message has been set.");
}
else
{
if (m_Entry != null)
from.SendMessage("Message deleted.");
else
from.SendLocalizedMessage(502980); // Message entry cancelled.
}
from.SendGump(new TownCrierGump(from, m_Owner));
}
}
public class TownCrierGump : Gump
{
private Mobile m_From;
private ITownCrierEntryList m_Owner;
public TownCrierGump(Mobile from, ITownCrierEntryList owner) : base(50, 50)
{
m_From = from;
m_Owner = owner;
from.CloseGump<TownCrierGump>();
AddPage(0);
List<TownCrierEntry> entries = owner.Entries;
owner.GetRandomEntry(); // force expiration checks
int count = 0;
if (entries != null)
count = entries.Count;
AddImageTiled(0, 0, 300, 38 + (count == 0 ? 20 : count * 85), 0xA40);
AddAlphaRegion(1, 1, 298, 36 + (count == 0 ? 20 : count * 85));
AddHtml(8, 8, 300 - 8 - 30, 20, "<basefont color=#FFFFFF><center>TOWN CRIER MESSAGES</center></basefont>");
AddButton(300 - 8 - 30, 8, 0xFAB, 0xFAD, 1);
if (count == 0)
AddHtml(8, 30, 284, 20, "<basefont color=#FFFFFF>The crier has no news.</basefont>");
else
for (int i = 0; i < entries.Count; ++i)
{
TownCrierEntry tce = entries[i];
TimeSpan toExpire = tce.ExpireTime - DateTime.UtcNow;
if (toExpire < TimeSpan.Zero)
toExpire = TimeSpan.Zero;
StringBuilder sb = new StringBuilder();
sb.Append("[Expires: ");
if (toExpire.TotalHours >= 1)
{
sb.Append((int)toExpire.TotalHours);
sb.Append(':');
sb.Append(toExpire.Minutes.ToString("D2"));
}
else
{
sb.Append(toExpire.Minutes);
}
sb.Append(':');
sb.Append(toExpire.Seconds.ToString("D2"));
sb.Append("] ");
for (int j = 0; j < tce.Lines.Length; ++j)
{
if (j > 0)
sb.Append("<br>");
sb.Append(tce.Lines[j]);
}
AddHtml(8, 35 + i * 85, 254, 80, sb.ToString(), true, true);
AddButton(300 - 8 - 26, 35 + i * 85, 0x15E1, 0x15E5, 2 + i);
}
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (info.ButtonID == 1)
{
m_From.SendMessage("Enter the duration for the new message. Format: <hours:minutes:seconds>");
m_From.Prompt = new TownCrierDurationPrompt(m_Owner);
}
else if (info.ButtonID > 1)
{
List<TownCrierEntry> entries = m_Owner.Entries;
int index = info.ButtonID - 2;
if (entries != null && index < entries.Count)
{
TownCrierEntry tce = entries[index];
TimeSpan ts = tce.ExpireTime - DateTime.UtcNow;
if (ts < TimeSpan.Zero)
ts = TimeSpan.Zero;
m_From.SendMessage("Editing entry #{0}.", index + 1);
m_From.SendMessage("Enter the first line to shout:");
m_From.Prompt = new TownCrierLinesPrompt(m_Owner, tce, new List<string>(), ts);
}
}
}
}
public class TownCrier : Mobile, ITownCrierEntryList
{
private Timer m_AutoShoutTimer;
private Timer m_NewsTimer;
[Constructible]
public TownCrier()
{
Instances.Add(this);
InitStats(100, 100, 25);
Title = "the town crier";
Hue = Race.Human.RandomSkinHue();
if (!Core.AOS)
NameHue = 0x35;
if (Female = Utility.RandomBool())
{
Body = 0x191;
Name = NameList.RandomName("female");
}
else
{
Body = 0x190;
Name = NameList.RandomName("male");
}
AddItem(new FancyShirt(Utility.RandomBlueHue()));
Item skirt;
switch (Utility.Random(2))
{
case 0:
skirt = new Skirt();
break;
default:
case 1:
skirt = new Kilt();
break;
}
skirt.Hue = Utility.RandomGreenHue();
AddItem(skirt);
AddItem(new FeatheredHat(Utility.RandomGreenHue()));
Item boots;
switch (Utility.Random(2))
{
case 0:
boots = new Boots();
break;
default:
case 1:
boots = new ThighBoots();
break;
}
AddItem(boots);
Utility.AssignRandomHair(this);
}
public TownCrier(Serial serial) : base(serial)
{
Instances.Add(this);
}
public static List<TownCrier> Instances{ get; } = new List<TownCrier>();
public List<TownCrierEntry> Entries{ get; private set; }
public TownCrierEntry GetRandomEntry()
{
if (Entries == null || Entries.Count == 0)
return GlobalTownCrierEntryList.Instance.GetRandomEntry();
for (int i = Entries.Count - 1; Entries != null && i >= 0; --i)
{
if (i >= Entries.Count)
continue;
TownCrierEntry tce = Entries[i];
if (tce.Expired)
RemoveEntry(tce);
}
if (Entries == null || Entries.Count == 0)
return GlobalTownCrierEntryList.Instance.GetRandomEntry();
TownCrierEntry entry = GlobalTownCrierEntryList.Instance.GetRandomEntry();
if (entry == null || Utility.RandomBool())
entry = Entries[Utility.Random(Entries.Count)];
return entry;
}
public TownCrierEntry AddEntry(string[] lines, TimeSpan duration)
{
if (Entries == null)
Entries = new List<TownCrierEntry>();
TownCrierEntry tce = new TownCrierEntry(lines, duration);
Entries.Add(tce);
if (m_AutoShoutTimer == null)
m_AutoShoutTimer = Timer.DelayCall(TimeSpan.FromSeconds(5.0), TimeSpan.FromMinutes(1.0), AutoShout_Callback);
return tce;
}
public void RemoveEntry(TownCrierEntry tce)
{
if (Entries == null)
return;
Entries.Remove(tce);
if (Entries.Count == 0)
Entries = null;
if (Entries == null && GlobalTownCrierEntryList.Instance.IsEmpty)
{
m_AutoShoutTimer?.Stop();
m_AutoShoutTimer = null;
}
}
public void ForceBeginAutoShout()
{
if (m_AutoShoutTimer == null)
m_AutoShoutTimer = Timer.DelayCall(TimeSpan.FromSeconds(5.0), TimeSpan.FromMinutes(1.0), AutoShout_Callback);
}
private void AutoShout_Callback()
{
TownCrierEntry tce = GetRandomEntry();
if (tce == null)
{
m_AutoShoutTimer?.Stop();
m_AutoShoutTimer = null;
}
else if (m_NewsTimer == null)
{
int index = 0;
m_NewsTimer = Timer.DelayCall(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(3.0),
() => ShoutNews_Callback(tce, index));
PublicOverheadMessage(MessageType.Regular, 0x3B2, 502976); // Hear ye! Hear ye!
}
}
private void ShoutNews_Callback(TownCrierEntry tce, int index)
{
if (index < 0 || index >= tce.Lines.Length)
{
m_NewsTimer?.Stop();
m_NewsTimer = null;
}
else
{
PublicOverheadMessage(MessageType.Regular, 0x3B2, false, tce.Lines[index]);
}
}
public override void OnDoubleClick(Mobile from)
{
if (from.AccessLevel >= AccessLevel.GameMaster)
from.SendGump(new TownCrierGump(from, this));
else
base.OnDoubleClick(from);
}
public override bool HandlesOnSpeech(Mobile from)
{
return m_NewsTimer == null && from.Alive && InRange(from, 12);
}
public override void OnSpeech(SpeechEventArgs e)
{
if (m_NewsTimer == null && e.HasKeyword(0x30) && e.Mobile.Alive && InRange(e.Mobile, 12)) // *news*
{
Direction = GetDirectionTo(e.Mobile);
TownCrierEntry tce = GetRandomEntry();
if (tce == null)
{
PublicOverheadMessage(MessageType.Regular, 0x3B2, 1005643); // I have no news at this time.
}
else
{
int index = 0;
m_NewsTimer = Timer.DelayCall(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(3.0),
() => ShoutNews_Callback(tce, index));
PublicOverheadMessage(MessageType.Regular, 0x3B2, 502978); // Some of the latest news!
}
}
}
public override bool CanBeDamaged()
{
return false;
}
public override void OnDelete()
{
Instances.Remove(this);
base.OnDelete();
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
if (Core.AOS && NameHue == 0x35)
NameHue = -1;
}
}
}