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,28 @@
using Server.Targeting;
namespace Server.Engines.BulkOrders
{
public class BODTarget : Target
{
private BaseBOD m_Deed;
public BODTarget(BaseBOD deed) : base(18, false, TargetFlags.None)
{
m_Deed = deed;
}
protected override void OnTarget(Mobile from, object targeted)
{
if (m_Deed.Deleted || !m_Deed.IsChildOf(from.Backpack))
return;
if (!(targeted is Item item && item.IsChildOf(from.Backpack)))
{
from.SendLocalizedMessage( 1045158 ); // You must have the item in your backpack to target it.
return;
}
m_Deed.EndCombine(from, item);
}
}
}

View file

@ -0,0 +1,153 @@
using System.Collections.Generic;
namespace Server.Engines.BulkOrders
{
public abstract class BaseBOD : Item
{
private int m_AmountMax;
private bool m_RequireExceptional;
private BulkMaterialType m_Material;
public static BulkMaterialType GetRandomMaterial(BulkMaterialType start, double[] chances)
{
double random = Utility.RandomDouble();
for ( int i = 0; i < chances.Length; ++i )
{
if ( random < chances[i] )
return i == 0 ? BulkMaterialType.None : start + (i - 1);
random -= chances[i];
}
return BulkMaterialType.None;
}
public BaseBOD(int hue, int amountMax, bool requireExeptional, BulkMaterialType material) : this()
{
Hue = hue;
AmountMax = amountMax;
RequireExceptional = requireExeptional;
Material = material;
}
public BaseBOD() : base(Core.AOS ? 0x2258 : 0x14EF)
{
Weight = 1.0;
LootType = LootType.Blessed;
}
public BaseBOD(Serial serial) : base(serial)
{
}
public abstract bool Complete{ get; }
[CommandProperty(AccessLevel.GameMaster)]
public sealed override int Hue{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public int AmountMax
{
get => m_AmountMax;
set{ m_AmountMax = value; InvalidateProperties(); }
}
[CommandProperty( AccessLevel.GameMaster )]
public bool RequireExceptional
{
get => m_RequireExceptional;
set{ m_RequireExceptional = value; InvalidateProperties(); }
}
[CommandProperty( AccessLevel.GameMaster )]
public BulkMaterialType Material
{
get => m_Material;
set{ m_Material = value; InvalidateProperties(); }
}
public abstract RewardGroup GetRewardGroup();
public abstract int ComputeGold();
public abstract int ComputeFame();
public abstract void EndCombine(Mobile from, Item item);
public virtual void GetRewards(out Item reward, out int gold, out int fame)
{
gold = ComputeGold();
fame = ComputeFame();
List<RewardItem> rewards = ComputeRewards(false);
reward = rewards.Count <= 0 ? null : rewards[Utility.Random(rewards.Count)].Construct();
}
public virtual List<RewardItem> ComputeRewards(bool full)
{
RewardGroup rewardGroup = GetRewardGroup();
List<RewardItem> list = new List<RewardItem>();
if (full)
{
for (int i = 0; i < rewardGroup?.Items.Length; ++i)
{
RewardItem reward = rewardGroup.Items[i];
if (reward != null)
list.Add(reward);
}
}
else
{
RewardItem reward = rewardGroup.AcquireItem();
if (reward != null)
list.Add(reward);
}
return list;
}
public virtual void BeginCombine(Mobile from)
{
if (Complete)
from.SendLocalizedMessage(1045166); // The maximum amount of requested items have already been combined to this deed.
else
from.Target = new BODTarget(this);
}
public override void Serialize( GenericWriter writer )
{
base.Serialize( writer );
writer.WriteEncodedInt( 0 ); // version
writer.Write( m_AmountMax );
writer.Write( m_RequireExceptional );
writer.Write( (int) m_Material );
}
public override void Deserialize( GenericReader reader )
{
base.Deserialize( reader );
int version = reader.ReadEncodedInt();
switch ( version )
{
case 0:
{
m_AmountMax = reader.ReadInt();
m_RequireExceptional = reader.ReadBool();
m_Material = (BulkMaterialType)reader.ReadInt();
break;
}
}
if ( Parent == null && Map == Map.Internal && Location == Point3D.Zero )
Delete();
}
}
}

View file

@ -0,0 +1,62 @@
namespace Server.Engines.BulkOrders
{
public class BOBFilter
{
public BOBFilter()
{
}
public BOBFilter(GenericReader reader)
{
int version = reader.ReadEncodedInt();
switch (version)
{
case 1:
{
Type = reader.ReadEncodedInt();
Quality = reader.ReadEncodedInt();
Material = reader.ReadEncodedInt();
Quantity = reader.ReadEncodedInt();
break;
}
}
}
public bool IsDefault => Type == 0 && Quality == 0 && Material == 0 && Quantity == 0;
public int Type{ get; set; }
public int Quality{ get; set; }
public int Material{ get; set; }
public int Quantity{ get; set; }
public void Clear()
{
Type = 0;
Quality = 0;
Material = 0;
Quantity = 0;
}
public void Serialize(GenericWriter writer)
{
if (IsDefault)
{
writer.WriteEncodedInt(0); // version
}
else
{
writer.WriteEncodedInt(1); // version
writer.WriteEncodedInt(Type);
writer.WriteEncodedInt(Quality);
writer.WriteEncodedInt(Material);
writer.WriteEncodedInt(Quantity);
}
}
}
}

View file

@ -0,0 +1,220 @@
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.BulkOrders
{
public class BOBFilterGump : Gump
{
private const int LabelColor = 0x7FFF;
private static int[,] m_MaterialFilters =
{
{ 1044067, 1 }, // Blacksmithy
{ 1062226, 3 }, // Iron
{ 1018332, 4 }, // Dull Copper
{ 1018333, 5 }, // Shadow Iron
{ 1018334, 6 }, // Copper
{ 1018335, 7 }, // Bronze
{ 0, 0 }, // --Blank--
{ 1018336, 8 }, // Golden
{ 1018337, 9 }, // Agapite
{ 1018338, 10 }, // Verite
{ 1018339, 11 }, // Valorite
{ 0, 0 }, // --Blank--
{ 1044094, 2 }, // Tailoring
{ 1044286, 12 }, // Cloth
{ 1062235, 13 }, // Leather
{ 1062236, 14 }, // Spined
{ 1062237, 15 }, // Horned
{ 1062238, 16 } // Barbed
};
private static int[,] m_TypeFilters =
{
{ 1062229, 0 }, // All
{ 1062224, 1 }, // Small
{ 1062225, 2 } // Large
};
private static int[,] m_QualityFilters =
{
{ 1062229, 0 }, // All
{ 1011542, 1 }, // Normal
{ 1060636, 2 } // Exceptional
};
private static int[,] m_AmountFilters =
{
{ 1062229, 0 }, // All
{ 1049706, 1 }, // 10
{ 1016007, 2 }, // 15
{ 1062239, 3 } // 20
};
private static int[][,] m_Filters =
{
m_TypeFilters,
m_QualityFilters,
m_MaterialFilters,
m_AmountFilters
};
private static int[] m_XOffsets_Type = { 0, 75, 170 };
private static int[] m_XOffsets_Quality = { 0, 75, 170 };
private static int[] m_XOffsets_Amount = { 0, 75, 180, 275 };
private static int[] m_XOffsets_Material = { 0, 105, 210, 305, 390, 485 };
private static int[] m_XWidths_Small = { 50, 50, 70, 50 };
private static int[] m_XWidths_Large = { 80, 50, 50, 50, 50, 50 };
private BulkOrderBook m_Book;
private PlayerMobile m_From;
public BOBFilterGump(PlayerMobile from, BulkOrderBook book) : base(12, 24)
{
from.CloseGump<BOBGump>();
from.CloseGump<BOBFilterGump>();
m_From = from;
m_Book = book;
BOBFilter f = from.UseOwnFilter ? from.BOBFilter : book.Filter;
AddPage(0);
AddBackground(10, 10, 600, 439, 5054);
AddImageTiled(18, 20, 583, 420, 2624);
AddAlphaRegion(18, 20, 583, 420);
AddImage(5, 5, 10460);
AddImage(585, 5, 10460);
AddImage(5, 424, 10460);
AddImage(585, 424, 10460);
AddHtmlLocalized(270, 32, 200, 32, 1062223, LabelColor); // Filter Preference
AddHtmlLocalized(26, 64, 120, 32, 1062228, LabelColor); // Bulk Order Type
AddFilterList(25, 96, m_XOffsets_Type, 40, m_TypeFilters, m_XWidths_Small, f.Type, 0);
AddHtmlLocalized(320, 64, 50, 32, 1062215, LabelColor); // Quality
AddFilterList(320, 96, m_XOffsets_Quality, 40, m_QualityFilters, m_XWidths_Small, f.Quality, 1);
AddHtmlLocalized(26, 160, 120, 32, 1062232, LabelColor); // Material Type
AddFilterList(25, 192, m_XOffsets_Material, 40, m_MaterialFilters, m_XWidths_Large, f.Material, 2);
AddHtmlLocalized(26, 320, 120, 32, 1062217, LabelColor); // Amount
AddFilterList(25, 352, m_XOffsets_Amount, 40, m_AmountFilters, m_XWidths_Small, f.Quantity, 3);
AddHtmlLocalized(75, 416, 120, 32, 1062477, from.UseOwnFilter ? LabelColor : 16927); // Set Book Filter
AddButton(40, 416, 4005, 4007, 1);
AddHtmlLocalized(235, 416, 120, 32, 1062478, from.UseOwnFilter ? 16927 : LabelColor); // Set Your Filter
AddButton(200, 416, 4005, 4007, 2);
AddHtmlLocalized(405, 416, 120, 32, 1062231, LabelColor); // Clear Filter
AddButton(370, 416, 4005, 4007, 3);
AddHtmlLocalized(540, 416, 50, 32, 1011046, LabelColor); // APPLY
AddButton(505, 416, 4017, 4018, 0);
}
private void AddFilterList(int x, int y, int[] xOffsets, int yOffset, int[,] filters, int[] xWidths, int filterValue,
int filterIndex)
{
for (int i = 0; i < filters.GetLength(0); ++i)
{
int number = filters[i, 0];
if (number == 0)
continue;
bool isSelected = filters[i, 1] == filterValue ||
i % xOffsets.Length == 0 && filterValue == 0;
AddHtmlLocalized(x + 35 + xOffsets[i % xOffsets.Length], y + i / xOffsets.Length * yOffset,
xWidths[i % xOffsets.Length], 32, number, isSelected ? 16927 : LabelColor);
AddButton(x + xOffsets[i % xOffsets.Length], y + i / xOffsets.Length * yOffset, 4005, 4007,
4 + filterIndex + i * 4);
}
}
public override void OnResponse(NetState sender, RelayInfo info)
{
BOBFilter f = m_From.UseOwnFilter ? m_From.BOBFilter : m_Book.Filter;
int index = info.ButtonID;
switch (index)
{
case 0: // Apply
{
m_From.SendGump(new BOBGump(m_From, m_Book));
break;
}
case 1: // Set Book Filter
{
m_From.UseOwnFilter = false;
m_From.SendGump(new BOBFilterGump(m_From, m_Book));
break;
}
case 2: // Set Your Filter
{
m_From.UseOwnFilter = true;
m_From.SendGump(new BOBFilterGump(m_From, m_Book));
break;
}
case 3: // Clear Filter
{
f.Clear();
m_From.SendGump(new BOBFilterGump(m_From, m_Book));
break;
}
default:
{
index -= 4;
int type = index % 4;
index /= 4;
if (type >= 0 && type < m_Filters.Length)
{
int[,] filters = m_Filters[type];
if (index >= 0 && index < filters.GetLength(0))
{
if (filters[index, 0] == 0)
break;
switch (type)
{
case 0:
f.Type = filters[index, 1];
break;
case 1:
f.Quality = filters[index, 1];
break;
case 2:
f.Material = filters[index, 1];
break;
case 3:
f.Quantity = filters[index, 1];
break;
}
m_From.SendGump(new BOBFilterGump(m_From, m_Book));
}
}
break;
}
}
}
}
}

View file

@ -0,0 +1,647 @@
using System;
using System.Collections.Generic;
using Server.Gumps;
using Server.Items;
using Server.Mobiles;
using Server.Network;
using Server.Prompts;
namespace Server.Engines.BulkOrders
{
public class BOBGump : Gump
{
private const int LabelColor = 0x7FFF;
private BulkOrderBook m_Book;
private PlayerMobile m_From;
private List<IBOBEntry> m_List;
private int m_Page;
public BOBGump(PlayerMobile from, BulkOrderBook book, int page = 0, List<IBOBEntry> list = null) : base(12, 24)
{
from.CloseGump<BOBGump>();
from.CloseGump<BOBFilterGump>();
m_From = from;
m_Book = book;
m_Page = page;
if (list == null)
{
list = new List<IBOBEntry>(book.Entries.Count);
for (int i = 0; i < book.Entries.Count; ++i)
{
IBOBEntry entry = book.Entries[i];
if (CheckFilter(entry))
list.Add(entry);
}
}
m_List = list;
int index = GetIndexForPage(page);
int count = GetCountForIndex(index);
int tableIndex = 0;
PlayerVendor pv = book.RootParent as PlayerVendor;
bool canDrop = book.IsChildOf(from.Backpack);
bool canBuy = pv != null;
bool canPrice = canDrop || canBuy;
if (canBuy)
{
VendorItem vi = pv.GetVendorItem(book);
canBuy = vi?.IsForSale == false;
}
int width = 600;
if (!canPrice)
width = 516;
X = (624 - width) / 2;
AddPage(0);
AddBackground(10, 10, width, 439, 5054);
AddImageTiled(18, 20, width - 17, 420, 2624);
if (canPrice)
{
AddImageTiled(573, 64, 24, 352, 200);
AddImageTiled(493, 64, 78, 352, 1416);
}
if (canDrop)
AddImageTiled(24, 64, 32, 352, 1416);
AddImageTiled(58, 64, 36, 352, 200);
AddImageTiled(96, 64, 133, 352, 1416);
AddImageTiled(231, 64, 80, 352, 200);
AddImageTiled(313, 64, 100, 352, 1416);
AddImageTiled(415, 64, 76, 352, 200);
for (int i = index; i < index + count && i >= 0 && i < list.Count; ++i)
{
IBOBEntry entry = list[i];
if (!CheckFilter(entry))
continue;
AddImageTiled(24, 94 + tableIndex * 32, canPrice ? 573 : 489, 2, 2624);
tableIndex += entry is BOBLargeEntry largeEntry ? largeEntry.Entries.Length : 1;
}
AddAlphaRegion(18, 20, width - 17, 420);
AddImage(5, 5, 10460);
AddImage(width - 15, 5, 10460);
AddImage(5, 424, 10460);
AddImage(width - 15, 424, 10460);
AddHtmlLocalized(canPrice ? 266 : 224, 32, 200, 32, 1062220, LabelColor); // Bulk Order Book
AddHtmlLocalized(63, 64, 200, 32, 1062213, LabelColor); // Type
AddHtmlLocalized(147, 64, 200, 32, 1062214, LabelColor); // Item
AddHtmlLocalized(246, 64, 200, 32, 1062215, LabelColor); // Quality
AddHtmlLocalized(336, 64, 200, 32, 1062216, LabelColor); // Material
AddHtmlLocalized(429, 64, 200, 32, 1062217, LabelColor); // Amount
AddButton(35, 32, 4005, 4007, 1);
AddHtmlLocalized(70, 32, 200, 32, 1062476, LabelColor); // Set Filter
BOBFilter f = from.UseOwnFilter ? from.BOBFilter : book.Filter;
if (f.IsDefault)
AddHtmlLocalized(canPrice ? 470 : 386, 32, 120, 32, 1062475, 16927); // Using No Filter
else if (from.UseOwnFilter)
AddHtmlLocalized(canPrice ? 470 : 386, 32, 120, 32, 1062451, 16927); // Using Your Filter
else
AddHtmlLocalized(canPrice ? 470 : 386, 32, 120, 32, 1062230, 16927); // Using Book Filter
AddButton(375, 416, 4017, 4018, 0);
AddHtmlLocalized(410, 416, 120, 20, 1011441, LabelColor); // EXIT
if (canDrop)
AddHtmlLocalized(26, 64, 50, 32, 1062212, LabelColor); // Drop
if (canPrice)
{
AddHtmlLocalized(516, 64, 200, 32, 1062218, LabelColor); // Price
if (canBuy)
{
AddHtmlLocalized(576, 64, 200, 32, 1062219, LabelColor); // Buy
}
else
{
AddHtmlLocalized(576, 64, 200, 32, 1062227, LabelColor); // Set
AddButton(450, 416, 4005, 4007, 4);
AddHtml(485, 416, 120, 20, "<BASEFONT COLOR=#FFFFFF>Price all</FONT>");
}
}
tableIndex = 0;
if (page > 0)
{
AddButton(75, 416, 4014, 4016, 2);
AddHtmlLocalized(110, 416, 150, 20, 1011067, LabelColor); // Previous page
}
if (GetIndexForPage(page + 1) < list.Count)
{
AddButton(225, 416, 4005, 4007, 3);
AddHtmlLocalized(260, 416, 150, 20, 1011066, LabelColor); // Next page
}
for (int i = index; i < index + count && i >= 0 && i < list.Count; ++i)
{
IBOBEntry entry = list[i];
if (!CheckFilter(entry))
continue;
if (entry is BOBLargeEntry largeEntry)
{
int y = 96 + tableIndex * 32;
if (canDrop)
AddButton(35, y + 2, 5602, 5606, 5 + i * 2);
if (canDrop || canBuy && entry.Price > 0)
{
AddButton(579, y + 2, 2117, 2118, 6 + i * 2);
AddLabel(495, y, 1152, entry.Price.ToString());
}
AddHtmlLocalized(61, y, 50, 32, 1062225, LabelColor); // Large
for (int j = 0; j < largeEntry.Entries.Length; ++j)
{
BOBLargeSubEntry sub = largeEntry.Entries[j];
AddHtmlLocalized(103, y, 130, 32, sub.Number, LabelColor);
if (entry.RequireExceptional)
AddHtmlLocalized(235, y, 80, 20, 1060636, LabelColor); // exceptional
else
AddHtmlLocalized(235, y, 80, 20, 1011542, LabelColor); // normal
object name = GetMaterialName(entry.Material, entry.DeedType, sub.ItemType);
if (name is int intName)
AddHtmlLocalized(316, y, 100, 20, intName, LabelColor);
else
AddLabel(316, y, 1152, name.ToString());
AddLabel(421, y, 1152, $"{sub.AmountCur} / {entry.AmountMax}");
++tableIndex;
y += 32;
}
}
else
{
BOBSmallEntry smallEntry = (BOBSmallEntry)entry;
int y = 96 + tableIndex++ * 32;
if (canDrop)
AddButton(35, y + 2, 5602, 5606, 5 + i * 2);
if (canDrop || canBuy && smallEntry.Price > 0)
{
AddButton(579, y + 2, 2117, 2118, 6 + i * 2);
AddLabel(495, y, 1152, smallEntry.Price.ToString());
}
AddHtmlLocalized(61, y, 50, 32, 1062224, LabelColor); // Small
AddHtmlLocalized(103, y, 130, 32, smallEntry.Number, LabelColor);
if (smallEntry.RequireExceptional)
AddHtmlLocalized(235, y, 80, 20, 1060636, LabelColor); // exceptional
else
AddHtmlLocalized(235, y, 80, 20, 1011542, LabelColor); // normal
object name = GetMaterialName(smallEntry.Material, smallEntry.DeedType, smallEntry.ItemType);
if (name is int intName)
AddHtmlLocalized(316, y, 100, 20, intName, LabelColor);
else
AddLabel(316, y, 1152, name.ToString());
AddLabel(421, y, 1152, $"{smallEntry.AmountCur} / {smallEntry.AmountMax}");
}
}
}
public bool CheckFilter(IBOBEntry entry)
{
if (entry is BOBLargeEntry largeEntry)
return CheckFilter(entry.Material, entry.AmountMax, true, entry.RequireExceptional, entry.DeedType,
largeEntry.Entries.Length > 0 ? largeEntry.Entries[0].ItemType : null);
if (entry is BOBSmallEntry smallEntry)
return CheckFilter(entry.Material, entry.AmountMax, false, entry.RequireExceptional,
entry.DeedType, smallEntry.ItemType);
return false;
}
public bool CheckFilter(BulkMaterialType mat, int amountMax, bool isLarge, bool reqExc, BODType deedType,
Type itemType)
{
BOBFilter f = m_From.UseOwnFilter ? m_From.BOBFilter : m_Book.Filter;
if (f.IsDefault)
return true;
if (f.Quality == 1 && reqExc)
return false;
if (f.Quality == 2 && !reqExc)
return false;
if (f.Quantity == 1 && amountMax != 10)
return false;
if (f.Quantity == 2 && amountMax != 15)
return false;
if (f.Quantity == 3 && amountMax != 20)
return false;
if (f.Type == 1 && isLarge)
return false;
if (f.Type == 2 && !isLarge)
return false;
switch (f.Material)
{
default:
return true;
case 1: return deedType == BODType.Smith;
case 2: return deedType == BODType.Tailor;
case 3:
return mat == BulkMaterialType.None &&
BGTClassifier.Classify(deedType, itemType) == BulkGenericType.Iron;
case 4: return mat == BulkMaterialType.DullCopper;
case 5: return mat == BulkMaterialType.ShadowIron;
case 6: return mat == BulkMaterialType.Copper;
case 7: return mat == BulkMaterialType.Bronze;
case 8: return mat == BulkMaterialType.Gold;
case 9: return mat == BulkMaterialType.Agapite;
case 10: return mat == BulkMaterialType.Verite;
case 11: return mat == BulkMaterialType.Valorite;
case 12:
return mat == BulkMaterialType.None &&
BGTClassifier.Classify(deedType, itemType) == BulkGenericType.Cloth;
case 13:
return mat == BulkMaterialType.None &&
BGTClassifier.Classify(deedType, itemType) == BulkGenericType.Leather;
case 14: return mat == BulkMaterialType.Spined;
case 15: return mat == BulkMaterialType.Horned;
case 16: return mat == BulkMaterialType.Barbed;
}
}
public int GetIndexForPage(int page)
{
int index = 0;
while (page-- > 0)
index += GetCountForIndex(index);
return index;
}
public int GetCountForIndex(int index)
{
int slots = 0;
int count = 0;
List<IBOBEntry> list = m_List;
for (int i = index; i >= 0 && i < list.Count; ++i)
{
IBOBEntry entry = list[i];
if (CheckFilter(entry))
{
int add = entry is BOBLargeEntry largeEntry ? largeEntry.Entries.Length : 1;
if (slots + add > 10)
break;
slots += add;
}
++count;
}
return count;
}
public int GetPageForIndex(int index, int sizeDropped)
{
if (index <= 0)
return 0;
int count = 0;
int page = 0;
int i;
List<IBOBEntry> list = m_List;
for (i = 0; i < index && i < list.Count; i++)
{
IBOBEntry entry = list[i];
if (!CheckFilter(entry))
continue;
int add = entry is BOBLargeEntry largeEntry ? largeEntry.Entries.Length : 1;
count += add;
if (count > 10)
{
page++;
count = add;
}
}
/* now we are on the page of the bod preceding the dropped one.
* next step: checking whether we have to remain where we are.
* The counter i needs to be incremented as the bod to this very moment
* has not yet been removed from m_List */
i++;
/* if, for instance, a big bod of size 6 has been removed, smaller bods
* might fall back into this page. Depending on their sizes, the page needs
* to be adjusted accordingly. This is done now.
*/
if (count + sizeDropped > 10)
{
while (i < list.Count && count <= 10)
{
IBOBEntry entry = list[i];
if (CheckFilter(entry))
count += entry is BOBLargeEntry largeEntry ? largeEntry.Entries.Length : 1;
i++;
}
if (count > 10)
page++;
}
return page;
}
public object GetMaterialName(BulkMaterialType mat, BODType type, Type itemType)
{
switch (type)
{
case BODType.Smith:
{
switch (mat)
{
case BulkMaterialType.None: return 1062226;
case BulkMaterialType.DullCopper: return 1018332;
case BulkMaterialType.ShadowIron: return 1018333;
case BulkMaterialType.Copper: return 1018334;
case BulkMaterialType.Bronze: return 1018335;
case BulkMaterialType.Gold: return 1018336;
case BulkMaterialType.Agapite: return 1018337;
case BulkMaterialType.Verite: return 1018338;
case BulkMaterialType.Valorite: return 1018339;
}
break;
}
case BODType.Tailor:
{
switch (mat)
{
case BulkMaterialType.None:
{
if (itemType.IsSubclassOf(typeof(BaseArmor)) || itemType.IsSubclassOf(typeof(BaseShoes)))
return 1062235;
return 1044286;
}
case BulkMaterialType.Spined: return 1062236;
case BulkMaterialType.Horned: return 1062237;
case BulkMaterialType.Barbed: return 1062238;
}
break;
}
}
return "Invalid";
}
public override void OnResponse(NetState sender, RelayInfo info)
{
int index = info.ButtonID;
switch (index)
{
case 0: // EXIT
{
break;
}
case 1: // Set Filter
{
m_From.SendGump(new BOBFilterGump(m_From, m_Book));
break;
}
case 2: // Previous page
{
if (m_Page > 0)
m_From.SendGump(new BOBGump(m_From, m_Book, m_Page - 1, m_List));
return;
}
case 3: // Next page
{
if (GetIndexForPage(m_Page + 1) < m_List.Count)
m_From.SendGump(new BOBGump(m_From, m_Book, m_Page + 1, m_List));
break;
}
case 4: // Price all
{
if (m_Book.IsChildOf(m_From.Backpack))
{
m_From.Prompt = new SetPricePrompt(m_Book, null, m_Page, m_List);
m_From.SendMessage("Type in a price for all deeds in the book:");
}
break;
}
default:
{
index -= 5;
int type = index % 2;
index /= 2;
if (index < 0 || index >= m_List.Count)
break;
IBOBEntry bobEntry = m_List[index];
if (!m_Book.Entries.Contains(bobEntry))
{
m_From.SendLocalizedMessage(1062382); // The deed selected is not available.
break;
}
if (type == 0) // Drop
{
if (m_Book.IsChildOf(m_From.Backpack))
{
Item item = bobEntry.Reconstruct();
Container pack = m_From.Backpack;
if (pack?.CheckHold(m_From, item, true, true, 0,
item.PileWeight + item.TotalWeight) != true)
{
m_From.SendLocalizedMessage(503204); // You do not have room in your backpack for this
m_From.SendGump(new BOBGump(m_From, m_Book, m_Page));
}
else
{
if (m_Book.IsChildOf(m_From.Backpack))
{
int sizeOfDroppedBod = bobEntry is BOBLargeEntry entry ? entry.Entries.Length : 1;
m_From.AddToBackpack(item);
m_From.SendLocalizedMessage(
1045152); // The bulk order deed has been placed in your backpack.
m_Book.Entries.Remove(bobEntry);
m_Book.InvalidateProperties();
if (m_Book.Entries.Count / 5 < m_Book.ItemCount)
{
m_Book.ItemCount--;
m_Book.InvalidateItems();
}
if (m_Book.Entries.Count > 0)
{
m_Page = GetPageForIndex(index, sizeOfDroppedBod);
m_From.SendGump(new BOBGump(m_From, m_Book, m_Page));
}
else
{
m_From.SendLocalizedMessage(1062381); // The book is empty.
}
}
}
}
}
else // Set Price | Buy
{
if (m_Book.IsChildOf(m_From.Backpack))
{
m_From.Prompt = new SetPricePrompt(m_Book, bobEntry, m_Page, m_List);
m_From.SendLocalizedMessage(1062383); // Type in a price for the deed:
}
else if (m_Book.RootParent is PlayerVendor pv)
{
VendorItem vi = pv.GetVendorItem(m_Book);
if (vi?.IsForSale != false)
return;
int sizeOfDroppedBod = bobEntry is BOBLargeEntry largeEntry ? largeEntry.Entries.Length : 1;
int price = bobEntry.Price;
if (price == 0)
{
m_From.SendLocalizedMessage(1062382); // The deed selected is not available.
}
else
{
if (m_Book.Entries.Count > 0)
{
m_Page = GetPageForIndex(index, sizeOfDroppedBod);
m_From.SendGump(new BODBuyGump(m_From, m_Book, bobEntry, m_Page, price));
}
else
{
m_From.SendLocalizedMessage(1062381); // The book is emptz
}
}
}
}
break;
}
}
}
private class SetPricePrompt : Prompt
{
private BulkOrderBook m_Book;
private List<IBOBEntry> m_List;
private IBOBEntry m_Entry;
private int m_Page;
public SetPricePrompt(BulkOrderBook book, IBOBEntry entry, int page, List<IBOBEntry> list)
{
m_Book = book;
m_Entry = entry;
m_Page = page;
m_List = list;
}
public override void OnResponse(Mobile from, string text)
{
if (m_Entry != null && !m_Book.Entries.Contains(m_Entry))
{
from.SendLocalizedMessage(1062382); // The deed selected is not available.
return;
}
int price = Utility.ToInt32(text);
if (price < 0 || price > 250000000)
{
from.SendLocalizedMessage(1062390); // The price you requested is outrageous!
}
else if (m_Entry == null)
{
for (int i = 0; i < m_List.Count; ++i)
{
IBOBEntry entry = m_List[i];
if (!m_Book.Entries.Contains(entry))
continue;
entry.Price = price;
}
from.SendMessage("Deed prices set.");
if (from is PlayerMobile mobile)
mobile.SendGump(new BOBGump(mobile, m_Book, m_Page, m_List));
}
else
{
m_Entry.Price = price;
from.SendLocalizedMessage(1062384); // Deed price set.
if (from is PlayerMobile mobile)
mobile.SendGump(new BOBGump(mobile, m_Book, m_Page, m_List));
}
}
}
}
}

View file

@ -0,0 +1,106 @@
namespace Server.Engines.BulkOrders
{
public class BOBLargeEntry: IBOBEntry
{
public BOBLargeEntry(LargeBOD bod)
{
RequireExceptional = bod.RequireExceptional;
if (bod is LargeTailorBOD)
DeedType = BODType.Tailor;
else if (bod is LargeSmithBOD)
DeedType = BODType.Smith;
Material = bod.Material;
AmountMax = bod.AmountMax;
Entries = new BOBLargeSubEntry[bod.Entries.Length];
for (int i = 0; i < Entries.Length; ++i)
Entries[i] = new BOBLargeSubEntry(bod.Entries[i]);
}
public BOBLargeEntry(GenericReader reader)
{
int version = reader.ReadEncodedInt();
switch (version)
{
case 0:
{
RequireExceptional = reader.ReadBool();
DeedType = (BODType)reader.ReadEncodedInt();
Material = (BulkMaterialType)reader.ReadEncodedInt();
AmountMax = reader.ReadEncodedInt();
Price = reader.ReadEncodedInt();
Entries = new BOBLargeSubEntry[reader.ReadEncodedInt()];
for (int i = 0; i < Entries.Length; ++i)
Entries[i] = new BOBLargeSubEntry(reader);
break;
}
}
}
public bool RequireExceptional{ get; }
public BODType DeedType{ get; }
public BulkMaterialType Material{ get; }
public int AmountMax{ get; }
public int Price{ get; set; }
public BOBLargeSubEntry[] Entries{ get; }
public Item Reconstruct()
{
LargeBOD bod = null;
if (DeedType == BODType.Smith)
bod = new LargeSmithBOD(AmountMax, RequireExceptional, Material, ReconstructEntries());
else if (DeedType == BODType.Tailor)
bod = new LargeTailorBOD(AmountMax, RequireExceptional, Material, ReconstructEntries());
for (int i = 0; bod?.Entries.Length >= i; ++i)
bod.Entries[i].Owner = bod;
return bod;
}
private LargeBulkEntry[] ReconstructEntries()
{
LargeBulkEntry[] entries = new LargeBulkEntry[Entries.Length];
for (int i = 0; i < Entries.Length; ++i)
{
entries[i] = new LargeBulkEntry(null,
new SmallBulkEntry(Entries[i].ItemType, Entries[i].Number, Entries[i].Graphic)) { Amount = Entries[i].AmountCur };
}
return entries;
}
public void Serialize(GenericWriter writer)
{
writer.WriteEncodedInt(0); // version
writer.Write(RequireExceptional);
writer.WriteEncodedInt((int)DeedType);
writer.WriteEncodedInt((int)Material);
writer.WriteEncodedInt(AmountMax);
writer.WriteEncodedInt(Price);
writer.WriteEncodedInt(Entries.Length);
for (int i = 0; i < Entries.Length; ++i)
Entries[i].Serialize(writer);
}
}
}

View file

@ -0,0 +1,56 @@
using System;
namespace Server.Engines.BulkOrders
{
public class BOBLargeSubEntry
{
public BOBLargeSubEntry(LargeBulkEntry lbe)
{
ItemType = lbe.Details.Type;
AmountCur = lbe.Amount;
Number = lbe.Details.Number;
Graphic = lbe.Details.Graphic;
}
public BOBLargeSubEntry(GenericReader reader)
{
int version = reader.ReadEncodedInt();
switch (version)
{
case 0:
{
string type = reader.ReadString();
if (type != null)
ItemType = ScriptCompiler.FindTypeByFullName(type);
AmountCur = reader.ReadEncodedInt();
Number = reader.ReadEncodedInt();
Graphic = reader.ReadEncodedInt();
break;
}
}
}
public Type ItemType{ get; }
public int AmountCur{ get; }
public int Number{ get; }
public int Graphic{ get; }
public void Serialize(GenericWriter writer)
{
writer.WriteEncodedInt(0); // version
writer.Write(ItemType == null ? null : ItemType.FullName);
writer.WriteEncodedInt(AmountCur);
writer.WriteEncodedInt(Number);
writer.WriteEncodedInt(Graphic);
}
}
}

View file

@ -0,0 +1,100 @@
using System;
namespace Server.Engines.BulkOrders
{
public class BOBSmallEntry : IBOBEntry
{
public BOBSmallEntry(SmallBOD bod)
{
ItemType = bod.Type;
RequireExceptional = bod.RequireExceptional;
if (bod is SmallTailorBOD)
DeedType = BODType.Tailor;
else if (bod is SmallSmithBOD)
DeedType = BODType.Smith;
Material = bod.Material;
AmountCur = bod.AmountCur;
AmountMax = bod.AmountMax;
Number = bod.Number;
Graphic = bod.Graphic;
}
public BOBSmallEntry(GenericReader reader)
{
int version = reader.ReadEncodedInt();
switch (version)
{
case 0:
{
string type = reader.ReadString();
if (type != null)
ItemType = ScriptCompiler.FindTypeByFullName(type);
RequireExceptional = reader.ReadBool();
DeedType = (BODType)reader.ReadEncodedInt();
Material = (BulkMaterialType)reader.ReadEncodedInt();
AmountCur = reader.ReadEncodedInt();
AmountMax = reader.ReadEncodedInt();
Number = reader.ReadEncodedInt();
Graphic = reader.ReadEncodedInt();
Price = reader.ReadEncodedInt();
break;
}
}
}
public Type ItemType{ get; }
public bool RequireExceptional{ get; }
public BODType DeedType{ get; }
public BulkMaterialType Material{ get; }
public int AmountCur{ get; }
public int AmountMax{ get; }
public int Number{ get; }
public int Graphic{ get; }
public int Price{ get; set; }
public Item Reconstruct()
{
SmallBOD bod = null;
if (DeedType == BODType.Smith)
bod = new SmallSmithBOD(AmountCur, AmountMax, ItemType, Number, Graphic, RequireExceptional, Material);
else if (DeedType == BODType.Tailor)
bod = new SmallTailorBOD(AmountCur, AmountMax, ItemType, Number, Graphic, RequireExceptional, Material);
return bod;
}
public void Serialize(GenericWriter writer)
{
writer.WriteEncodedInt(0); // version
writer.Write(ItemType == null ? null : ItemType.FullName);
writer.Write(RequireExceptional);
writer.WriteEncodedInt((int)DeedType);
writer.WriteEncodedInt((int)Material);
writer.WriteEncodedInt(AmountCur);
writer.WriteEncodedInt(AmountMax);
writer.WriteEncodedInt(Number);
writer.WriteEncodedInt(Graphic);
writer.WriteEncodedInt(Price);
}
}
}

View file

@ -0,0 +1,122 @@
using Server.Gumps;
using Server.Items;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.BulkOrders
{
public class BODBuyGump : Gump
{
private BulkOrderBook m_Book;
private PlayerMobile m_From;
private IBOBEntry m_Entry;
private int m_Page;
private int m_Price;
public BODBuyGump(PlayerMobile from, BulkOrderBook book, IBOBEntry entry, int page, int price) : base(100, 200)
{
m_From = from;
m_Book = book;
m_Entry = entry;
m_Price = price;
m_Page = page;
AddPage(0);
AddBackground(100, 10, 300, 150, 5054);
AddHtmlLocalized(125, 20, 250, 24, 1019070); // You have agreed to purchase:
AddHtmlLocalized(125, 45, 250, 24, 1045151); // a bulk order deed
AddHtmlLocalized(125, 70, 250, 24, 1019071); // for the amount of:
AddLabel(125, 95, 0, price.ToString());
AddButton(250, 130, 4005, 4007, 1);
AddHtmlLocalized(282, 130, 100, 24, 1011012); // CANCEL
AddButton(120, 130, 4005, 4007, 2);
AddHtmlLocalized(152, 130, 100, 24, 1011036); // OKAY
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (info.ButtonID != 2)
{
m_From.SendLocalizedMessage(503207); // Cancelled purchase.
return;
}
if (!(m_Book.RootParent is PlayerVendor pv))
{
m_From.SendLocalizedMessage(1062382); // The deed selected is not available.
return;
}
if (!m_Book.Entries.Contains(m_Entry))
{
pv.SayTo(m_From, 1062382); // The deed selected is not available.
return;
}
int price = 0;
if (pv.GetVendorItem(m_Book)?.IsForSale == false)
price = m_Entry.Price;
if (price != m_Price)
{
pv.SayTo(m_From,
"The price has been been changed. If you like, you may offer to purchase the item again.");
return;
}
if (price == 0)
{
pv.SayTo(m_From, 1062382); // The deed selected is not available.
return;
}
Item item = m_Entry.Reconstruct();
pv.Say(m_From.Name);
Container pack = m_From.Backpack;
if (pack?.CheckHold(m_From, item, true, true, 0,
item.PileWeight + item.TotalWeight) != true)
{
pv.SayTo(m_From, 503204); // You do not have room in your backpack for this
m_From.SendGump(new BOBGump(m_From, m_Book, m_Page));
item.Delete();
}
else
{
if (pack.ConsumeTotal(typeof(Gold), price) || Banker.Withdraw(m_From, price))
{
m_Book.Entries.Remove(m_Entry);
m_Book.InvalidateProperties();
pv.HoldGold += price;
m_From.AddToBackpack(item);
m_From.SendLocalizedMessage(
1045152); // The bulk order deed has been placed in your backpack.
if (m_Book.Entries.Count / 5 < m_Book.ItemCount)
{
m_Book.ItemCount--;
m_Book.InvalidateItems();
}
if (m_Book.Entries.Count > 0)
m_From.SendGump(new BOBGump(m_From, m_Book, m_Page));
else
m_From.SendLocalizedMessage(1062381); // The book is empty.
}
else
{
pv.SayTo(m_From, 503205); // You cannot afford this item.
item.Delete();
}
}
}
}
}

View file

@ -0,0 +1,8 @@
namespace Server.Engines.BulkOrders
{
public enum BODType
{
Smith,
Tailor
}
}

View file

@ -0,0 +1,308 @@
using System.Collections.Generic;
using Server.Gumps;
using Server.Multis;
using Server.Prompts;
using Server.Mobiles;
using Server.ContextMenus;
using Server.Items;
namespace Server.Engines.BulkOrders
{
public class BulkOrderBook : Item, ISecurable
{
private string m_BookName;
[CommandProperty( AccessLevel.GameMaster )]
public string BookName
{
get => m_BookName;
set{ m_BookName = value; InvalidateProperties(); }
}
[CommandProperty( AccessLevel.GameMaster )]
public SecureLevel Level { get; set; }
public List<IBOBEntry> Entries { get; private set; }
public BOBFilter Filter { get; private set; }
public int ItemCount { get; set; }
[Constructible]
public BulkOrderBook() : base( 0x2259 )
{
Weight = 1.0;
LootType = LootType.Blessed;
Entries = new List<IBOBEntry>();
Filter = new BOBFilter();
Level = SecureLevel.CoOwners;
}
public override void OnDoubleClick( Mobile from )
{
if ( !from.InRange( GetWorldLocation(), 2 ) )
from.LocalOverheadMessage( Network.MessageType.Regular, 0x3B2, 1019045 ); // I can't reach that.
else if ( Entries.Count == 0 )
from.SendLocalizedMessage( 1062381 ); // The book is empty.
else if ( from is PlayerMobile mobile )
mobile.SendGump( new BOBGump( mobile, this ) );
}
public override void OnDoubleClickSecureTrade( Mobile from )
{
if ( !from.InRange( GetWorldLocation(), 2 ) )
{
from.SendLocalizedMessage( 500446 ); // That is too far away.
}
else if ( Entries.Count == 0 )
{
from.SendLocalizedMessage( 1062381 ); // The book is empty.
}
else
{
from.SendGump( new BOBGump( (PlayerMobile)from, this ) );
SecureTrade trade = GetSecureTradeCont()?.Trade;
if (trade?.From.Mobile == from )
trade.To.Mobile.SendGump( new BOBGump( (PlayerMobile)trade.To.Mobile, this ) );
else if (trade?.To.Mobile == from )
trade.From.Mobile.SendGump( new BOBGump( (PlayerMobile)trade.From.Mobile, this ) );
}
}
public override bool OnDragDrop( Mobile from, Item dropped )
{
if ( dropped is BaseBOD )
{
if ( !IsChildOf( from.Backpack ) )
{
from.SendLocalizedMessage( 1062385 ); // You must have the book in your backpack to add deeds to it.
return false;
}
if ( !from.Backpack.CheckHold( from, dropped, true, true ) )
return false;
if ( Entries.Count < 500 )
{
if ( dropped is LargeBOD bod )
Entries.Add( new BOBLargeEntry( bod ) );
else
Entries.Add( new BOBSmallEntry( (SmallBOD)dropped ) );
InvalidateProperties();
if ( Entries.Count / 5 > ItemCount )
{
ItemCount++;
InvalidateItems();
}
from.SendSound(0x42, GetWorldLocation());
from.SendLocalizedMessage( 1062386 ); // Deed added to book.
if ( from is PlayerMobile pm )
pm.SendGump( new BOBGump( pm, this ) );
dropped.Delete();
return true;
}
from.SendLocalizedMessage( 1062387 ); // The book is full of deeds.
return false;
}
from.SendLocalizedMessage( 1062388 ); // That is not a bulk order deed.
return false;
}
public override int GetTotal( TotalType type )
{
int total = base.GetTotal( type );
if ( type == TotalType.Items )
total = ItemCount;
return total;
}
public void InvalidateItems()
{
if ( RootParent is Mobile m )
{
m.UpdateTotals();
InvalidateContainers( Parent );
}
}
public void InvalidateContainers(IEntity parent)
{
if ( parent is Container c )
{
c.InvalidateProperties();
InvalidateContainers( c.Parent );
}
}
public BulkOrderBook( Serial serial ) : base( serial )
{
}
public override void Serialize( GenericWriter writer )
{
base.Serialize( writer );
writer.Write( 2 ); // version
writer.Write( ItemCount );
writer.Write( (int) Level );
writer.Write( m_BookName );
Filter.Serialize( writer );
writer.WriteEncodedInt( Entries.Count );
for ( int i = 0; i < Entries.Count; ++i )
{
object obj = Entries[i];
if ( obj is BOBLargeEntry entry )
{
writer.WriteEncodedInt( 0 );
entry.Serialize( writer );
}
else
{
writer.WriteEncodedInt( 1 );
((BOBSmallEntry)obj).Serialize( writer );
}
}
}
public override void Deserialize( GenericReader reader )
{
base.Deserialize( reader );
int version = reader.ReadInt();
switch ( version )
{
case 2:
{
ItemCount = reader.ReadInt();
goto case 1;
}
case 1:
{
Level = (SecureLevel)reader.ReadInt();
goto case 0;
}
case 0:
{
m_BookName = reader.ReadString();
Filter = new BOBFilter( reader );
int count = reader.ReadEncodedInt();
Entries = new List<IBOBEntry>( count );
for ( int i = 0; i < count; ++i )
{
int v = reader.ReadEncodedInt();
switch ( v )
{
case 0: Entries.Add( new BOBLargeEntry( reader ) ); break;
case 1: Entries.Add( new BOBSmallEntry( reader ) ); break;
}
}
break;
}
}
}
public override void GetProperties( ObjectPropertyList list )
{
base.GetProperties( list );
list.Add( 1062344, Entries.Count.ToString() ); // Deeds in book: ~1_val~
if ( !string.IsNullOrEmpty(m_BookName) )
list.Add( 1062481, m_BookName ); // Book Name: ~1_val~
}
public override void OnSingleClick(Mobile from)
{
base.OnSingleClick(from);
LabelTo(from, 1062344, Entries.Count.ToString()); // Deeds in book: ~1_val~
if (!string.IsNullOrEmpty(m_BookName))
LabelTo(from, 1062481, m_BookName);
}
public override void GetContextMenuEntries( Mobile from, List<ContextMenuEntry> list )
{
base.GetContextMenuEntries( from, list );
if ( from.CheckAlive() && IsChildOf( from.Backpack ) )
list.Add( new NameBookEntry( from, this ) );
SetSecureLevelEntry.AddTo( from, this, list );
}
private class NameBookEntry : ContextMenuEntry
{
private Mobile m_From;
private BulkOrderBook m_Book;
public NameBookEntry( Mobile from, BulkOrderBook book ) : base( 6216 )
{
m_From = from;
m_Book = book;
}
public override void OnClick()
{
if ( m_From.CheckAlive() && m_Book.IsChildOf( m_From.Backpack ) )
{
m_From.Prompt = new NameBookPrompt( m_Book );
m_From.SendLocalizedMessage( 1062479 ); // Type in the new name of the book:
}
}
}
private class NameBookPrompt : Prompt
{
private BulkOrderBook m_Book;
public NameBookPrompt( BulkOrderBook book )
{
m_Book = book;
}
public override void OnResponse( Mobile from, string text )
{
if ( text.Length > 40 )
text = text.Substring( 0, 40 );
if ( from.CheckAlive() && m_Book.IsChildOf( from.Backpack ) )
{
m_Book.BookName = Utility.FixHtml( text.Trim() );
from.SendLocalizedMessage( 1062480 ); // The bulk order book's name has been changed.
}
}
public override void OnCancel( Mobile from )
{
}
}
}
}

View file

@ -0,0 +1,12 @@
namespace Server.Engines.BulkOrders
{
public interface IBOBEntry
{
bool RequireExceptional{ get; }
BODType DeedType{ get; }
BulkMaterialType Material{ get; }
int AmountMax{ get; }
int Price{ get; set; }
Item Reconstruct();
}
}

View file

@ -0,0 +1,40 @@
using System;
using Server.Items;
namespace Server.Engines.BulkOrders
{
public enum BulkMaterialType
{
None,
DullCopper,
ShadowIron,
Copper,
Bronze,
Gold,
Agapite,
Verite,
Valorite,
Spined,
Horned,
Barbed
}
public enum BulkGenericType
{
Iron,
Cloth,
Leather
}
public class BGTClassifier
{
public static BulkGenericType Classify(BODType deedType, Type itemType)
{
if (deedType != BODType.Tailor)
return BulkGenericType.Iron;
return itemType == null || itemType.IsSubclassOf(typeof(BaseArmor)) || itemType.IsSubclassOf(typeof(BaseShoes))
? BulkGenericType.Leather : BulkGenericType.Cloth;
}
}
}

View file

@ -0,0 +1,175 @@
using Server.Mobiles;
namespace Server.Engines.BulkOrders
{
public abstract class LargeBOD : BaseBOD
{
private LargeBulkEntry[] m_Entries;
public LargeBulkEntry[] Entries
{
get => m_Entries;
set{ m_Entries = value; InvalidateProperties(); }
}
[CommandProperty( AccessLevel.GameMaster )]
public override bool Complete
{
get
{
for ( int i = 0; i < m_Entries.Length; ++i )
if ( m_Entries[i].Amount < AmountMax )
return false;
return true;
}
}
public override int LabelNumber => 1045151; // a bulk order deed
public LargeBOD(int hue, int amountMax, bool requireExeptional, BulkMaterialType material, LargeBulkEntry[] entries) :
base(hue, amountMax, requireExeptional, material)
{
m_Entries = entries;
}
public LargeBOD()
{
}
public override void GetProperties( ObjectPropertyList list )
{
base.GetProperties( list );
list.Add( 1060655 ); // large bulk order
if ( RequireExceptional )
list.Add( 1045141 ); // All items must be exceptional.
if ( Material != BulkMaterialType.None )
list.Add( LargeBODGump.GetMaterialNumberFor( Material ) ); // All items must be made with x material.
list.Add( 1060656, AmountMax.ToString() ); // amount to make: ~1_val~
for ( int i = 0; i < m_Entries.Length; ++i )
list.Add( 1060658 + i, "#{0}\t{1}", m_Entries[i].Details.Number, m_Entries[i].Amount ); // ~1_val~: ~2_val~
}
public override void OnDoubleClickNotAccessible( Mobile from )
{
OnDoubleClick( from );
}
public override void OnDoubleClickSecureTrade( Mobile from )
{
OnDoubleClick( from );
}
public override void OnDoubleClick( Mobile from )
{
if ( IsChildOf( from.Backpack ) || InSecureTrade || RootParent is PlayerVendor )
from.SendGump( new LargeBODGump( from, this ) );
else
from.SendLocalizedMessage( 1045156 ); // You must have the deed in your backpack to use it.
}
public override void EndCombine(Mobile from, Item item)
{
if (!(item is SmallBOD small))
{
from.SendLocalizedMessage(1045159); // That is not a bulk order.
return;
}
LargeBulkEntry entry = null;
for (int i = 0; i < m_Entries.Length; ++i)
{
if (m_Entries[i].Details.Type == small.Type)
{
entry = m_Entries[i];
break;
}
}
if (entry == null)
{
from.SendLocalizedMessage(1045160); // That is not a bulk order for this large request.
}
else if (RequireExceptional && !small.RequireExceptional)
{
from.SendLocalizedMessage(1045161); // Both orders must be of exceptional quality.
}
else if (Material >= BulkMaterialType.DullCopper && Material <= BulkMaterialType.Valorite &&
small.Material != Material)
{
from.SendLocalizedMessage(1045162); // Both orders must use the same ore type.
}
else if (Material >= BulkMaterialType.Spined && Material <= BulkMaterialType.Barbed &&
small.Material != Material)
{
from.SendLocalizedMessage(1049351); // Both orders must use the same leather type.
}
else if (AmountMax != small.AmountMax)
{
from.SendLocalizedMessage(1045163); // The two orders have different requested amounts and cannot be combined.
}
else if (small.AmountCur < small.AmountMax)
{
from.SendLocalizedMessage(1045164); // The order to combine with is not completed.
}
else if (entry.Amount >= AmountMax)
{
from.SendLocalizedMessage(
1045166); // The maximum amount of requested items have already been combined to this deed.
}
else
{
entry.Amount += small.AmountCur;
small.Delete();
from.SendLocalizedMessage(1045165); // The orders have been combined.
from.SendGump(new LargeBODGump(from, this));
if (!Complete)
BeginCombine(from);
}
}
public LargeBOD(Serial serial) : base(serial)
{
}
public override void Serialize( GenericWriter writer )
{
base.Serialize( writer );
writer.WriteEncodedInt( 0 ); // version
writer.Write( m_Entries.Length );
for ( int i = 0; i < m_Entries.Length; ++i )
m_Entries[i].Serialize( writer );
}
public override void Deserialize( GenericReader reader )
{
base.Deserialize( reader );
int version = reader.ReadEncodedInt();
switch ( version )
{
case 0:
{
m_Entries = new LargeBulkEntry[reader.ReadInt()];
for ( int i = 0; i < m_Entries.Length; ++i )
m_Entries[i] = new LargeBulkEntry( this, reader );
break;
}
}
}
}
}

View file

@ -0,0 +1,110 @@
using Server.Gumps;
using Server.Network;
namespace Server.Engines.BulkOrders
{
public class LargeBODAcceptGump : Gump
{
private LargeBOD m_Deed;
private Mobile m_From;
public LargeBODAcceptGump(Mobile from, LargeBOD deed) : base(50, 50)
{
m_From = from;
m_Deed = deed;
m_From.CloseGump<LargeBODAcceptGump>();
m_From.CloseGump<SmallBODAcceptGump>();
LargeBulkEntry[] entries = deed.Entries;
AddPage(0);
AddBackground(25, 10, 430, 240 + entries.Length * 24, 5054);
AddImageTiled(33, 20, 413, 221 + entries.Length * 24, 2624);
AddAlphaRegion(33, 20, 413, 221 + entries.Length * 24);
AddImage(20, 5, 10460);
AddImage(430, 5, 10460);
AddImage(20, 225 + entries.Length * 24, 10460);
AddImage(430, 225 + entries.Length * 24, 10460);
AddHtmlLocalized(180, 25, 120, 20, 1045134, 0x7FFF); // A large bulk order
AddHtmlLocalized(40, 48, 350, 20, 1045135, 0x7FFF); // Ah! Thanks for the goods! Would you help me out?
AddHtmlLocalized(40, 72, 210, 20, 1045138, 0x7FFF); // Amount to make:
AddLabel(250, 72, 1152, deed.AmountMax.ToString());
AddHtmlLocalized(40, 96, 120, 20, 1045137, 0x7FFF); // Items requested:
int y = 120;
for (int i = 0; i < entries.Length; ++i, y += 24)
AddHtmlLocalized(40, y, 210, 20, entries[i].Details.Number, 0x7FFF);
if (deed.RequireExceptional || deed.Material != BulkMaterialType.None)
{
AddHtmlLocalized(40, y, 210, 20, 1045140, 0x7FFF); // Special requirements to meet:
y += 24;
if (deed.RequireExceptional)
{
AddHtmlLocalized(40, y, 350, 20, 1045141, 0x7FFF); // All items must be exceptional.
y += 24;
}
if (deed.Material != BulkMaterialType.None)
{
AddHtmlLocalized(40, y, 350, 20, GetMaterialNumberFor(deed.Material), 0x7FFF); // All items must be made with x material.
y += 24;
}
}
AddHtmlLocalized(40, 192 + entries.Length * 24, 350, 20, 1045139, 0x7FFF); // Do you want to accept this order?
AddButton(100, 216 + entries.Length * 24, 4005, 4007, 1);
AddHtmlLocalized(135, 216 + entries.Length * 24, 120, 20, 1006044, 0x7FFF); // Ok
AddButton(275, 216 + entries.Length * 24, 4005, 4007, 0);
AddHtmlLocalized(310, 216 + entries.Length * 24, 120, 20, 1011012, 0x7FFF); // CANCEL
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (info.ButtonID == 1) // Ok
{
if (m_From.PlaceInBackpack(m_Deed))
{
m_From.SendLocalizedMessage(1045152); // The bulk order deed has been placed in your backpack.
}
else
{
m_From.SendLocalizedMessage(1045150); // There is not enough room in your backpack for the deed.
m_Deed.Delete();
}
}
else
{
m_Deed.Delete();
}
}
public override void OnServerClose(NetState owner)
{
if (m_Deed?.Deleted == false)
m_Deed.Delete();
}
public static int GetMaterialNumberFor(BulkMaterialType material)
{
if (material >= BulkMaterialType.DullCopper && material <= BulkMaterialType.Valorite)
return 1045142 + (material - BulkMaterialType.DullCopper);
if (material >= BulkMaterialType.Spined && material <= BulkMaterialType.Barbed)
return 1049348 + (material - BulkMaterialType.Spined);
return 0;
}
}
}

View file

@ -0,0 +1,98 @@
using Server.Gumps;
using Server.Network;
namespace Server.Engines.BulkOrders
{
public class LargeBODGump : Gump
{
private LargeBOD m_Deed;
private Mobile m_From;
public LargeBODGump(Mobile from, LargeBOD deed) : base(25, 25)
{
m_From = from;
m_Deed = deed;
m_From.CloseGump<LargeBODGump>();
m_From.CloseGump<SmallBODGump>();
LargeBulkEntry[] entries = deed.Entries;
AddPage(0);
AddBackground(50, 10, 455, 236 + entries.Length * 24, 5054);
AddImageTiled(58, 20, 438, 217 + entries.Length * 24, 2624);
AddAlphaRegion(58, 20, 438, 217 + entries.Length * 24);
AddImage(45, 5, 10460);
AddImage(480, 5, 10460);
AddImage(45, 221 + entries.Length * 24, 10460);
AddImage(480, 221 + entries.Length * 24, 10460);
AddHtmlLocalized(225, 25, 120, 20, 1045134, 0x7FFF); // A large bulk order
AddHtmlLocalized(75, 48, 250, 20, 1045138, 0x7FFF); // Amount to make:
AddLabel(275, 48, 1152, deed.AmountMax.ToString());
AddHtmlLocalized(75, 72, 120, 20, 1045137, 0x7FFF); // Items requested:
AddHtmlLocalized(275, 76, 200, 20, 1045153, 0x7FFF); // Amount finished:
int y = 96;
for (int i = 0; i < entries.Length; ++i)
{
LargeBulkEntry entry = entries[i];
SmallBulkEntry details = entry.Details;
AddHtmlLocalized(75, y, 210, 20, details.Number, 0x7FFF);
AddLabel(275, y, 0x480, entry.Amount.ToString());
y += 24;
}
if (deed.RequireExceptional || deed.Material != BulkMaterialType.None)
{
AddHtmlLocalized(75, y, 200, 20, 1045140, 0x7FFF); // Special requirements to meet:
y += 24;
}
if (deed.RequireExceptional)
{
AddHtmlLocalized(75, y, 300, 20, 1045141, 0x7FFF); // All items must be exceptional.
y += 24;
}
if (deed.Material != BulkMaterialType.None)
AddHtmlLocalized(75, y, 300, 20, GetMaterialNumberFor(deed.Material), 0x7FFF); // All items must be made with x material.
AddButton(125, 168 + entries.Length * 24, 4005, 4007, 2);
AddHtmlLocalized(160, 168 + entries.Length * 24, 300, 20, 1045155, 0x7FFF); // Combine this deed with another deed.
AddButton(125, 192 + entries.Length * 24, 4005, 4007, 1);
AddHtmlLocalized(160, 192 + entries.Length * 24, 120, 20, 1011441, 0x7FFF); // EXIT
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (m_Deed.Deleted || !m_Deed.IsChildOf(m_From.Backpack))
return;
if (info.ButtonID == 2) // Combine
{
m_From.SendGump(new LargeBODGump(m_From, m_Deed));
m_Deed.BeginCombine(m_From);
}
}
public static int GetMaterialNumberFor(BulkMaterialType material)
{
if (material >= BulkMaterialType.DullCopper && material <= BulkMaterialType.Valorite)
return 1045142 + (material - BulkMaterialType.DullCopper);
if (material >= BulkMaterialType.Spined && material <= BulkMaterialType.Barbed)
return 1049348 + (material - BulkMaterialType.Spined);
return 0;
}
}
}

View file

@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
namespace Server.Engines.BulkOrders
{
public class LargeBulkEntry
{
private int m_Amount;
public LargeBOD Owner { get; set; }
public int Amount
{
get => m_Amount;
set{ m_Amount = value; Owner?.InvalidateProperties(); }
}
public SmallBulkEntry Details { get; }
public static SmallBulkEntry[] LargeRing => GetEntries( "Blacksmith", "largering" );
public static SmallBulkEntry[] LargePlate => GetEntries( "Blacksmith", "largeplate" );
public static SmallBulkEntry[] LargeChain => GetEntries( "Blacksmith", "largechain" );
public static SmallBulkEntry[] LargeAxes => GetEntries( "Blacksmith", "largeaxes" );
public static SmallBulkEntry[] LargeFencing => GetEntries( "Blacksmith", "largefencing" );
public static SmallBulkEntry[] LargeMaces => GetEntries( "Blacksmith", "largemaces" );
public static SmallBulkEntry[] LargePolearms => GetEntries( "Blacksmith", "largepolearms" );
public static SmallBulkEntry[] LargeSwords => GetEntries( "Blacksmith", "largeswords" );
public static SmallBulkEntry[] BoneSet => GetEntries( "Tailoring", "boneset" );
public static SmallBulkEntry[] Farmer => GetEntries( "Tailoring", "farmer" );
public static SmallBulkEntry[] FemaleLeatherSet => GetEntries( "Tailoring", "femaleleatherset" );
public static SmallBulkEntry[] FisherGirl => GetEntries( "Tailoring", "fishergirl" );
public static SmallBulkEntry[] Gypsy => GetEntries( "Tailoring", "gypsy" );
public static SmallBulkEntry[] HatSet => GetEntries( "Tailoring", "hatset" );
public static SmallBulkEntry[] Jester => GetEntries( "Tailoring", "jester" );
public static SmallBulkEntry[] Lady => GetEntries( "Tailoring", "lady" );
public static SmallBulkEntry[] MaleLeatherSet => GetEntries( "Tailoring", "maleleatherset" );
public static SmallBulkEntry[] Pirate => GetEntries( "Tailoring", "pirate" );
public static SmallBulkEntry[] ShoeSet => GetEntries( "Tailoring", "shoeset" );
public static SmallBulkEntry[] StuddedSet => GetEntries( "Tailoring", "studdedset" );
public static SmallBulkEntry[] TownCrier => GetEntries( "Tailoring", "towncrier" );
public static SmallBulkEntry[] Wizard => GetEntries( "Tailoring", "wizard" );
private static Dictionary<string,Dictionary<string,SmallBulkEntry[]>> m_Cache;
public static SmallBulkEntry[] GetEntries( string type, string name )
{
if (m_Cache == null)
m_Cache = new Dictionary<string, Dictionary<string, SmallBulkEntry[]>>();
if (!m_Cache.TryGetValue( type, out Dictionary<string, SmallBulkEntry[]> table ))
m_Cache[type] = table = new Dictionary<string, SmallBulkEntry[]>();
if (!table.TryGetValue( name, out SmallBulkEntry[] entries ))
table[name] = entries = SmallBulkEntry.LoadEntries(type, name);
return entries;
}
public static LargeBulkEntry[] ConvertEntries( LargeBOD owner, SmallBulkEntry[] small )
{
LargeBulkEntry[] large = new LargeBulkEntry[small.Length];
for ( int i = 0; i < small.Length; ++i )
large[i] = new LargeBulkEntry( owner, small[i] );
return large;
}
public LargeBulkEntry( LargeBOD owner, SmallBulkEntry details )
{
Owner = owner;
Details = details;
}
public LargeBulkEntry( LargeBOD owner, GenericReader reader )
{
Owner = owner;
m_Amount = reader.ReadInt();
Type realType = null;
string type = reader.ReadString();
if ( type != null )
realType = ScriptCompiler.FindTypeByFullName( type );
Details = new SmallBulkEntry( realType, reader.ReadInt(), reader.ReadInt() );
}
public void Serialize( GenericWriter writer )
{
writer.Write( m_Amount );
writer.Write( Details.Type == null ? null : Details.Type.FullName );
writer.Write( Details.Number );
writer.Write( Details.Graphic );
}
}
}

View file

@ -0,0 +1,102 @@
namespace Server.Engines.BulkOrders
{
public class LargeSmithBOD : LargeBOD
{
public static double[] m_BlacksmithMaterialChances =
{
0.501953125, // None
0.250000000, // Dull Copper
0.125000000, // Shadow Iron
0.062500000, // Copper
0.031250000, // Bronze
0.015625000, // Gold
0.007812500, // Agapite
0.003906250, // Verite
0.001953125 // Valorite
};
[Constructible]
public LargeSmithBOD()
{
LargeBulkEntry[] entries;
bool useMaterials = true;
int rand = Utility.Random(8);
switch (rand)
{
default:
case 0:
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.LargeRing);
break;
case 1:
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.LargePlate);
break;
case 2:
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.LargeChain);
break;
case 3:
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.LargeAxes);
break;
case 4:
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.LargeFencing);
break;
case 5:
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.LargeMaces);
break;
case 6:
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.LargePolearms);
break;
case 7:
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.LargeSwords);
break;
}
if (rand > 2 && rand < 8)
useMaterials = false;
int hue = 0x44E;
int amountMax = Utility.RandomList(10, 15, 20, 20);
bool reqExceptional = 0.825 > Utility.RandomDouble();
BulkMaterialType material = useMaterials ? GetRandomMaterial(BulkMaterialType.DullCopper, m_BlacksmithMaterialChances)
: BulkMaterialType.None;
Hue = hue;
AmountMax = amountMax;
Entries = entries;
RequireExceptional = reqExceptional;
Material = material;
}
public LargeSmithBOD(int amountMax, bool reqExceptional, BulkMaterialType mat, LargeBulkEntry[] entries)
: base(0x44E, amountMax, reqExceptional, mat, entries)
{
}
public LargeSmithBOD(Serial serial) : base(serial)
{
}
public override int ComputeFame() => SmithRewardCalculator.Instance.ComputeFame(this);
public override int ComputeGold() => SmithRewardCalculator.Instance.ComputeGold(this);
public override RewardGroup GetRewardGroup() =>
SmithRewardCalculator.Instance.LookupRewards(SmithRewardCalculator.Instance.ComputePoints(this));
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,121 @@
namespace Server.Engines.BulkOrders
{
public class LargeTailorBOD : LargeBOD
{
public static double[] m_TailoringMaterialChances =
{
0.857421875, // None
0.125000000, // Spined
0.015625000, // Horned
0.001953125 // Barbed
};
[Constructible]
public LargeTailorBOD()
{
LargeBulkEntry[] entries;
bool useMaterials = false;
switch (Utility.Random(14))
{
default:
case 0:
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.Farmer);
break;
case 1:
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.FemaleLeatherSet);
useMaterials = true;
break;
case 2:
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.FisherGirl);
break;
case 3:
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.Gypsy);
break;
case 4:
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.HatSet);
break;
case 5:
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.Jester);
break;
case 6:
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.Lady);
break;
case 7:
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.MaleLeatherSet);
useMaterials = true;
break;
case 8:
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.Pirate);
break;
case 9:
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.ShoeSet);
useMaterials = Core.ML;
break;
case 10:
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.StuddedSet);
useMaterials = true;
break;
case 11:
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.TownCrier);
break;
case 12:
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.Wizard);
break;
case 13:
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.BoneSet);
useMaterials = true;
break;
}
int hue = 0x483;
int amountMax = Utility.RandomList(10, 15, 20, 20);
bool reqExceptional = 0.825 > Utility.RandomDouble();
BulkMaterialType material = useMaterials ? GetRandomMaterial(BulkMaterialType.Spined, m_TailoringMaterialChances)
: BulkMaterialType.None;
Hue = hue;
AmountMax = amountMax;
Entries = entries;
RequireExceptional = reqExceptional;
Material = material;
}
public LargeTailorBOD(int amountMax, bool reqExceptional, BulkMaterialType mat, LargeBulkEntry[] entries)
: base(0x483, amountMax, reqExceptional, mat, entries)
{
}
public LargeTailorBOD(Serial serial) : base(serial)
{
}
public override int ComputeFame()
{
return TailorRewardCalculator.Instance.ComputeFame(this);
}
public override int ComputeGold()
{
return TailorRewardCalculator.Instance.ComputeGold(this);
}
public override RewardGroup GetRewardGroup() =>
TailorRewardCalculator.Instance.LookupRewards(TailorRewardCalculator.Instance.ComputePoints(this));
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,745 @@
using System;
using Server.Items;
namespace Server.Engines.BulkOrders
{
public delegate Item ConstructCallback(int type);
public sealed class RewardType
{
public RewardType(int points, params Type[] types)
{
Points = points;
Types = types;
}
public int Points{ get; }
public Type[] Types{ get; }
public bool Contains(Type type)
{
for (int i = 0; i < Types.Length; ++i)
if (Types[i] == type)
return true;
return false;
}
}
public sealed class RewardItem
{
public RewardItem(int weight, ConstructCallback constructor, int type = 0)
{
Weight = weight;
Constructor = constructor;
Type = type;
}
public int Weight{ get; }
public ConstructCallback Constructor{ get; }
public int Type{ get; }
public Item Construct()
{
try
{
return Constructor(Type);
}
catch
{
return null;
}
}
}
public sealed class RewardGroup
{
public RewardGroup(int points, params RewardItem[] items)
{
Points = points;
Items = items;
}
public int Points{ get; }
public RewardItem[] Items{ get; }
public RewardItem AcquireItem()
{
if (Items.Length == 0)
return null;
if (Items.Length == 1)
return Items[0];
int totalWeight = 0;
for (int i = 0; i < Items.Length; ++i)
totalWeight += Items[i].Weight;
int randomWeight = Utility.Random(totalWeight);
for (int i = 0; i < Items.Length; ++i)
{
RewardItem item = Items[i];
if (randomWeight < item.Weight)
return item;
randomWeight -= item.Weight;
}
return null;
}
}
public abstract class RewardCalculator
{
public RewardGroup[] Groups{ get; set; }
public abstract int ComputePoints(int quantity, bool exceptional, BulkMaterialType material, int itemCount,
Type type);
public abstract int ComputeGold(int quantity, bool exceptional, BulkMaterialType material, int itemCount, Type type);
public virtual int ComputeFame(SmallBOD bod)
{
int points = ComputePoints(bod) / 50;
return points * points;
}
public virtual int ComputeFame(LargeBOD bod)
{
int points = ComputePoints(bod) / 50;
return points * points;
}
public virtual int ComputePoints(SmallBOD bod)
{
return ComputePoints(bod.AmountMax, bod.RequireExceptional, bod.Material, 1, bod.Type);
}
public virtual int ComputePoints(LargeBOD bod)
{
return ComputePoints(bod.AmountMax, bod.RequireExceptional, bod.Material, bod.Entries.Length,
bod.Entries[0].Details.Type);
}
public virtual int ComputeGold(SmallBOD bod)
{
return ComputeGold(bod.AmountMax, bod.RequireExceptional, bod.Material, 1, bod.Type);
}
public virtual int ComputeGold(LargeBOD bod)
{
return ComputeGold(bod.AmountMax, bod.RequireExceptional, bod.Material, bod.Entries.Length,
bod.Entries[0].Details.Type);
}
public virtual RewardGroup LookupRewards(int points)
{
for (int i = Groups.Length - 1; i >= 1; --i)
{
RewardGroup group = Groups[i];
if (points >= group.Points)
return group;
}
return Groups[0];
}
public virtual int LookupTypePoints(RewardType[] types, Type type)
{
for (int i = 0; i < types.Length; ++i)
if (types[i].Contains(type))
return types[i].Points;
return 0;
}
}
public sealed class SmithRewardCalculator : RewardCalculator
{
private static readonly ConstructCallback SturdyShovel = CreateSturdyShovel;
private static readonly ConstructCallback SturdyPickaxe = CreateSturdyPickaxe;
private static readonly ConstructCallback MiningGloves = CreateMiningGloves;
private static readonly ConstructCallback GargoylesPickaxe = CreateGargoylesPickaxe;
private static readonly ConstructCallback ProspectorsTool = CreateProspectorsTool;
private static readonly ConstructCallback PowderOfTemperament = CreatePowderOfTemperament;
private static readonly ConstructCallback RunicHammer = CreateRunicHammer;
private static readonly ConstructCallback PowerScroll = CreatePowerScroll;
private static readonly ConstructCallback ColoredAnvil = CreateColoredAnvil;
private static readonly ConstructCallback AncientHammer = CreateAncientHammer;
public static readonly SmithRewardCalculator Instance = new SmithRewardCalculator();
private static int[][][] m_GoldTable =
{
new[] // 1-part (regular)
{
new[] { 150, 250, 250, 400, 400, 750, 750, 1200, 1200 },
new[] { 225, 375, 375, 600, 600, 1125, 1125, 1800, 1800 },
new[] { 300, 500, 750, 800, 1050, 1500, 2250, 2400, 4000 }
},
new[] // 1-part (exceptional)
{
new[] { 250, 400, 400, 750, 750, 1500, 1500, 3000, 3000 },
new[] { 375, 600, 600, 1125, 1125, 2250, 2250, 4500, 4500 },
new[] { 500, 800, 1200, 1500, 2500, 3000, 6000, 6000, 12000 }
},
new[] // Ringmail (regular)
{
new[] { 3000, 5000, 5000, 7500, 7500, 10000, 10000, 15000, 15000 },
new[] { 4500, 7500, 7500, 11250, 11500, 15000, 15000, 22500, 22500 },
new[] { 6000, 10000, 15000, 15000, 20000, 20000, 30000, 30000, 50000 }
},
new[] // Ringmail (exceptional)
{
new[] { 5000, 10000, 10000, 15000, 15000, 25000, 25000, 50000, 50000 },
new[] { 7500, 15000, 15000, 22500, 22500, 37500, 37500, 75000, 75000 },
new[] { 10000, 20000, 30000, 30000, 50000, 50000, 100000, 100000, 200000 }
},
new[] // Chainmail (regular)
{
new[] { 4000, 7500, 7500, 10000, 10000, 15000, 15000, 25000, 25000 },
new[] { 6000, 11250, 11250, 15000, 15000, 22500, 22500, 37500, 37500 },
new[] { 8000, 15000, 20000, 20000, 30000, 30000, 50000, 50000, 100000 }
},
new[] // Chainmail (exceptional)
{
new[] { 7500, 15000, 15000, 25000, 25000, 50000, 50000, 100000, 100000 },
new[] { 11250, 22500, 22500, 37500, 37500, 75000, 75000, 150000, 150000 },
new[] { 15000, 30000, 50000, 50000, 100000, 100000, 200000, 200000, 200000 }
},
new[] // Platemail (regular)
{
new[] { 5000, 10000, 10000, 15000, 15000, 25000, 25000, 50000, 50000 },
new[] { 7500, 15000, 15000, 22500, 22500, 37500, 37500, 75000, 75000 },
new[] { 10000, 20000, 30000, 30000, 50000, 50000, 100000, 100000, 200000 }
},
new[] // Platemail (exceptional)
{
new[] { 10000, 25000, 25000, 50000, 50000, 100000, 100000, 100000, 100000 },
new[] { 15000, 37500, 37500, 75000, 75000, 150000, 150000, 150000, 150000 },
new[] { 20000, 50000, 100000, 100000, 200000, 200000, 200000, 200000, 200000 }
},
new[] // 2-part weapons (regular)
{
new[] { 3000, 0, 0, 0, 0, 0, 0, 0, 0 },
new[] { 4500, 0, 0, 0, 0, 0, 0, 0, 0 },
new[] { 6000, 0, 0, 0, 0, 0, 0, 0, 0 }
},
new[] // 2-part weapons (exceptional)
{
new[] { 5000, 0, 0, 0, 0, 0, 0, 0, 0 },
new[] { 7500, 0, 0, 0, 0, 0, 0, 0, 0 },
new[] { 10000, 0, 0, 0, 0, 0, 0, 0, 0 }
},
new[] // 5-part weapons (regular)
{
new[] { 4000, 0, 0, 0, 0, 0, 0, 0, 0 },
new[] { 6000, 0, 0, 0, 0, 0, 0, 0, 0 },
new[] { 8000, 0, 0, 0, 0, 0, 0, 0, 0 }
},
new[] // 5-part weapons (exceptional)
{
new[] { 7500, 0, 0, 0, 0, 0, 0, 0, 0 },
new[] { 11250, 0, 0, 0, 0, 0, 0, 0, 0 },
new[] { 15000, 0, 0, 0, 0, 0, 0, 0, 0 }
},
new[] // 6-part weapons (regular)
{
new[] { 4000, 0, 0, 0, 0, 0, 0, 0, 0 },
new[] { 6000, 0, 0, 0, 0, 0, 0, 0, 0 },
new[] { 10000, 0, 0, 0, 0, 0, 0, 0, 0 }
},
new[] // 6-part weapons (exceptional)
{
new[] { 7500, 0, 0, 0, 0, 0, 0, 0, 0 },
new[] { 11250, 0, 0, 0, 0, 0, 0, 0, 0 },
new[] { 15000, 0, 0, 0, 0, 0, 0, 0, 0 }
}
};
private RewardType[] m_Types =
{
// Armors
new RewardType(200, typeof(RingmailGloves), typeof(RingmailChest), typeof(RingmailArms), typeof(RingmailLegs)),
new RewardType(300, typeof(ChainCoif), typeof(ChainLegs), typeof(ChainChest)),
new RewardType(400, typeof(PlateArms), typeof(PlateLegs), typeof(PlateHelm), typeof(PlateGorget),
typeof(PlateGloves), typeof(PlateChest)),
// Weapons
new RewardType(200, typeof(Bardiche), typeof(Halberd)),
new RewardType(300, typeof(Dagger), typeof(ShortSpear), typeof(Spear), typeof(WarFork),
typeof(Kryss)), //OSI put the dagger in there. Odd, ain't it.
new RewardType(350, typeof(Axe), typeof(BattleAxe), typeof(DoubleAxe), typeof(ExecutionersAxe),
typeof(LargeBattleAxe), typeof(TwoHandedAxe)),
new RewardType(350, typeof(Broadsword), typeof(Cutlass), typeof(Katana), typeof(Longsword),
typeof(Scimitar), /*typeof( ThinLongsword ),*/ typeof(VikingSword)),
new RewardType(350, typeof(WarAxe), typeof(HammerPick), typeof(Mace), typeof(Maul), typeof(WarHammer),
typeof(WarMace))
};
public SmithRewardCalculator()
{
Groups = new[]
{
new RewardGroup(0, new RewardItem(1, SturdyShovel)),
new RewardGroup(25, new RewardItem(1, SturdyPickaxe)),
new RewardGroup(50, new RewardItem(45, SturdyShovel), new RewardItem(45, SturdyPickaxe),
new RewardItem(10, MiningGloves, 1)),
new RewardGroup(200, new RewardItem(45, GargoylesPickaxe), new RewardItem(45, ProspectorsTool),
new RewardItem(10, MiningGloves, 3)),
new RewardGroup(400, new RewardItem(2, GargoylesPickaxe), new RewardItem(2, ProspectorsTool),
new RewardItem(1, PowderOfTemperament)),
new RewardGroup(450, new RewardItem(9, PowderOfTemperament), new RewardItem(1, MiningGloves, 5)),
new RewardGroup(500, new RewardItem(1, RunicHammer, 1)),
new RewardGroup(550, new RewardItem(3, RunicHammer, 1), new RewardItem(2, RunicHammer, 2)),
new RewardGroup(600, new RewardItem(1, RunicHammer, 2)),
new RewardGroup(625, new RewardItem(3, RunicHammer, 2), new RewardItem(6, PowerScroll, 5),
new RewardItem(1, ColoredAnvil)),
new RewardGroup(650, new RewardItem(1, RunicHammer, 3)),
new RewardGroup(675, new RewardItem(1, ColoredAnvil), new RewardItem(6, PowerScroll, 10),
new RewardItem(3, RunicHammer, 3)),
new RewardGroup(700, new RewardItem(1, RunicHammer, 4)),
new RewardGroup(750, new RewardItem(1, AncientHammer, 10)),
new RewardGroup(800, new RewardItem(1, PowerScroll, 15)),
new RewardGroup(850, new RewardItem(1, AncientHammer, 15)),
new RewardGroup(900, new RewardItem(1, PowerScroll, 20)),
new RewardGroup(950, new RewardItem(1, RunicHammer, 5)),
new RewardGroup(1000, new RewardItem(1, AncientHammer, 30)),
new RewardGroup(1050, new RewardItem(1, RunicHammer, 6)),
new RewardGroup(1100, new RewardItem(1, AncientHammer, 60)),
new RewardGroup(1150, new RewardItem(1, RunicHammer, 7)),
new RewardGroup(1200, new RewardItem(1, RunicHammer, 8))
};
}
public override int ComputePoints(int quantity, bool exceptional, BulkMaterialType material, int itemCount,
Type type)
{
int points = 0;
if (quantity == 10)
points += 10;
else if (quantity == 15)
points += 25;
else if (quantity == 20)
points += 50;
if (exceptional)
points += 200;
if (itemCount > 1)
points += LookupTypePoints(m_Types, type);
if (material >= BulkMaterialType.DullCopper && material <= BulkMaterialType.Valorite)
points += 200 + 50 * (material - BulkMaterialType.DullCopper);
return points;
}
private int ComputeType(Type type, int itemCount)
{
// Item count of 1 means it's a small BOD.
if (itemCount == 1)
return 0;
int typeIdx = 0;
// Loop through the RewardTypes defined earlier and find the correct one.
for (; typeIdx < 7; ++typeIdx)
if (m_Types[typeIdx].Contains(type))
break;
// Types 5, 6 and 7 are Large Weapon BODs with the same rewards.
if (typeIdx > 5)
typeIdx = 5;
return (typeIdx + 1) * 2;
}
public override int ComputeGold(int quantity, bool exceptional, BulkMaterialType material, int itemCount, Type type)
{
int[][][] goldTable = m_GoldTable;
int typeIndex = ComputeType(type, itemCount);
int quanIndex = quantity == 20 ? 2 : quantity == 15 ? 1 : 0;
int mtrlIndex = material >= BulkMaterialType.DullCopper && material <= BulkMaterialType.Valorite
? 1 + (material - BulkMaterialType.DullCopper)
: 0;
if (exceptional)
typeIndex++;
int gold = goldTable[typeIndex][quanIndex][mtrlIndex];
int min = gold * 9 / 10;
int max = gold * 10 / 9;
return Utility.RandomMinMax(min, max);
}
#region Constructors
private static Item CreateSturdyShovel(int type)
{
return new SturdyShovel();
}
private static Item CreateSturdyPickaxe(int type)
{
return new SturdyPickaxe();
}
private static Item CreateMiningGloves(int type)
{
switch (type)
{
case 1:
return new LeatherGlovesOfMining(1);
case 3:
return new StuddedGlovesOfMining(3);
case 5:
return new RingmailGlovesOfMining(5);
default:
throw new InvalidOperationException();
}
}
private static Item CreateGargoylesPickaxe(int type)
{
return new GargoylesPickaxe();
}
private static Item CreateProspectorsTool(int type)
{
return new ProspectorsTool();
}
private static Item CreatePowderOfTemperament(int type)
{
return new PowderOfTemperament();
}
private static Item CreateRunicHammer(int type)
{
if (type >= 1 && type <= 8)
return new RunicHammer(CraftResource.Iron + type, Core.AOS ? 55 - type * 5 : 50);
throw new InvalidOperationException();
}
private static Item CreatePowerScroll(int type)
{
if (type == 5 || type == 10 || type == 15 || type == 20)
return new PowerScroll(SkillName.Blacksmith, 100 + type);
throw new InvalidOperationException();
}
private static Item CreateColoredAnvil(int type)
{
// Generate an anvil deed, not an actual anvil.
//return new ColoredAnvilDeed();
return new ColoredAnvil();
}
private static Item CreateAncientHammer(int type)
{
if (type == 10 || type == 15 || type == 30 || type == 60)
return new AncientSmithyHammer(type);
throw new InvalidOperationException();
}
#endregion
}
public sealed class TailorRewardCalculator : RewardCalculator
{
private static readonly ConstructCallback Cloth = CreateCloth;
private static readonly ConstructCallback Sandals = CreateSandals;
private static readonly ConstructCallback StretchedHide = CreateStretchedHide;
private static readonly ConstructCallback RunicKit = CreateRunicKit;
private static readonly ConstructCallback Tapestry = CreateTapestry;
private static readonly ConstructCallback PowerScroll = CreatePowerScroll;
private static readonly ConstructCallback BearRug = CreateBearRug;
private static readonly ConstructCallback ClothingBlessDeed = CreateCBD;
public static readonly TailorRewardCalculator Instance = new TailorRewardCalculator();
private static int[][][] m_AosGoldTable =
{
new[] // 1-part (regular)
{
new[] { 150, 150, 300, 300 },
new[] { 225, 225, 450, 450 },
new[] { 300, 400, 600, 750 }
},
new[] // 1-part (exceptional)
{
new[] { 300, 300, 600, 600 },
new[] { 450, 450, 900, 900 },
new[] { 600, 750, 1200, 1800 }
},
new[] // 4-part (regular)
{
new[] { 4000, 4000, 5000, 5000 },
new[] { 6000, 6000, 7500, 7500 },
new[] { 8000, 10000, 10000, 15000 }
},
new[] // 4-part (exceptional)
{
new[] { 5000, 5000, 7500, 7500 },
new[] { 7500, 7500, 11250, 11250 },
new[] { 10000, 15000, 15000, 20000 }
},
new[] // 5-part (regular)
{
new[] { 5000, 5000, 7500, 7500 },
new[] { 7500, 7500, 11250, 11250 },
new[] { 10000, 15000, 15000, 20000 }
},
new[] // 5-part (exceptional)
{
new[] { 7500, 7500, 10000, 10000 },
new[] { 11250, 11250, 15000, 15000 },
new[] { 15000, 20000, 20000, 30000 }
},
new[] // 6-part (regular)
{
new[] { 7500, 7500, 10000, 10000 },
new[] { 11250, 11250, 15000, 15000 },
new[] { 15000, 20000, 20000, 30000 }
},
new[] // 6-part (exceptional)
{
new[] { 10000, 10000, 15000, 15000 },
new[] { 15000, 15000, 22500, 22500 },
new[] { 20000, 30000, 30000, 50000 }
}
};
private static int[][][] m_OldGoldTable =
{
new[] // 1-part (regular)
{
new[] { 150, 150, 300, 300 },
new[] { 225, 225, 450, 450 },
new[] { 300, 400, 600, 750 }
},
new[] // 1-part (exceptional)
{
new[] { 300, 300, 600, 600 },
new[] { 450, 450, 900, 900 },
new[] { 600, 750, 1200, 1800 }
},
new[] // 4-part (regular)
{
new[] { 3000, 3000, 4000, 4000 },
new[] { 4500, 4500, 6000, 6000 },
new[] { 6000, 8000, 8000, 10000 }
},
new[] // 4-part (exceptional)
{
new[] { 4000, 4000, 5000, 5000 },
new[] { 6000, 6000, 7500, 7500 },
new[] { 8000, 10000, 10000, 15000 }
},
new[] // 5-part (regular)
{
new[] { 4000, 4000, 5000, 5000 },
new[] { 6000, 6000, 7500, 7500 },
new[] { 8000, 10000, 10000, 15000 }
},
new[] // 5-part (exceptional)
{
new[] { 5000, 5000, 7500, 7500 },
new[] { 7500, 7500, 11250, 11250 },
new[] { 10000, 15000, 15000, 20000 }
},
new[] // 6-part (regular)
{
new[] { 5000, 5000, 7500, 7500 },
new[] { 7500, 7500, 11250, 11250 },
new[] { 10000, 15000, 15000, 20000 }
},
new[] // 6-part (exceptional)
{
new[] { 7500, 7500, 10000, 10000 },
new[] { 11250, 11250, 15000, 15000 },
new[] { 15000, 20000, 20000, 30000 }
}
};
public TailorRewardCalculator()
{
Groups = new[]
{
new RewardGroup(0, new RewardItem(1, Cloth)),
new RewardGroup(50, new RewardItem(1, Cloth, 1)),
new RewardGroup(100, new RewardItem(1, Cloth, 2)),
new RewardGroup(150, new RewardItem(9, Cloth, 3), new RewardItem(1, Sandals)),
new RewardGroup(200, new RewardItem(4, Cloth, 4), new RewardItem(1, Sandals)),
new RewardGroup(300, new RewardItem(1, StretchedHide)),
new RewardGroup(350, new RewardItem(1, RunicKit, 1)),
new RewardGroup(400, new RewardItem(2, PowerScroll, 5), new RewardItem(3, Tapestry)),
new RewardGroup(450, new RewardItem(1, BearRug)),
new RewardGroup(500, new RewardItem(1, PowerScroll, 10)),
new RewardGroup(550, new RewardItem(1, ClothingBlessDeed)),
new RewardGroup(575, new RewardItem(1, PowerScroll, 15)),
new RewardGroup(600, new RewardItem(1, RunicKit, 2)),
new RewardGroup(650, new RewardItem(1, PowerScroll, 20)),
new RewardGroup(700, new RewardItem(1, RunicKit, 3))
};
}
public override int ComputePoints(int quantity, bool exceptional, BulkMaterialType material, int itemCount,
Type type)
{
int points = 0;
if (quantity == 10)
points += 10;
else if (quantity == 15)
points += 25;
else if (quantity == 20)
points += 50;
if (exceptional)
points += 100;
if (itemCount == 4)
points += 300;
else if (itemCount == 5)
points += 400;
else if (itemCount == 6)
points += 500;
if (material == BulkMaterialType.Spined)
points += 50;
else if (material == BulkMaterialType.Horned)
points += 100;
else if (material == BulkMaterialType.Barbed)
points += 150;
return points;
}
public override int ComputeGold(int quantity, bool exceptional, BulkMaterialType material, int itemCount, Type type)
{
int[][][] goldTable = Core.AOS ? m_AosGoldTable : m_OldGoldTable;
int typeIndex = (itemCount == 6 ? 3 : itemCount == 5 ? 2 : itemCount == 4 ? 1 : 0) * 2 + (exceptional ? 1 : 0);
int quanIndex = quantity == 20 ? 2 : quantity == 15 ? 1 : 0;
int mtrlIndex = material == BulkMaterialType.Barbed ? 3 :
material == BulkMaterialType.Horned ? 2 :
material == BulkMaterialType.Spined ? 1 : 0;
int gold = goldTable[typeIndex][quanIndex][mtrlIndex];
int min = gold * 9 / 10;
int max = gold * 10 / 9;
return Utility.RandomMinMax(min, max);
}
#region Constructors
private static int[][] m_ClothHues =
{
new[] { 0x483, 0x48C, 0x488, 0x48A },
new[] { 0x495, 0x48B, 0x486, 0x485 },
new[] { 0x48D, 0x490, 0x48E, 0x491 },
new[] { 0x48F, 0x494, 0x484, 0x497 },
new[] { 0x489, 0x47F, 0x482, 0x47E }
};
private static Item CreateCloth(int type)
{
if (type >= 0 && type < m_ClothHues.Length)
{
UncutCloth cloth = new UncutCloth(100);
cloth.Hue = m_ClothHues[type][Utility.Random(m_ClothHues[type].Length)];
return cloth;
}
throw new InvalidOperationException();
}
private static int[] m_SandalHues =
{
0x489, 0x47F, 0x482,
0x47E, 0x48F, 0x494,
0x484, 0x497
};
private static Item CreateSandals(int type)
{
return new Sandals(m_SandalHues[Utility.Random(m_SandalHues.Length)]);
}
private static Item CreateStretchedHide(int type)
{
switch (Utility.Random(4))
{
default:
return new SmallStretchedHideEastDeed();
case 1: return new SmallStretchedHideSouthDeed();
case 2: return new MediumStretchedHideEastDeed();
case 3: return new MediumStretchedHideSouthDeed();
}
}
private static Item CreateTapestry(int type)
{
switch (Utility.Random(4))
{
default:
return new LightFlowerTapestryEastDeed();
case 1: return new LightFlowerTapestrySouthDeed();
case 2: return new DarkFlowerTapestryEastDeed();
case 3: return new DarkFlowerTapestrySouthDeed();
}
}
private static Item CreateBearRug(int type)
{
switch (Utility.Random(4))
{
default:
return new BrownBearRugEastDeed();
case 1: return new BrownBearRugSouthDeed();
case 2: return new PolarBearRugEastDeed();
case 3: return new PolarBearRugSouthDeed();
}
}
private static Item CreateRunicKit(int type)
{
if (type >= 1 && type <= 3)
return new RunicSewingKit(CraftResource.RegularLeather + type, 60 - type * 15);
throw new InvalidOperationException();
}
private static Item CreatePowerScroll(int type)
{
if (type == 5 || type == 10 || type == 15 || type == 20)
return new PowerScroll(SkillName.Tailoring, 100 + type);
throw new InvalidOperationException();
}
private static Item CreateCBD(int type)
{
return new ClothingBlessDeed();
}
#endregion
}
}

View file

@ -0,0 +1,213 @@
using System;
using Server.Items;
using Server.Mobiles;
namespace Server.Engines.BulkOrders
{
public abstract class SmallBOD : BaseBOD
{
private int m_AmountCur;
private int m_Number;
public SmallBOD(int hue, int amountCur, int amountMax, Type type, int number, int graphic, bool requireExeptional,
BulkMaterialType material) : base(hue, amountMax, requireExeptional, material)
{
Type = type;
Graphic = graphic;
m_AmountCur = amountCur;
m_Number = number;
}
public SmallBOD()
{
}
public SmallBOD(Serial serial) : base(serial)
{
}
[CommandProperty(AccessLevel.GameMaster)]
public int AmountCur
{
get => m_AmountCur;
set
{
m_AmountCur = value;
InvalidateProperties();
}
}
[CommandProperty(AccessLevel.GameMaster)]
public Type Type{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public int Number
{
get => m_Number;
set
{
m_Number = value;
InvalidateProperties();
}
}
[CommandProperty(AccessLevel.GameMaster)]
public int Graphic{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public override bool Complete => m_AmountCur == AmountMax;
public override int LabelNumber => 1045151; // a bulk order deed
public override void GetProperties(ObjectPropertyList list)
{
base.GetProperties(list);
list.Add(1060654); // small bulk order
if (RequireExceptional)
list.Add(1045141); // All items must be exceptional.
if (Material != BulkMaterialType.None)
list.Add(SmallBODGump.GetMaterialNumberFor(Material)); // All items must be made with x material.
list.Add(1060656, AmountMax.ToString()); // amount to make: ~1_val~
list.Add(1060658, "#{0}\t{1}", m_Number, m_AmountCur); // ~1_val~: ~2_val~
}
public override void OnDoubleClick(Mobile from)
{
if (IsChildOf(from.Backpack) || InSecureTrade || RootParent is PlayerVendor)
from.SendGump(new SmallBODGump(from, this));
else
from.SendLocalizedMessage(1045156); // You must have the deed in your backpack to use it.
}
public override void OnDoubleClickNotAccessible(Mobile from)
{
OnDoubleClick(from);
}
public override void OnDoubleClickSecureTrade(Mobile from)
{
OnDoubleClick(from);
}
public static BulkMaterialType GetMaterial(CraftResource resource)
{
switch (resource)
{
case CraftResource.DullCopper: return BulkMaterialType.DullCopper;
case CraftResource.ShadowIron: return BulkMaterialType.ShadowIron;
case CraftResource.Copper: return BulkMaterialType.Copper;
case CraftResource.Bronze: return BulkMaterialType.Bronze;
case CraftResource.Gold: return BulkMaterialType.Gold;
case CraftResource.Agapite: return BulkMaterialType.Agapite;
case CraftResource.Verite: return BulkMaterialType.Verite;
case CraftResource.Valorite: return BulkMaterialType.Valorite;
case CraftResource.SpinedLeather: return BulkMaterialType.Spined;
case CraftResource.HornedLeather: return BulkMaterialType.Horned;
case CraftResource.BarbedLeather: return BulkMaterialType.Barbed;
}
return BulkMaterialType.None;
}
public override void EndCombine(Mobile from, Item item)
{
Type objectType = item.GetType();
if (m_AmountCur >= AmountMax)
{
from.SendLocalizedMessage(
1045166); // The maximum amount of requested items have already been combined to this deed.
}
else if (Type == null || objectType != Type && !objectType.IsSubclassOf(Type) ||
!(item is BaseWeapon) && !(item is BaseArmor) && !(item is BaseClothing))
{
from.SendLocalizedMessage(1045169); // The item is not in the request.
}
else
{
BaseArmor armor = item as BaseArmor;
BaseClothing clothing = item as BaseClothing;
BulkMaterialType material = GetMaterial(armor?.Resource ?? clothing?.Resource ?? CraftResource.None);
if (Material >= BulkMaterialType.DullCopper && Material <= BulkMaterialType.Valorite &&
material != Material)
{
from.SendLocalizedMessage(1045168); // The item is not made from the requested ore.
}
else if (Material >= BulkMaterialType.Spined && Material <= BulkMaterialType.Barbed &&
material != Material)
{
from.SendLocalizedMessage(1049352); // The item is not made from the requested leather type.
}
else
{
bool isExceptional;
if (item is BaseWeapon weapon)
isExceptional = weapon.Quality == WeaponQuality.Exceptional;
else if (armor != null)
isExceptional = armor.Quality == ArmorQuality.Exceptional;
else
isExceptional = clothing.Quality == ClothingQuality.Exceptional;
if (RequireExceptional && !isExceptional)
{
from.SendLocalizedMessage(1045167); // The item must be exceptional.
}
else
{
item.Delete();
++AmountCur;
from.SendLocalizedMessage(1045170); // The item has been combined with the deed.
from.SendGump(new SmallBODGump(from, this));
if (m_AmountCur < AmountMax)
BeginCombine(from);
}
}
}
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
writer.Write(m_AmountCur);
writer.Write(Type == null ? null : Type.FullName);
writer.Write(m_Number);
writer.Write(Graphic);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 0:
{
m_AmountCur = reader.ReadInt();
string type = reader.ReadString();
if (type != null)
Type = ScriptCompiler.FindTypeByFullName(type);
m_Number = reader.ReadInt();
Graphic = reader.ReadInt();
break;
}
}
}
}
}

View file

@ -0,0 +1,98 @@
using Server.Gumps;
using Server.Network;
namespace Server.Engines.BulkOrders
{
public class SmallBODAcceptGump : Gump
{
private SmallBOD m_Deed;
private Mobile m_From;
public SmallBODAcceptGump(Mobile from, SmallBOD deed) : base(50, 50)
{
m_From = from;
m_Deed = deed;
m_From.CloseGump<LargeBODAcceptGump>();
m_From.CloseGump<SmallBODAcceptGump>();
AddPage(0);
AddBackground(25, 10, 430, 264, 5054);
AddImageTiled(33, 20, 413, 245, 2624);
AddAlphaRegion(33, 20, 413, 245);
AddImage(20, 5, 10460);
AddImage(430, 5, 10460);
AddImage(20, 249, 10460);
AddImage(430, 249, 10460);
AddHtmlLocalized(190, 25, 120, 20, 1045133, 0x7FFF); // A bulk order
AddHtmlLocalized(40, 48, 350, 20, 1045135, 0x7FFF); // Ah! Thanks for the goods! Would you help me out?
AddHtmlLocalized(40, 72, 210, 20, 1045138, 0x7FFF); // Amount to make:
AddLabel(250, 72, 1152, deed.AmountMax.ToString());
AddHtmlLocalized(40, 96, 120, 20, 1045136, 0x7FFF); // Item requested:
AddItem(385, 96, deed.Graphic);
AddHtmlLocalized(40, 120, 210, 20, deed.Number, 0xFFFFFF);
if (deed.RequireExceptional || deed.Material != BulkMaterialType.None)
{
AddHtmlLocalized(40, 144, 210, 20, 1045140, 0x7FFF); // Special requirements to meet:
if (deed.RequireExceptional)
AddHtmlLocalized(40, 168, 350, 20, 1045141, 0x7FFF); // All items must be exceptional.
if (deed.Material != BulkMaterialType.None)
AddHtmlLocalized(40, deed.RequireExceptional ? 192 : 168, 350, 20, GetMaterialNumberFor(deed.Material),
0x7FFF); // All items must be made with x material.
}
AddHtmlLocalized(40, 216, 350, 20, 1045139, 0x7FFF); // Do you want to accept this order?
AddButton(100, 240, 4005, 4007, 1);
AddHtmlLocalized(135, 240, 120, 20, 1006044, 0x7FFF); // Ok
AddButton(275, 240, 4005, 4007, 0);
AddHtmlLocalized(310, 240, 120, 20, 1011012, 0x7FFF); // CANCEL
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (info.ButtonID == 1) // Ok
{
if (m_From.PlaceInBackpack(m_Deed))
{
m_From.SendLocalizedMessage(1045152); // The bulk order deed has been placed in your backpack.
}
else
{
m_From.SendLocalizedMessage(1045150); // There is not enough room in your backpack for the deed.
m_Deed.Delete();
}
}
else
{
m_Deed.Delete();
}
}
public override void OnServerClose(NetState owner)
{
if (m_Deed?.Deleted == false)
m_Deed.Delete();
}
public static int GetMaterialNumberFor(BulkMaterialType material)
{
if (material >= BulkMaterialType.DullCopper && material <= BulkMaterialType.Valorite)
return 1045142 + (material - BulkMaterialType.DullCopper);
if (material >= BulkMaterialType.Spined && material <= BulkMaterialType.Barbed)
return 1049348 + (material - BulkMaterialType.Spined);
return 0;
}
}
}

View file

@ -0,0 +1,82 @@
using Server.Gumps;
using Server.Network;
namespace Server.Engines.BulkOrders
{
public class SmallBODGump : Gump
{
private SmallBOD m_Deed;
private Mobile m_From;
public SmallBODGump(Mobile from, SmallBOD deed) : base(25, 25)
{
m_From = from;
m_Deed = deed;
m_From.CloseGump<LargeBODGump>();
m_From.CloseGump<SmallBODGump>();
AddPage(0);
AddBackground(50, 10, 455, 260, 5054);
AddImageTiled(58, 20, 438, 241, 2624);
AddAlphaRegion(58, 20, 438, 241);
AddImage(45, 5, 10460);
AddImage(480, 5, 10460);
AddImage(45, 245, 10460);
AddImage(480, 245, 10460);
AddHtmlLocalized(225, 25, 120, 20, 1045133, 0x7FFF); // A bulk order
AddHtmlLocalized(75, 48, 250, 20, 1045138, 0x7FFF); // Amount to make:
AddLabel(275, 48, 1152, deed.AmountMax.ToString());
AddHtmlLocalized(275, 76, 200, 20, 1045153, 0x7FFF); // Amount finished:
AddHtmlLocalized(75, 72, 120, 20, 1045136, 0x7FFF); // Item requested:
AddItem(410, 72, deed.Graphic);
AddHtmlLocalized(75, 96, 210, 20, deed.Number, 0x7FFF);
AddLabel(275, 96, 0x480, deed.AmountCur.ToString());
if (deed.RequireExceptional || deed.Material != BulkMaterialType.None)
AddHtmlLocalized(75, 120, 200, 20, 1045140, 0x7FFF); // Special requirements to meet:
if (deed.RequireExceptional)
AddHtmlLocalized(75, 144, 300, 20, 1045141, 0x7FFF); // All items must be exceptional.
if (deed.Material != BulkMaterialType.None)
AddHtmlLocalized(75, deed.RequireExceptional ? 168 : 144, 300, 20, GetMaterialNumberFor(deed.Material),
0x7FFF); // All items must be made with x material.
AddButton(125, 192, 4005, 4007, 2);
AddHtmlLocalized(160, 192, 300, 20, 1045154, 0x7FFF); // Combine this deed with the item requested.
AddButton(125, 216, 4005, 4007, 1);
AddHtmlLocalized(160, 216, 120, 20, 1011441, 0x7FFF); // EXIT
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (m_Deed.Deleted || !m_Deed.IsChildOf(m_From.Backpack))
return;
if (info.ButtonID == 2) // Combine
{
m_From.SendGump(new SmallBODGump(m_From, m_Deed));
m_Deed.BeginCombine(m_From);
}
}
public static int GetMaterialNumberFor(BulkMaterialType material)
{
if (material >= BulkMaterialType.DullCopper && material <= BulkMaterialType.Valorite)
return 1045142 + (material - BulkMaterialType.DullCopper);
if (material >= BulkMaterialType.Spined && material <= BulkMaterialType.Barbed)
return 1049348 + (material - BulkMaterialType.Spined);
return 0;
}
}
}

View file

@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace Server.Engines.BulkOrders
{
public class SmallBulkEntry
{
public Type Type { get; }
public int Number { get; }
public int Graphic { get; }
public SmallBulkEntry( Type type, int number, int graphic )
{
Type = type;
Number = number;
Graphic = graphic;
}
public static SmallBulkEntry[] BlacksmithWeapons => GetEntries( "Blacksmith", "weapons" );
public static SmallBulkEntry[] BlacksmithArmor => GetEntries( "Blacksmith", "armor" );
public static SmallBulkEntry[] TailorCloth => GetEntries( "Tailoring", "cloth" );
public static SmallBulkEntry[] TailorLeather => GetEntries( "Tailoring", "leather" );
private static Dictionary<string, Dictionary<string, SmallBulkEntry[]>> m_Cache;
public static SmallBulkEntry[] GetEntries( string type, string name )
{
if ( m_Cache == null )
m_Cache = new Dictionary<string, Dictionary<string, SmallBulkEntry[]>>();
if (!m_Cache.TryGetValue( type, out Dictionary<string, SmallBulkEntry[]> table ))
m_Cache[type] = table = new Dictionary<string, SmallBulkEntry[]>();
if (!table.TryGetValue( name, out SmallBulkEntry[] entries ))
table[name] = entries = LoadEntries(type, name);
return entries;
}
public static SmallBulkEntry[] LoadEntries( string type, string name )
{
return LoadEntries($"Data/Bulk Orders/{type}/{name}.cfg");
}
public static SmallBulkEntry[] LoadEntries( string path )
{
path = Path.Combine( Core.BaseDirectory, path );
List<SmallBulkEntry> list = new List<SmallBulkEntry>();
if ( File.Exists( path ) )
{
using ( StreamReader ip = new StreamReader( path ) )
{
string line;
while ( (line = ip.ReadLine()) != null )
{
if ( line.Length == 0 || line.StartsWith( "#" ) )
continue;
try
{
string[] split = line.Split( '\t' );
if ( split.Length >= 2 )
{
Type type = ScriptCompiler.FindTypeByName( split[0] );
int graphic = Utility.ToInt32( split[split.Length - 1] );
if ( type != null && graphic > 0 )
list.Add( new SmallBulkEntry( type, graphic < 0x4000 ? 1020000 + graphic : 1078872 + graphic, graphic ) );
}
}
catch
{
// ignored
}
}
}
}
return list.ToArray();
}
}
}

View file

@ -0,0 +1,195 @@
using System;
using System.Collections.Generic;
using Server.Engines.Craft;
namespace Server.Engines.BulkOrders
{
public class SmallSmithBOD : SmallBOD
{
public static double[] m_BlacksmithMaterialChances =
{
0.501953125, // None
0.250000000, // Dull Copper
0.125000000, // Shadow Iron
0.062500000, // Copper
0.031250000, // Bronze
0.015625000, // Gold
0.007812500, // Agapite
0.003906250, // Verite
0.001953125 // Valorite
};
private SmallSmithBOD(SmallBulkEntry entry, BulkMaterialType mat, int amountMax, bool reqExceptional)
: base(0x44E, 0, amountMax, entry.Type, entry.Number, entry.Graphic, reqExceptional, mat)
{
}
[Constructible]
public SmallSmithBOD()
{
bool useMaterials = Utility.RandomBool();
SmallBulkEntry[] entries = useMaterials ? SmallBulkEntry.BlacksmithArmor :
SmallBulkEntry.BlacksmithWeapons;
if (entries.Length <= 0)
return;
int hue = 0x44E;
int amountMax = Utility.RandomList(10, 15, 20);
BulkMaterialType material = useMaterials ? GetRandomMaterial(BulkMaterialType.DullCopper, m_BlacksmithMaterialChances)
: BulkMaterialType.None;
bool reqExceptional = Utility.RandomBool() || material == BulkMaterialType.None;
SmallBulkEntry entry = entries[Utility.Random(entries.Length)];
Hue = hue;
AmountMax = amountMax;
Type = entry.Type;
Number = entry.Number;
Graphic = entry.Graphic;
RequireExceptional = reqExceptional;
Material = material;
}
public SmallSmithBOD(int amountCur, int amountMax, Type type, int number, int graphic, bool reqExceptional,
BulkMaterialType mat) : base(0x44E, amountCur, amountMax, type, number, graphic, reqExceptional, mat)
{
}
public SmallSmithBOD(Serial serial) : base(serial)
{
}
public override int ComputeFame() => SmithRewardCalculator.Instance.ComputeFame(this);
public override int ComputeGold() => SmithRewardCalculator.Instance.ComputeGold(this);
public override RewardGroup GetRewardGroup() =>
SmithRewardCalculator.Instance.LookupRewards(SmithRewardCalculator.Instance.ComputePoints(this));
public static SmallSmithBOD CreateRandomFor(Mobile m)
{
bool useMaterials = Utility.RandomBool();
SmallBulkEntry[] entries = useMaterials ? SmallBulkEntry.BlacksmithArmor :
SmallBulkEntry.BlacksmithWeapons;
if (entries.Length <= 0)
return null;
double theirSkill = m.Skills.Blacksmith.Base;
int amountMax;
if (theirSkill >= 70.1)
amountMax = Utility.RandomList(10, 15, 20, 20);
else if (theirSkill >= 50.1)
amountMax = Utility.RandomList(10, 15, 15, 20);
else
amountMax = Utility.RandomList(10, 10, 15, 20);
BulkMaterialType material = BulkMaterialType.None;
if (useMaterials && theirSkill >= 70.1)
for (int i = 0; i < 20; ++i)
{
BulkMaterialType check = GetRandomMaterial(BulkMaterialType.DullCopper, m_BlacksmithMaterialChances);
double skillReq = 0.0;
switch (check)
{
case BulkMaterialType.DullCopper:
skillReq = 65.0;
break;
case BulkMaterialType.ShadowIron:
skillReq = 70.0;
break;
case BulkMaterialType.Copper:
skillReq = 75.0;
break;
case BulkMaterialType.Bronze:
skillReq = 80.0;
break;
case BulkMaterialType.Gold:
skillReq = 85.0;
break;
case BulkMaterialType.Agapite:
skillReq = 90.0;
break;
case BulkMaterialType.Verite:
skillReq = 95.0;
break;
case BulkMaterialType.Valorite:
skillReq = 100.0;
break;
case BulkMaterialType.Spined:
skillReq = 65.0;
break;
case BulkMaterialType.Horned:
skillReq = 80.0;
break;
case BulkMaterialType.Barbed:
skillReq = 99.0;
break;
}
if (theirSkill >= skillReq)
{
material = check;
break;
}
}
double excChance = theirSkill >= 70.1 ? (theirSkill + 80.0) / 200.0 : 0.0;
bool reqExceptional = excChance > Utility.RandomDouble();
CraftSystem system = DefBlacksmithy.CraftSystem;
List<SmallBulkEntry> validEntries = new List<SmallBulkEntry>();
for (int i = 0; i < entries.Length; ++i)
{
CraftItem item = system.CraftItems.SearchFor(entries[i].Type);
if (item != null)
{
bool allRequiredSkills = true;
double chance = item.GetSuccessChance(m, null, system, false, ref allRequiredSkills);
if (allRequiredSkills && chance >= 0.0)
{
if (reqExceptional)
chance = item.GetExceptionalChance(system, chance, m);
if (chance > 0.0)
validEntries.Add(entries[i]);
}
}
}
if (validEntries.Count <= 0)
return null;
SmallBulkEntry entry = validEntries[Utility.Random(validEntries.Count)];
return new SmallSmithBOD(entry, material, amountMax, reqExceptional);
}
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,191 @@
using System;
using System.Collections.Generic;
using Server.Engines.Craft;
namespace Server.Engines.BulkOrders
{
public class SmallTailorBOD : SmallBOD
{
public static double[] m_TailoringMaterialChances =
{
0.857421875, // None
0.125000000, // Spined
0.015625000, // Horned
0.001953125 // Barbed
};
private SmallTailorBOD(SmallBulkEntry entry, BulkMaterialType mat, int amountMax, bool reqExceptional)
: base(0x483, 0, amountMax, entry.Type, entry.Number, entry.Graphic, reqExceptional, mat)
{
}
[Constructible]
public SmallTailorBOD()
{
bool useMaterials = Utility.RandomBool();
SmallBulkEntry[] entries = useMaterials ? SmallBulkEntry.TailorLeather : SmallBulkEntry.TailorCloth;
if (entries.Length <= 0)
return;
int hue = 0x483;
int amountMax = Utility.RandomList(10, 15, 20);
BulkMaterialType material = useMaterials ? GetRandomMaterial(BulkMaterialType.Spined, m_TailoringMaterialChances)
: BulkMaterialType.None;
bool reqExceptional = Utility.RandomBool() || material == BulkMaterialType.None;
SmallBulkEntry entry = entries[Utility.Random(entries.Length)];
Hue = hue;
AmountMax = amountMax;
Type = entry.Type;
Number = entry.Number;
Graphic = entry.Graphic;
RequireExceptional = reqExceptional;
Material = material;
}
public SmallTailorBOD(int amountCur, int amountMax, Type type, int number, int graphic, bool reqExceptional,
BulkMaterialType mat) : base(0x483, amountCur, amountMax, type, number, graphic, reqExceptional, mat)
{
}
public SmallTailorBOD(Serial serial) : base(serial)
{
}
public override int ComputeFame() => TailorRewardCalculator.Instance.ComputeFame(this);
public override int ComputeGold() => TailorRewardCalculator.Instance.ComputeGold(this);
public override RewardGroup GetRewardGroup() =>
TailorRewardCalculator.Instance.LookupRewards(TailorRewardCalculator.Instance.ComputePoints(this));
public static SmallTailorBOD CreateRandomFor(Mobile m)
{
SmallBulkEntry[] entries;
bool useMaterials = Utility.RandomBool();
double theirSkill = m.Skills.Tailoring.Base;
// Ugly, but the easiest leather BOD is Leather Cap which requires at least 6.2 skill.
if (useMaterials && theirSkill >= 6.2)
entries = SmallBulkEntry.TailorLeather;
else
entries = SmallBulkEntry.TailorCloth;
if (entries.Length > 0)
{
int amountMax;
if (theirSkill >= 70.1)
amountMax = Utility.RandomList(10, 15, 20, 20);
else if (theirSkill >= 50.1)
amountMax = Utility.RandomList(10, 15, 15, 20);
else
amountMax = Utility.RandomList(10, 10, 15, 20);
BulkMaterialType material = BulkMaterialType.None;
if (useMaterials && theirSkill >= 70.1)
for (int i = 0; i < 20; ++i)
{
BulkMaterialType check = GetRandomMaterial(BulkMaterialType.Spined, m_TailoringMaterialChances);
double skillReq = 0.0;
switch (check)
{
case BulkMaterialType.DullCopper:
skillReq = 65.0;
break;
case BulkMaterialType.Bronze:
skillReq = 80.0;
break;
case BulkMaterialType.Gold:
skillReq = 85.0;
break;
case BulkMaterialType.Agapite:
skillReq = 90.0;
break;
case BulkMaterialType.Verite:
skillReq = 95.0;
break;
case BulkMaterialType.Valorite:
skillReq = 100.0;
break;
case BulkMaterialType.Spined:
skillReq = 65.0;
break;
case BulkMaterialType.Horned:
skillReq = 80.0;
break;
case BulkMaterialType.Barbed:
skillReq = 99.0;
break;
}
if (theirSkill >= skillReq)
{
material = check;
break;
}
}
double excChance = 0.0;
if (theirSkill >= 70.1)
excChance = (theirSkill + 80.0) / 200.0;
bool reqExceptional = excChance > Utility.RandomDouble();
CraftSystem system = DefTailoring.CraftSystem;
List<SmallBulkEntry> validEntries = new List<SmallBulkEntry>();
for (int i = 0; i < entries.Length; ++i)
{
CraftItem item = system.CraftItems.SearchFor(entries[i].Type);
if (item != null)
{
bool allRequiredSkills = true;
double chance = item.GetSuccessChance(m, null, system, false, ref allRequiredSkills);
if (allRequiredSkills && chance >= 0.0)
{
if (reqExceptional)
chance = item.GetExceptionalChance(system, chance, m);
if (chance > 0.0)
validEntries.Add(entries[i]);
}
}
}
if (validEntries.Count > 0)
{
SmallBulkEntry entry = validEntries[Utility.Random(validEntries.Count)];
return new SmallTailorBOD(entry, material, amountMax, reqExceptional);
}
}
return null;
}
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,54 @@
using Server.Items;
namespace Server.Engines.CannedEvil
{
public class ChampionAltar : PentagramAddon
{
private ChampionSpawn m_Spawn;
public ChampionAltar(ChampionSpawn spawn)
{
m_Spawn = spawn;
}
public ChampionAltar(Serial serial) : base(serial)
{
}
public override void OnAfterDelete()
{
base.OnAfterDelete();
m_Spawn?.Delete();
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
writer.Write(m_Spawn);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 0:
{
m_Spawn = reader.ReadItem() as ChampionSpawn;
if (m_Spawn == null)
Delete();
break;
}
}
}
}
}

View file

@ -0,0 +1,85 @@
using Server.Items;
namespace Server.Engines.CannedEvil
{
public class ChampionPlatform : BaseAddon
{
private ChampionSpawn m_Spawn;
public ChampionPlatform(ChampionSpawn spawn)
{
m_Spawn = spawn;
for (int x = -2; x <= 2; ++x)
for (int y = -2; y <= 2; ++y)
AddComponent(0x750, x, y, -5);
for (int x = -1; x <= 1; ++x)
for (int y = -1; y <= 1; ++y)
AddComponent(0x750, x, y, 0);
for (int i = -1; i <= 1; ++i)
{
AddComponent(0x751, i, 2, 0);
AddComponent(0x752, 2, i, 0);
AddComponent(0x753, i, -2, 0);
AddComponent(0x754, -2, i, 0);
}
AddComponent(0x759, -2, -2, 0);
AddComponent(0x75A, 2, 2, 0);
AddComponent(0x75B, -2, 2, 0);
AddComponent(0x75C, 2, -2, 0);
}
public ChampionPlatform(Serial serial) : base(serial)
{
}
public void AddComponent(int id, int x, int y, int z)
{
AddonComponent ac = new AddonComponent(id);
ac.Hue = 0x497;
AddComponent(ac, x, y, z);
}
public override void OnAfterDelete()
{
base.OnAfterDelete();
m_Spawn?.Delete();
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
writer.Write(m_Spawn);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 0:
{
m_Spawn = reader.ReadItem() as ChampionSpawn;
if (m_Spawn == null)
Delete();
break;
}
}
}
}
}

View file

@ -0,0 +1,89 @@
using Server.Engines.CannedEvil;
namespace Server.Items
{
public class ChampionSkull : Item
{
private ChampionSkullType m_Type;
[Constructible]
public ChampionSkull(ChampionSkullType type) : base(0x1AE1)
{
m_Type = type;
LootType = LootType.Cursed;
// TODO: All hue values
switch (type)
{
case ChampionSkullType.Power:
Hue = 0x159;
break;
case ChampionSkullType.Venom:
Hue = 0x172;
break;
case ChampionSkullType.Greed:
Hue = 0x1EE;
break;
case ChampionSkullType.Death:
Hue = 0x025;
break;
case ChampionSkullType.Pain:
Hue = 0x035;
break;
}
}
public ChampionSkull(Serial serial) : base(serial)
{
}
[CommandProperty(AccessLevel.GameMaster)]
public ChampionSkullType Type
{
get => m_Type;
set
{
m_Type = value;
InvalidateProperties();
}
}
public override int LabelNumber => 1049479 + (int)m_Type;
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(1); // version
writer.Write((int)m_Type);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 1:
case 0:
{
m_Type = (ChampionSkullType)reader.ReadInt();
break;
}
}
if (version == 0)
{
if (LootType != LootType.Cursed)
LootType = LootType.Cursed;
if (Insured)
Insured = false;
}
}
}
}

View file

@ -0,0 +1,184 @@
using Server.Items;
using Server.Mobiles;
using Server.Targeting;
namespace Server.Engines.CannedEvil
{
public class ChampionSkullBrazier : AddonComponent
{
private Item m_Skull;
private ChampionSkullType m_Type;
public ChampionSkullBrazier(ChampionSkullPlatform platform, ChampionSkullType type) : base(0x19BB)
{
Hue = 0x455;
Light = LightType.Circle300;
Platform = platform;
m_Type = type;
}
public ChampionSkullBrazier(Serial serial) : base(serial)
{
}
[CommandProperty(AccessLevel.GameMaster)]
public ChampionSkullPlatform Platform{ get; private set; }
[CommandProperty(AccessLevel.GameMaster)]
public ChampionSkullType Type
{
get => m_Type;
set
{
m_Type = value;
InvalidateProperties();
}
}
[CommandProperty(AccessLevel.GameMaster)]
public Item Skull
{
get => m_Skull;
set
{
m_Skull = value;
Platform?.Validate();
}
}
public override int LabelNumber => 1049489 + (int)m_Type;
public override void OnDoubleClick(Mobile from)
{
Platform?.Validate();
BeginSacrifice(from);
}
public void BeginSacrifice(Mobile from)
{
if (Deleted)
return;
if (m_Skull?.Deleted == true)
Skull = null;
if (from.Map != Map || !from.InRange(GetWorldLocation(), 3))
{
from.SendLocalizedMessage(500446); // That is too far away.
}
else if (!Harrower.CanSpawn)
{
from.SendMessage("The harrower has already been spawned.");
}
else if (m_Skull == null)
{
from.SendLocalizedMessage(1049485); // What would you like to sacrifice?
from.Target = new SacrificeTarget(this);
}
else
{
SendLocalizedMessageTo(from, 1049487, ""); // I already have my champions awakening skull!
}
}
public void EndSacrifice(Mobile from, ChampionSkull skull)
{
if (Deleted)
return;
if (m_Skull?.Deleted == true)
Skull = null;
if (from.Map != Map || !from.InRange(GetWorldLocation(), 3))
{
from.SendLocalizedMessage(500446); // That is too far away.
}
else if (!Harrower.CanSpawn)
{
from.SendMessage("The harrower has already been spawned.");
}
else if (skull == null)
{
SendLocalizedMessageTo(from, 1049488, ""); // That is not my champions awakening skull!
}
else if (m_Skull != null)
{
SendLocalizedMessageTo(from, 1049487, ""); // I already have my champions awakening skull!
}
else if (!skull.IsChildOf(from.Backpack))
{
from.SendLocalizedMessage(1049486); // You can only sacrifice items that are in your backpack!
}
else
{
if (skull.Type == Type)
{
skull.Movable = false;
skull.MoveToWorld(GetWorldTop(), Map);
Skull = skull;
}
else
{
SendLocalizedMessageTo(from, 1049488, ""); // That is not my champions awakening skull!
}
}
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
writer.Write((int)m_Type);
writer.Write(Platform);
writer.Write(m_Skull);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 0:
{
m_Type = (ChampionSkullType)reader.ReadInt();
Platform = reader.ReadItem() as ChampionSkullPlatform;
m_Skull = reader.ReadItem();
if (Platform == null)
Delete();
break;
}
}
if (Hue == 0x497)
Hue = 0x455;
if (Light != LightType.Circle300)
Light = LightType.Circle300;
}
private class SacrificeTarget : Target
{
private ChampionSkullBrazier m_Brazier;
public SacrificeTarget(ChampionSkullBrazier brazier) : base(12, false, TargetFlags.None)
{
m_Brazier = brazier;
}
protected override void OnTarget(Mobile from, object targeted)
{
m_Brazier.EndSacrifice(from, targeted as ChampionSkull);
}
}
}
}

View file

@ -0,0 +1,128 @@
using Server.Items;
using Server.Mobiles;
namespace Server.Engines.CannedEvil
{
public class ChampionSkullPlatform : BaseAddon
{
private ChampionSkullBrazier m_Power, m_Enlightenment, m_Venom, m_Pain, m_Greed, m_Death;
[Constructible]
public ChampionSkullPlatform()
{
AddComponent(new AddonComponent(0x71A), -1, -1, -1);
AddComponent(new AddonComponent(0x709), 0, -1, -1);
AddComponent(new AddonComponent(0x709), 1, -1, -1);
AddComponent(new AddonComponent(0x709), -1, 0, -1);
AddComponent(new AddonComponent(0x709), 0, 0, -1);
AddComponent(new AddonComponent(0x709), 1, 0, -1);
AddComponent(new AddonComponent(0x709), -1, 1, -1);
AddComponent(new AddonComponent(0x709), 0, 1, -1);
AddComponent(new AddonComponent(0x71B), 1, 1, -1);
AddComponent(new AddonComponent(0x50F), 0, -1, 4);
AddComponent(m_Power = new ChampionSkullBrazier(this, ChampionSkullType.Power), 0, -1, 5);
AddComponent(new AddonComponent(0x50F), 1, -1, 4);
AddComponent(m_Enlightenment = new ChampionSkullBrazier(this, ChampionSkullType.Enlightenment), 1, -1, 5);
AddComponent(new AddonComponent(0x50F), -1, 0, 4);
AddComponent(m_Venom = new ChampionSkullBrazier(this, ChampionSkullType.Venom), -1, 0, 5);
AddComponent(new AddonComponent(0x50F), 1, 0, 4);
AddComponent(m_Pain = new ChampionSkullBrazier(this, ChampionSkullType.Pain), 1, 0, 5);
AddComponent(new AddonComponent(0x50F), -1, 1, 4);
AddComponent(m_Greed = new ChampionSkullBrazier(this, ChampionSkullType.Greed), -1, 1, 5);
AddComponent(new AddonComponent(0x50F), 0, 1, 4);
AddComponent(m_Death = new ChampionSkullBrazier(this, ChampionSkullType.Death), 0, 1, 5);
AddonComponent comp = new LocalizedAddonComponent(0x20D2, 1049495);
comp.Hue = 0x482;
AddComponent(comp, 0, 0, 5);
comp = new LocalizedAddonComponent(0x0BCF, 1049496);
comp.Hue = 0x482;
AddComponent(comp, 0, 2, -7);
comp = new LocalizedAddonComponent(0x0BD0, 1049497);
comp.Hue = 0x482;
AddComponent(comp, 2, 0, -7);
}
public ChampionSkullPlatform(Serial serial) : base(serial)
{
}
public void Validate()
{
if (Validate(m_Power) && Validate(m_Enlightenment) && Validate(m_Venom) && Validate(m_Pain) &&
Validate(m_Greed) && Validate(m_Death))
{
Mobile harrower = Harrower.Spawn(new Point3D(X, Y, Z + 6), Map);
if (harrower == null)
return;
Clear(m_Power);
Clear(m_Enlightenment);
Clear(m_Venom);
Clear(m_Pain);
Clear(m_Greed);
Clear(m_Death);
}
}
public void Clear(ChampionSkullBrazier brazier)
{
if (brazier != null)
{
Effects.SendBoltEffect(brazier);
brazier.Skull?.Delete();
}
}
public bool Validate(ChampionSkullBrazier brazier)
{
return brazier?.Skull?.Deleted == false;
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
writer.Write(m_Power);
writer.Write(m_Enlightenment);
writer.Write(m_Venom);
writer.Write(m_Pain);
writer.Write(m_Greed);
writer.Write(m_Death);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 0:
{
m_Power = reader.ReadItem() as ChampionSkullBrazier;
m_Enlightenment = reader.ReadItem() as ChampionSkullBrazier;
m_Venom = reader.ReadItem() as ChampionSkullBrazier;
m_Pain = reader.ReadItem() as ChampionSkullBrazier;
m_Greed = reader.ReadItem() as ChampionSkullBrazier;
m_Death = reader.ReadItem() as ChampionSkullBrazier;
break;
}
}
}
}
}

View file

@ -0,0 +1,12 @@
namespace Server.Engines.CannedEvil
{
public enum ChampionSkullType
{
Power,
Enlightenment,
Venom,
Pain,
Greed,
Death
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,133 @@
using System;
using Server.Mobiles;
namespace Server.Engines.CannedEvil
{
public enum ChampionSpawnType
{
Abyss,
Arachnid,
ColdBlood,
ForestLord,
VerminHorde,
UnholyTerror,
SleepingDragon,
Glade,
Pestilence
}
public class ChampionSpawnInfo
{
public ChampionSpawnInfo(string name, Type champion, string[] levelNames, Type[][] spawnTypes)
{
Name = name;
Champion = champion;
LevelNames = levelNames;
SpawnTypes = spawnTypes;
}
public string Name{ get; }
public Type Champion{ get; }
public Type[][] SpawnTypes{ get; }
public string[] LevelNames{ get; }
public static ChampionSpawnInfo[] Table{ get; } =
{
new ChampionSpawnInfo("Abyss", typeof(Semidar), new[] { "Foe", "Assassin", "Conqueror" }, new[] // Abyss
{
// Abyss
new[] { typeof(GreaterMongbat), typeof(Imp) }, // Level 1
new[] { typeof(Gargoyle), typeof(Harpy) }, // Level 2
new[] { typeof(FireGargoyle), typeof(StoneGargoyle) }, // Level 3
new[] { typeof(Daemon), typeof(Succubus) } // Level 4
}),
new ChampionSpawnInfo("Arachnid", typeof(Mephitis), new[] { "Bane", "Killer", "Vanquisher" }, new[] // Arachnid
{
// Arachnid
new[] { typeof(Scorpion), typeof(GiantSpider) }, // Level 1
new[] { typeof(TerathanDrone), typeof(TerathanWarrior) }, // Level 2
new[] { typeof(DreadSpider), typeof(TerathanMatriarch) }, // Level 3
new[] { typeof(PoisonElemental), typeof(TerathanAvenger) } // Level 4
}),
new ChampionSpawnInfo("Cold Blood", typeof(Rikktor), new[] { "Blight", "Slayer", "Destroyer" },
new[] // Cold Blood
{
// Cold Blood
new[] { typeof(Lizardman), typeof(Snake) }, // Level 1
new[] { typeof(LavaLizard), typeof(OphidianWarrior) }, // Level 2
new[] { typeof(Drake), typeof(OphidianArchmage) }, // Level 3
new[] { typeof(Dragon), typeof(OphidianKnight) } // Level 4
}),
new ChampionSpawnInfo("Forest Lord", typeof(LordOaks), new[] { "Enemy", "Curse", "Slaughterer" },
new[] // Forest Lord
{
// Forest Lord
new[] { typeof(Pixie), typeof(ShadowWisp) }, // Level 1
new[] { typeof(Kirin), typeof(Wisp) }, // Level 2
new[] { typeof(Centaur), typeof(Unicorn) }, // Level 3
new[] { typeof(EtherealWarrior), typeof(SerpentineDragon) } // Level 4
}),
new ChampionSpawnInfo("Vermin Horde", typeof(Barracoon), new[] { "Adversary", "Subjugator", "Eradicator" },
new[] // Vermin Horde
{
// Vermin Horde
new[] { typeof(GiantRat), typeof(Slime) }, // Level 1
new[] { typeof(DireWolf), typeof(Ratman) }, // Level 2
new[] { typeof(HellHound), typeof(RatmanMage) }, // Level 3
new[] { typeof(RatmanArcher), typeof(SilverSerpent) } // Level 4
}),
new ChampionSpawnInfo("Unholy Terror", typeof(Neira), new[] { "Scourge", "Punisher", "Nemesis" },
new[] // Unholy Terror
{
// Unholy Terror
Core.AOS
? new[]
{
typeof(Bogle), typeof(Ghoul), typeof(Shade), typeof(Spectre), typeof(Wraith)
} // Level 1 (Pre-AoS)
: new[] { typeof(Ghoul), typeof(Shade), typeof(Spectre), typeof(Wraith) }, // Level 1
new[] { typeof(BoneMagi), typeof(Mummy), typeof(SkeletalMage) }, // Level 2
new[] { typeof(BoneKnight), typeof(Lich), typeof(SkeletalKnight) }, // Level 3
new[] { typeof(LichLord), typeof(RottingCorpse) } // Level 4
}),
new ChampionSpawnInfo("Sleeping Dragon", typeof(Serado), new[] { "Rival", "Challenger", "Antagonist" }, new[]
{
// Unholy Terror
new[] { typeof(DeathwatchBeetleHatchling), typeof(Lizardman) },
new[] { typeof(DeathwatchBeetle), typeof(Kappa) },
new[] { typeof(LesserHiryu), typeof(RevenantLion) },
new[] { typeof(Hiryu), typeof(Oni) }
}),
new ChampionSpawnInfo("Glade", typeof(Twaulo), new[] { "Banisher", "Enforcer", "Eradicator" }, new[]
{
// Glade
new[] { typeof(Pixie), typeof(ShadowWisp) },
new[] { typeof(Centaur), typeof(MLDryad) },
new[] { typeof(Satyr), typeof(CuSidhe) },
new[] { typeof(FeralTreefellow), typeof(RagingGrizzlyBear) }
}),
new ChampionSpawnInfo("The Corrupt", typeof(Ilhenir), new[] { "Cleanser", "Expunger", "Depurator" }, new[]
{
// Unholy Terror
new[] { typeof(PlagueSpawn), typeof(Bogling) },
new[] { typeof(PlagueBeast), typeof(BogThing) },
new[] { typeof(PlagueBeastLord), typeof(InterredGrizzle) },
new[] { typeof(FetidEssence), typeof(PestilentBandage) }
})
};
public static ChampionSpawnInfo GetInfo(ChampionSpawnType type)
{
int v = (int)type;
if (v < 0 || v >= Table.Length)
v = 0;
return Table[v];
}
}
}

View file

@ -0,0 +1,56 @@
namespace Server.Items
{
public class HarrowerGate : Moongate
{
private Mobile m_Harrower;
public HarrowerGate(Mobile harrower, Point3D loc, Map map, Point3D targLoc, Map targMap) : base(targLoc, targMap)
{
m_Harrower = harrower;
Dispellable = false;
ItemID = 0x1FD4;
Light = LightType.Circle300;
MoveToWorld(loc, map);
}
public HarrowerGate(Serial serial) : base(serial)
{
}
public override int LabelNumber => 1049498; // dark moongate
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
writer.Write(m_Harrower);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 0:
{
m_Harrower = reader.ReadMobile();
if (m_Harrower == null)
Delete();
break;
}
}
if (Light != LightType.Circle300)
Light = LightType.Circle300;
}
}
}

View file

@ -0,0 +1,20 @@
using System;
namespace Server.Engines.CannedEvil
{
public class RestartTimer : Timer
{
private ChampionSpawn m_Spawn;
public RestartTimer(ChampionSpawn spawn, TimeSpan delay) : base(delay)
{
m_Spawn = spawn;
Priority = TimerPriority.FiveSeconds;
}
protected override void OnTick()
{
m_Spawn.EndRestart();
}
}
}

View file

@ -0,0 +1,20 @@
using System;
namespace Server.Engines.CannedEvil
{
public class SliceTimer : Timer
{
private ChampionSpawn m_Spawn;
public SliceTimer(ChampionSpawn spawn) : base(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0))
{
m_Spawn = spawn;
Priority = TimerPriority.OneSecond;
}
protected override void OnTick()
{
m_Spawn.OnSlice();
}
}
}

View file

@ -0,0 +1,99 @@
using System;
namespace Server.Items
{
public class StarRoomGate : Moongate
{
private bool m_Decays;
private DateTime m_DecayTime;
private Timer m_Timer;
[Constructible]
public StarRoomGate(Point3D loc, Map map, bool decays) : this(decays)
{
MoveToWorld(loc, map);
Effects.PlaySound(loc, map, 0x20E);
}
[Constructible]
public StarRoomGate(bool decays = false) : base(new Point3D(5143, 1774, 0), Map.Felucca)
{
Dispellable = false;
ItemID = 0x1FD4;
if (decays)
{
m_Decays = true;
m_DecayTime = DateTime.UtcNow + TimeSpan.FromMinutes(2.0);
m_Timer = new InternalTimer(this, m_DecayTime);
m_Timer.Start();
}
}
public StarRoomGate(Serial serial) : base(serial)
{
}
public override int LabelNumber => 1049498; // dark moongate
public override void OnAfterDelete()
{
m_Timer?.Stop();
base.OnAfterDelete();
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
writer.Write(m_Decays);
if (m_Decays)
writer.WriteDeltaTime(m_DecayTime);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 0:
{
m_Decays = reader.ReadBool();
if (m_Decays)
{
m_DecayTime = reader.ReadDeltaTime();
m_Timer = new InternalTimer(this, m_DecayTime);
m_Timer.Start();
}
break;
}
}
}
private class InternalTimer : Timer
{
private Item m_Item;
public InternalTimer(Item item, DateTime end) : base(end - DateTime.UtcNow)
{
m_Item = item;
}
protected override void OnTick()
{
m_Item.Delete();
}
}
}
}

View file

@ -0,0 +1,448 @@
using System.Collections.Generic;
namespace Server.Engines.Chat
{
public class Channel
{
private string m_Name;
private string m_Password;
private List<ChatUser> m_Users, m_Banned, m_Moderators, m_Voices;
private bool m_VoiceRestricted;
public Channel(string name)
{
m_Name = name;
m_Users = new List<ChatUser>();
m_Banned = new List<ChatUser>();
m_Moderators = new List<ChatUser>();
m_Voices = new List<ChatUser>();
}
public Channel(string name, string password) : this(name)
{
m_Password = password;
}
public string Name
{
get => m_Name;
set
{
SendCommand(ChatCommand.RemoveChannel, m_Name);
m_Name = value;
SendCommand(ChatCommand.AddChannel, m_Name);
SendCommand(ChatCommand.JoinedChannel, m_Name);
}
}
public string Password
{
get => m_Password;
set
{
string newValue = null;
if (value != null)
{
newValue = value.Trim();
if (string.IsNullOrEmpty(newValue))
newValue = null;
}
m_Password = newValue;
}
}
public bool VoiceRestricted
{
get => m_VoiceRestricted;
set
{
m_VoiceRestricted = value;
if (value)
SendMessage(56); // From now on, only moderators will have speaking privileges in this conference by default.
else
SendMessage(55); // From now on, everyone in the conference will have speaking privileges by default.
}
}
public bool AlwaysAvailable{ get; set; }
public static List<Channel> Channels{ get; } = new List<Channel>();
public bool Contains(ChatUser user)
{
return m_Users.Contains(user);
}
public bool IsBanned(ChatUser user)
{
return m_Banned.Contains(user);
}
public bool CanTalk(ChatUser user)
{
return !m_VoiceRestricted || m_Voices.Contains(user) || m_Moderators.Contains(user);
}
public bool IsModerator(ChatUser user)
{
return m_Moderators.Contains(user);
}
public bool IsVoiced(ChatUser user)
{
return m_Voices.Contains(user);
}
public bool ValidatePassword(string password)
{
return m_Password == null || Insensitive.Equals(m_Password, password);
}
public bool ValidateModerator(ChatUser user)
{
if (user != null && !IsModerator(user))
{
user.SendMessage(29); // You must have operator status to do this.
return false;
}
return true;
}
public bool ValidateAccess(ChatUser from, ChatUser target)
{
if (from == null || target == null || from.Mobile.AccessLevel >= target.Mobile.AccessLevel)
return true;
from.Mobile.SendMessage("Your access level is too low to do this.");
return false;
}
public bool AddUser(ChatUser user, string password = null)
{
if (Contains(user))
{
user.SendMessage(46, m_Name); // You are already in the conference '%1'.
return true;
}
if (IsBanned(user))
{
user.SendMessage(64); // You have been banned from this conference.
return false;
}
if (!ValidatePassword(password))
{
user.SendMessage(34); // That is not the correct password.
return false;
}
user.CurrentChannel?.RemoveUser(user); // Remove them from their current channel first
ChatSystem.SendCommandTo(user.Mobile, ChatCommand.JoinedChannel, m_Name);
SendCommand(ChatCommand.AddUserToChannel, user.GetColorCharacter() + user.Username);
m_Users.Add(user);
user.CurrentChannel = this;
if (user.Mobile.AccessLevel >= AccessLevel.GameMaster || !AlwaysAvailable && m_Users.Count == 1)
AddModerator(user);
SendUsersTo(user);
return true;
}
public void RemoveUser(ChatUser user)
{
if (Contains(user))
{
m_Users.Remove(user);
user.CurrentChannel = null;
if (m_Moderators.Contains(user))
m_Moderators.Remove(user);
if (m_Voices.Contains(user))
m_Voices.Remove(user);
SendCommand(ChatCommand.RemoveUserFromChannel, user, user.Username);
ChatSystem.SendCommandTo(user.Mobile, ChatCommand.LeaveChannel);
if (m_Users.Count == 0 && !AlwaysAvailable)
RemoveChannel(this);
}
}
public void AddBan(ChatUser user, ChatUser moderator = null)
{
if (!ValidateModerator(moderator) || !ValidateAccess(moderator, user))
return;
if (!m_Banned.Contains(user))
m_Banned.Add(user);
Kick(user, moderator, true);
}
public void RemoveBan(ChatUser user)
{
if (m_Banned.Contains(user))
m_Banned.Remove(user);
}
public void Kick(ChatUser user, ChatUser moderator = null)
{
Kick(user, moderator, false);
}
public void Kick(ChatUser user, ChatUser moderator, bool wasBanned)
{
if (!ValidateModerator(moderator) || !ValidateAccess(moderator, user))
return;
if (Contains(user))
{
if (moderator != null)
{
if (wasBanned)
user.SendMessage(63,
moderator.Username); // %1, a conference moderator, has banned you from the conference.
else
user.SendMessage(45,
moderator.Username); // %1, a conference moderator, has kicked you out of the conference.
}
RemoveUser(user);
ChatSystem.SendCommandTo(user.Mobile, ChatCommand.AddUserToChannel,
user.GetColorCharacter() + user.Username);
SendMessage(44, user.Username); // %1 has been kicked out of the conference.
}
if (wasBanned)
moderator?.SendMessage(62, user.Username); // You are banning %1 from this conference.
}
public void AddVoiced(ChatUser user, ChatUser moderator = null)
{
if (!ValidateModerator(moderator))
return;
if (!IsBanned(user) && !IsModerator(user) && !IsVoiced(user))
{
m_Voices.Add(user);
if (moderator != null)
user.SendMessage(54,
moderator
.Username); // %1, a conference moderator, has granted you speaking privileges in this conference.
SendMessage(52, user, user.Username); // %1 now has speaking privileges in this conference.
SendCommand(ChatCommand.AddUserToChannel, user, user.GetColorCharacter() + user.Username);
}
}
public void RemoveVoiced(ChatUser user, ChatUser moderator)
{
if (!ValidateModerator(moderator) || !ValidateAccess(moderator, user))
return;
if (!IsModerator(user) && IsVoiced(user))
{
m_Voices.Remove(user);
if (moderator != null)
user.SendMessage(53,
moderator
.Username); // %1, a conference moderator, has removed your speaking privileges for this conference.
SendMessage(51, user, user.Username); // %1 no longer has speaking privileges in this conference.
SendCommand(ChatCommand.AddUserToChannel, user, user.GetColorCharacter() + user.Username);
}
}
public void AddModerator(ChatUser user, ChatUser moderator = null)
{
if (!ValidateModerator(moderator))
return;
if (IsBanned(user) || IsModerator(user))
return;
if (IsVoiced(user))
m_Voices.Remove(user);
m_Moderators.Add(user);
if (moderator != null)
user.SendMessage(50, moderator.Username); // %1 has made you a conference moderator.
SendMessage(48, user, user.Username); // %1 is now a conference moderator.
SendCommand(ChatCommand.AddUserToChannel, user.GetColorCharacter() + user.Username);
}
public void RemoveModerator(ChatUser user, ChatUser moderator = null)
{
if (!ValidateModerator(moderator) || !ValidateAccess(moderator, user))
return;
if (IsModerator(user))
{
m_Moderators.Remove(user);
if (moderator != null)
user.SendMessage(49, moderator.Username); // %1 has removed you from the list of conference moderators.
SendMessage(47, user, user.Username); // %1 is no longer a conference moderator.
SendCommand(ChatCommand.AddUserToChannel, user.GetColorCharacter() + user.Username);
}
}
public void SendMessage(int number, string param1 = null)
{
SendMessage(number, null, param1);
}
public void SendMessage(int number, ChatUser initiator, string param1 = null, string param2 = null)
{
for (int i = 0; i < m_Users.Count; ++i)
{
ChatUser user = m_Users[i];
if (user == initiator)
continue;
if (user.CheckOnline())
user.SendMessage(number, param1, param2);
else if (!Contains(user))
--i;
}
}
public void SendIgnorableMessage(int number, ChatUser from, string param1, string param2)
{
for (int i = 0; i < m_Users.Count; ++i)
{
ChatUser user = m_Users[i];
if (user.IsIgnored(from))
continue;
if (user.CheckOnline())
user.SendMessage(number, from.Mobile, param1, param2);
else if (!Contains(user))
--i;
}
}
public void SendCommand(ChatCommand command, string param1 = null, string param2 = null)
{
SendCommand(command, null, param1, param2);
}
public void SendCommand(ChatCommand command, ChatUser initiator, string param1 = null, string param2 = null)
{
for (int i = 0; i < m_Users.Count; ++i)
{
ChatUser user = m_Users[i];
if (user == initiator)
continue;
if (user.CheckOnline())
ChatSystem.SendCommandTo(user.Mobile, command, param1, param2);
else if (!Contains(user))
--i;
}
}
public void SendUsersTo(ChatUser to)
{
for (int i = 0; i < m_Users.Count; ++i)
{
ChatUser user = m_Users[i];
ChatSystem.SendCommandTo(to.Mobile, ChatCommand.AddUserToChannel, user.GetColorCharacter() + user.Username);
}
}
public static void SendChannelsTo(ChatUser user)
{
for (int i = 0; i < Channels.Count; ++i)
{
Channel channel = Channels[i];
if (!channel.IsBanned(user))
ChatSystem.SendCommandTo(user.Mobile, ChatCommand.AddChannel, channel.Name, "0");
}
}
public static Channel AddChannel(string name, string password = null)
{
Channel channel = FindChannelByName(name);
if (channel == null)
{
channel = new Channel(name, password);
Channels.Add(channel);
}
ChatUser.GlobalSendCommand(ChatCommand.AddChannel, name, "0");
return channel;
}
public static void RemoveChannel(string name)
{
RemoveChannel(FindChannelByName(name));
}
public static void RemoveChannel(Channel channel)
{
if (channel == null)
return;
if (Channels.Contains(channel) && channel.m_Users.Count == 0)
{
ChatUser.GlobalSendCommand(ChatCommand.RemoveChannel, channel.Name);
channel.m_Moderators.Clear();
channel.m_Voices.Clear();
Channels.Remove(channel);
}
}
public static Channel FindChannelByName(string name)
{
for (int i = 0; i < Channels.Count; ++i)
{
Channel channel = Channels[i];
if (channel.m_Name == name)
return channel;
}
return null;
}
public static void Initialize()
{
AddStaticChannel("Newbie Help");
}
public static void AddStaticChannel(string name)
{
AddChannel(name).AlwaysAvailable = true;
}
}
}

View file

@ -0,0 +1,150 @@
using System;
using System.IO;
using Server.Accounting;
using Server.Misc;
using Server.Network;
namespace Server.Engines.Chat
{
public class ChatSystem
{
public static bool Enabled{ get; set; } = true;
public static void Initialize()
{
PacketHandlers.Register(0xB5, 0x40, true, OpenChatWindowRequest);
PacketHandlers.Register(0xB3, 0, true, ChatAction);
}
public static void SendCommandTo(Mobile to, ChatCommand type, string param1 = null, string param2 = null)
{
to?.Send(new ChatMessagePacket(null, (int)type + 20, param1, param2));
}
public static void OpenChatWindowRequest(NetState state, PacketReader pvSrc)
{
Mobile from = state.Mobile;
if (!Enabled)
{
from.SendMessage("The chat system has been disabled.");
return;
}
pvSrc.Seek(2, SeekOrigin.Begin);
string chatName = pvSrc.ReadUnicodeStringSafe((0x40 - 2) >> 1).Trim();
Account acct = state.Account as Account;
string accountChatName = null;
if (acct != null)
accountChatName = acct.GetTag("ChatName");
accountChatName = accountChatName?.Trim();
if (!string.IsNullOrEmpty(accountChatName))
{
if (chatName.Length > 0 && chatName != accountChatName)
from.SendMessage("You cannot change chat nickname once it has been set.");
}
else
{
if (chatName.Length == 0)
{
SendCommandTo(from, ChatCommand.AskNewNickname);
return;
}
if (NameVerification.Validate(chatName, 2, 31, true, true, true, 0, NameVerification.SpaceDashPeriodQuote) &&
chatName.ToLower().IndexOf("system") == -1)
{
// TODO: Optimize this search
foreach (Account checkAccount in Accounts.GetAccounts())
{
string existingName = checkAccount.GetTag("ChatName");
if (existingName != null)
{
existingName = existingName.Trim();
if (Insensitive.Equals(existingName, chatName))
{
from.SendMessage("Nickname already in use.");
SendCommandTo(from, ChatCommand.AskNewNickname);
return;
}
}
}
accountChatName = chatName;
acct?.AddTag("ChatName", chatName);
}
else
{
from.SendLocalizedMessage(501173); // That name is disallowed.
SendCommandTo(from, ChatCommand.AskNewNickname);
return;
}
}
SendCommandTo(from, ChatCommand.OpenChatWindow, accountChatName);
ChatUser.AddChatUser(from);
}
public static ChatUser SearchForUser(ChatUser from, string name)
{
ChatUser user = ChatUser.GetChatUser(name);
if (user == null)
from.SendMessage(32, name); // There is no player named '%1'.
return user;
}
public static void ChatAction(NetState state, PacketReader pvSrc)
{
if (!Enabled)
return;
try
{
Mobile from = state.Mobile;
ChatUser user = ChatUser.GetChatUser(from);
if (user == null)
return;
string lang = pvSrc.ReadStringSafe(4);
int actionID = pvSrc.ReadInt16();
string param = pvSrc.ReadUnicodeString();
ChatActionHandler handler = ChatActionHandlers.GetHandler(actionID);
if (handler != null)
{
Channel channel = user.CurrentChannel;
if (handler.RequireConference && channel == null)
user.SendMessage(31); /* You must be in a conference to do this.
* To join a conference, select one from the Conference menu.
*/
else if (handler.RequireModerator && !user.IsModerator)
user.SendMessage(29); // You must have operator status to do this.
else
handler.Callback(user, channel, param);
}
else
{
Console.WriteLine("Client: {0}: Unknown chat action 0x{1:X}: {2}", state, actionID, param);
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
}

View file

@ -0,0 +1,20 @@
namespace Server.Engines.Chat
{
public delegate void OnChatAction(ChatUser from, Channel channel, string param);
public class ChatActionHandler
{
public ChatActionHandler(bool requireModerator, bool requireConference, OnChatAction callback)
{
RequireModerator = requireModerator;
RequireConference = requireConference;
Callback = callback;
}
public bool RequireModerator{ get; }
public bool RequireConference{ get; }
public OnChatAction Callback{ get; }
}
}

View file

@ -0,0 +1,358 @@
namespace Server.Engines.Chat
{
public class ChatActionHandlers
{
private static ChatActionHandler[] m_Handlers;
static ChatActionHandlers()
{
m_Handlers = new ChatActionHandler[0x100];
Register(0x41, true, true, ChangeChannelPassword);
Register(0x58, false, false, LeaveChat);
Register(0x61, false, true, ChannelMessage);
Register(0x62, false, false, JoinChannel);
Register(0x63, false, false, JoinNewChannel);
Register(0x64, true, true, RenameChannel);
Register(0x65, false, false, PrivateMessage);
Register(0x66, false, false, AddIgnore);
Register(0x67, false, false, RemoveIgnore);
Register(0x68, false, false, ToggleIgnore);
Register(0x69, true, true, AddVoice);
Register(0x6A, true, true, RemoveVoice);
Register(0x6B, true, true, ToggleVoice);
Register(0x6C, true, true, AddModerator);
Register(0x6D, true, true, RemoveModerator);
Register(0x6E, true, true, ToggleModerator);
Register(0x6F, false, false, AllowPrivateMessages);
Register(0x70, false, false, DisallowPrivateMessages);
Register(0x71, false, false, TogglePrivateMessages);
Register(0x72, false, false, ShowCharacterName);
Register(0x73, false, false, HideCharacterName);
Register(0x74, false, false, ToggleCharacterName);
Register(0x75, false, false, QueryWhoIs);
Register(0x76, true, true, Kick);
Register(0x77, true, true, EnableDefaultVoice);
Register(0x78, true, true, DisableDefaultVoice);
Register(0x79, true, true, ToggleDefaultVoice);
Register(0x7A, false, true, EmoteMessage);
}
public static void Register(int actionID, bool requireModerator, bool requireConference, OnChatAction callback)
{
if (actionID >= 0 && actionID < m_Handlers.Length)
m_Handlers[actionID] = new ChatActionHandler(requireModerator, requireConference, callback);
}
public static ChatActionHandler GetHandler(int actionID)
{
if (actionID >= 0 && actionID < m_Handlers.Length)
return m_Handlers[actionID];
return null;
}
public static void ChannelMessage(ChatUser from, Channel channel, string param)
{
if (channel.CanTalk(from))
channel.SendIgnorableMessage(57, from, from.GetColorCharacter() + from.Username, param); // %1: %2
else
from.SendMessage(36); // The moderator of this conference has not given you speaking privileges.
}
public static void EmoteMessage(ChatUser from, Channel channel, string param)
{
if (channel.CanTalk(from))
channel.SendIgnorableMessage(58, from, from.GetColorCharacter() + from.Username, param); // %1 %2
else
from.SendMessage(36); // The moderator of this conference has not given you speaking privileges.
}
public static void PrivateMessage(ChatUser from, Channel channel, string param)
{
int indexOf = param.IndexOf(' ');
string name = param.Substring(0, indexOf);
string text = param.Substring(indexOf + 1);
ChatUser target = ChatSystem.SearchForUser(from, name);
if (target == null)
return;
if (target.IsIgnored(from))
from.SendMessage(35,
target.Username); // %1 has chosen to ignore you. None of your messages to them will get through.
else if (target.IgnorePrivateMessage)
from.SendMessage(42, target.Username); // %1 has chosen to not receive private messages at the moment.
else
target.SendMessage(59, from.Mobile, from.GetColorCharacter() + from.Username, text); // [%1]: %2
}
public static void LeaveChat(ChatUser from, Channel channel, string param)
{
ChatUser.RemoveChatUser(from);
}
public static void ChangeChannelPassword(ChatUser from, Channel channel, string param)
{
channel.Password = param;
from.SendMessage(60); // The password to the conference has been changed.
}
public static void AllowPrivateMessages(ChatUser from, Channel channel, string param)
{
from.IgnorePrivateMessage = false;
from.SendMessage(37); // You can now receive private messages.
}
public static void DisallowPrivateMessages(ChatUser from, Channel channel, string param)
{
from.IgnorePrivateMessage = true;
from.SendMessage(38); /* You will no longer receive private messages.
* Those who send you a message will be notified that you are blocking incoming messages.
*/
}
public static void TogglePrivateMessages(ChatUser from, Channel channel, string param)
{
from.IgnorePrivateMessage = !from.IgnorePrivateMessage;
from.SendMessage(from.IgnorePrivateMessage ? 38 : 37); // See above for messages
}
public static void ShowCharacterName(ChatUser from, Channel channel, string param)
{
from.Anonymous = false;
from.SendMessage(
39); // You are now showing your character name to any players who inquire with the whois command.
}
public static void HideCharacterName(ChatUser from, Channel channel, string param)
{
from.Anonymous = true;
from.SendMessage(
40); // You are no longer showing your character name to any players who inquire with the whois command.
}
public static void ToggleCharacterName(ChatUser from, Channel channel, string param)
{
from.Anonymous = !from.Anonymous;
from.SendMessage(from.Anonymous ? 40 : 39); // See above for messages
}
public static void JoinChannel(ChatUser from, Channel channel, string param)
{
string name;
string password = null;
int start = param.IndexOf('\"');
if (start >= 0)
{
int end = param.IndexOf('\"', ++start);
if (end >= 0)
{
name = param.Substring(start, end - start);
password = param.Substring(++end);
}
else
{
name = param.Substring(start);
}
}
else
{
int indexOf = param.IndexOf(' ');
if (indexOf >= 0)
{
name = param.Substring(0, indexOf++);
password = param.Substring(indexOf);
}
else
{
name = param;
}
}
password = password?.Trim();
if (password?.Length == 0)
password = null;
Channel joined = Channel.FindChannelByName(name);
if (joined == null)
from.SendMessage(33, name); // There is no conference named '%1'.
else
joined.AddUser(from, password);
}
public static void JoinNewChannel(ChatUser from, Channel channel, string param)
{
if ((param = param.Trim()).Length == 0)
return;
string name;
string password = null;
int start = param.IndexOf('{');
if (start >= 0)
{
name = param.Substring(0, start++);
int end = param.IndexOf('}', start);
if (end >= start)
password = param.Substring(start, end - start);
}
else
{
name = param;
}
password = password?.Trim();
if (password?.Length == 0)
password = null;
Channel.AddChannel(name, password).AddUser(from, password);
}
public static void AddIgnore(ChatUser from, Channel channel, string param)
{
ChatUser target = ChatSystem.SearchForUser(from, param);
if (target == null)
return;
from.AddIgnored(target);
}
public static void RemoveIgnore(ChatUser from, Channel channel, string param)
{
ChatUser target = ChatSystem.SearchForUser(from, param);
if (target == null)
return;
from.RemoveIgnored(target);
}
public static void ToggleIgnore(ChatUser from, Channel channel, string param)
{
ChatUser target = ChatSystem.SearchForUser(from, param);
if (target == null)
return;
if (from.IsIgnored(target))
from.RemoveIgnored(target);
else
from.AddIgnored(target);
}
public static void AddVoice(ChatUser from, Channel channel, string param)
{
ChatUser target = ChatSystem.SearchForUser(from, param);
if (target != null)
channel.AddVoiced(target, from);
}
public static void RemoveVoice(ChatUser from, Channel channel, string param)
{
ChatUser target = ChatSystem.SearchForUser(from, param);
if (target != null)
channel.RemoveVoiced(target, from);
}
public static void ToggleVoice(ChatUser from, Channel channel, string param)
{
ChatUser target = ChatSystem.SearchForUser(from, param);
if (target == null)
return;
if (channel.IsVoiced(target))
channel.RemoveVoiced(target, from);
else
channel.AddVoiced(target, from);
}
public static void AddModerator(ChatUser from, Channel channel, string param)
{
ChatUser target = ChatSystem.SearchForUser(from, param);
if (target != null)
channel.AddModerator(target, from);
}
public static void RemoveModerator(ChatUser from, Channel channel, string param)
{
ChatUser target = ChatSystem.SearchForUser(from, param);
if (target != null)
channel.RemoveModerator(target, from);
}
public static void ToggleModerator(ChatUser from, Channel channel, string param)
{
ChatUser target = ChatSystem.SearchForUser(from, param);
if (target == null)
return;
if (channel.IsModerator(target))
channel.RemoveModerator(target, from);
else
channel.AddModerator(target, from);
}
public static void RenameChannel(ChatUser from, Channel channel, string param)
{
channel.Name = param;
}
public static void QueryWhoIs(ChatUser from, Channel channel, string param)
{
ChatUser target = ChatSystem.SearchForUser(from, param);
if (target == null)
return;
if (target.Anonymous)
from.SendMessage(41, target.Username); // %1 is remaining anonymous.
else
from.SendMessage(43, target.Username, target.Mobile.Name); // %2 is known in the lands of Britannia as %2.
}
public static void Kick(ChatUser from, Channel channel, string param)
{
ChatUser target = ChatSystem.SearchForUser(from, param);
if (target != null)
channel.Kick(target, from);
}
public static void EnableDefaultVoice(ChatUser from, Channel channel, string param)
{
channel.VoiceRestricted = false;
}
public static void DisableDefaultVoice(ChatUser from, Channel channel, string param)
{
channel.VoiceRestricted = true;
}
public static void ToggleDefaultVoice(ChatUser from, Channel channel, string param)
{
channel.VoiceRestricted = !channel.VoiceRestricted;
}
}
}

View file

@ -0,0 +1,50 @@
namespace Server.Engines.Chat
{
public enum ChatCommand
{
/// <summary>
/// Add a channel to top list.
/// </summary>
AddChannel = 0x3E8,
/// <summary>
/// Remove channel from top list.
/// </summary>
RemoveChannel = 0x3E9,
/// <summary>
/// Queries for a new chat nickname.
/// </summary>
AskNewNickname = 0x3EB,
/// <summary>
/// Closes the chat window.
/// </summary>
CloseChatWindow = 0x3EC,
/// <summary>
/// Opens the chat window.
/// </summary>
OpenChatWindow = 0x3ED,
/// <summary>
/// Add a user to current channel.
/// </summary>
AddUserToChannel = 0x3EE,
/// <summary>
/// Remove a user from current channel.
/// </summary>
RemoveUserFromChannel = 0x3EF,
/// <summary>
/// Send a message putting generic conference name at top when player leaves a channel.
/// </summary>
LeaveChannel = 0x3F0,
/// <summary>
/// Send a message putting Channel name at top and telling player he joined the channel.
/// </summary>
JoinedChannel = 0x3F1
}
}

View file

@ -0,0 +1,212 @@
using System.Collections.Generic;
using Server.Accounting;
namespace Server.Engines.Chat
{
public class ChatUser
{
public const char NormalColorCharacter = '0';
public const char ModeratorColorCharacter = '1';
public const char VoicedColorCharacter = '2';
private static List<ChatUser> m_Users = new List<ChatUser>();
private static Dictionary<Mobile, ChatUser> m_Table = new Dictionary<Mobile, ChatUser>();
public ChatUser(Mobile m)
{
Mobile = m;
Ignored = new List<ChatUser>();
Ignoring = new List<ChatUser>();
}
public Mobile Mobile{ get; }
public List<ChatUser> Ignored{ get; }
public List<ChatUser> Ignoring{ get; }
public string Username
{
get
{
if (Mobile.Account is Account acct)
return acct.GetTag("ChatName");
return null;
}
set
{
if (Mobile.Account is Account acct)
acct.SetTag("ChatName", value);
}
}
public Channel CurrentChannel{ get; set; }
public bool IsOnline => Mobile.NetState != null;
public bool Anonymous{ get; set; }
public bool IgnorePrivateMessage{ get; set; }
public bool IsModerator => CurrentChannel?.IsModerator(this) == true;
public char GetColorCharacter()
{
return IsModerator ? ModeratorColorCharacter :
CurrentChannel?.IsVoiced(this) == true ? VoicedColorCharacter : NormalColorCharacter;
}
public bool CheckOnline()
{
if (IsOnline)
return true;
RemoveChatUser(this);
return false;
}
public void SendMessage(int number, string param1 = null, string param2 = null)
{
if (Mobile.NetState != null)
Mobile.Send(new ChatMessagePacket(Mobile, number, param1, param2));
}
public void SendMessage(int number, Mobile from, string param1, string param2)
{
if (Mobile.NetState != null)
Mobile.Send(new ChatMessagePacket(from, number, param1, param2));
}
public bool IsIgnored(ChatUser check)
{
return Ignored.Contains(check);
}
public void AddIgnored(ChatUser user)
{
if (IsIgnored(user))
{
SendMessage(22, user.Username); // You are already ignoring %1.
}
else
{
Ignored.Add(user);
user.Ignoring.Add(this);
SendMessage(23, user.Username); // You are now ignoring %1.
}
}
public void RemoveIgnored(ChatUser user)
{
if (IsIgnored(user))
{
Ignored.Remove(user);
user.Ignoring.Remove(this);
SendMessage(24, user.Username); // You are no longer ignoring %1.
if (Ignored.Count == 0)
SendMessage(26); // You are no longer ignoring anyone.
}
else
{
SendMessage(25, user.Username); // You are not ignoring %1.
}
}
public static ChatUser AddChatUser(Mobile from)
{
ChatUser user = GetChatUser(from);
if (user != null)
return user;
user = new ChatUser(from);
m_Users.Add(user);
m_Table[from] = user;
Channel.SendChannelsTo(user);
List<Channel> list = Channel.Channels;
for (int i = 0; i < list.Count; ++i)
{
Channel c = list[i];
if (c.AddUser(user))
break;
}
//ChatSystem.SendCommandTo( user.m_Mobile, ChatCommand.AddUserToChannel, user.GetColorCharacter() + user.Username );
return user;
}
public static void RemoveChatUser(ChatUser user)
{
if (user == null)
return;
for (int i = 0; i < user.Ignoring.Count; ++i)
user.Ignoring[i].RemoveIgnored(user);
if (m_Users.Contains(user))
{
ChatSystem.SendCommandTo(user.Mobile, ChatCommand.CloseChatWindow);
user.CurrentChannel?.RemoveUser(user);
m_Users.Remove(user);
m_Table.Remove(user.Mobile);
}
}
public static void RemoveChatUser(Mobile from)
{
ChatUser user = GetChatUser(from);
RemoveChatUser(user);
}
public static ChatUser GetChatUser(Mobile from)
{
m_Table.TryGetValue(from, out ChatUser c);
return c;
}
public static ChatUser GetChatUser(string username)
{
for (int i = 0; i < m_Users.Count; ++i)
{
ChatUser user = m_Users[i];
if (user.Username == username)
return user;
}
return null;
}
public static void GlobalSendCommand(ChatCommand command, string param1, string param2 = null)
{
GlobalSendCommand(command, null, param1, param2);
}
public static void GlobalSendCommand(ChatCommand command, ChatUser initiator = null, string param1 = null, string param2 = null)
{
for (int i = 0; i < m_Users.Count; ++i)
{
ChatUser user = m_Users[i];
if (user == initiator)
continue;
if (user.CheckOnline())
ChatSystem.SendCommandTo(user.Mobile, command, param1, param2);
}
}
}
}

View file

@ -0,0 +1,15 @@
namespace Server.Chat
{
public class ChatSystem
{
public static void Initialize()
{
EventSink.ChatRequest += EventSink_ChatRequest;
}
private static void EventSink_ChatRequest(ChatRequestEventArgs e)
{
e.Mobile.SendMessage("Chat is not currently supported.");
}
}
}

View file

@ -0,0 +1,28 @@
using Server.Network;
namespace Server.Engines.Chat
{
public sealed class ChatMessagePacket : Packet
{
public ChatMessagePacket(Mobile who, int number, string param1, string param2) : base(0xB2)
{
if (param1 == null)
param1 = string.Empty;
if (param2 == null)
param2 = string.Empty;
EnsureCapacity(13 + (param1.Length + param2.Length) * 2);
m_Stream.Write((ushort)(number - 20));
if (who != null)
m_Stream.WriteAsciiFixed(who.Language, 4);
else
m_Stream.Write(0);
m_Stream.WriteBigUniNull(param1);
m_Stream.WriteBigUniNull(param2);
}
}
}

View file

@ -0,0 +1,279 @@
using System;
using System.Collections.Generic;
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class AcceptDuelGump : Gump
{
private const int LabelColor32 = 0xFFFFFF;
private const int BlackColor32 = 0x000008;
private static Dictionary<Mobile, List<IgnoreEntry>> m_IgnoreLists = new Dictionary<Mobile, List<IgnoreEntry>>();
private bool m_Active = true;
private Mobile m_Challenger, m_Challenged;
private DuelContext m_Context;
private Participant m_Participant;
private int m_Slot;
public AcceptDuelGump(Mobile challenger, Mobile challenged, DuelContext context, Participant p, int slot) : base(50,
50)
{
m_Challenger = challenger;
m_Challenged = challenged;
m_Context = context;
m_Participant = p;
m_Slot = slot;
challenged.CloseGump<AcceptDuelGump>();
Closable = false;
AddPage(0);
//AddBackground( 0, 0, 400, 220, 9150 );
AddBackground(1, 1, 398, 218, 3600);
//AddBackground( 16, 15, 369, 189, 9100 );
AddImageTiled(16, 15, 369, 189, 3604);
AddAlphaRegion(16, 15, 369, 189);
AddImage(215, -43, 0xEE40);
//AddImage( 330, 141, 0x8BA );
AddHtml(22 - 1, 22, 294, 20, Color(Center("Duel Challenge"), BlackColor32));
AddHtml(22 + 1, 22, 294, 20, Color(Center("Duel Challenge"), BlackColor32));
AddHtml(22, 22 - 1, 294, 20, Color(Center("Duel Challenge"), BlackColor32));
AddHtml(22, 22 + 1, 294, 20, Color(Center("Duel Challenge"), BlackColor32));
AddHtml(22, 22, 294, 20, Color(Center("Duel Challenge"), LabelColor32));
string fmt;
if (p.Contains(challenger))
fmt = "You have been asked to join sides with {0} in a duel. Do you accept?";
else
fmt = "You have been challenged to a duel from {0}. Do you accept?";
AddHtml(22 - 1, 50, 294, 40, Color(string.Format(fmt, challenger.Name), BlackColor32));
AddHtml(22 + 1, 50, 294, 40, Color(string.Format(fmt, challenger.Name), BlackColor32));
AddHtml(22, 50 - 1, 294, 40, Color(string.Format(fmt, challenger.Name), BlackColor32));
AddHtml(22, 50 + 1, 294, 40, Color(string.Format(fmt, challenger.Name), BlackColor32));
AddHtml(22, 50, 294, 40, Color(string.Format(fmt, challenger.Name), 0xB0C868));
AddImageTiled(32, 88, 264, 1, 9107);
AddImageTiled(42, 90, 264, 1, 9157);
AddRadio(24, 100, 9727, 9730, true, 1);
AddHtml(60 - 1, 105, 250, 20, Color("Yes, I will fight this duel.", BlackColor32));
AddHtml(60 + 1, 105, 250, 20, Color("Yes, I will fight this duel.", BlackColor32));
AddHtml(60, 105 - 1, 250, 20, Color("Yes, I will fight this duel.", BlackColor32));
AddHtml(60, 105 + 1, 250, 20, Color("Yes, I will fight this duel.", BlackColor32));
AddHtml(60, 105, 250, 20, Color("Yes, I will fight this duel.", LabelColor32));
AddRadio(24, 135, 9727, 9730, false, 2);
AddHtml(60 - 1, 140, 250, 20, Color("No, I do not wish to fight.", BlackColor32));
AddHtml(60 + 1, 140, 250, 20, Color("No, I do not wish to fight.", BlackColor32));
AddHtml(60, 140 - 1, 250, 20, Color("No, I do not wish to fight.", BlackColor32));
AddHtml(60, 140 + 1, 250, 20, Color("No, I do not wish to fight.", BlackColor32));
AddHtml(60, 140, 250, 20, Color("No, I do not wish to fight.", LabelColor32));
AddRadio(24, 170, 9727, 9730, false, 3);
AddHtml(60 - 1, 175, 250, 20, Color("No, knave. Do not ask again.", BlackColor32));
AddHtml(60 + 1, 175, 250, 20, Color("No, knave. Do not ask again.", BlackColor32));
AddHtml(60, 175 - 1, 250, 20, Color("No, knave. Do not ask again.", BlackColor32));
AddHtml(60, 175 + 1, 250, 20, Color("No, knave. Do not ask again.", BlackColor32));
AddHtml(60, 175, 250, 20, Color("No, knave. Do not ask again.", LabelColor32));
AddButton(314, 173, 247, 248, 1);
Timer.DelayCall(TimeSpan.FromSeconds(15.0), AutoReject);
}
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public string Color(string text, int color)
{
return $"<BASEFONT COLOR=#{color:X6}>{text}</BASEFONT>";
}
public void AutoReject()
{
if (!m_Active)
return;
m_Active = false;
m_Challenged.CloseGump<AcceptDuelGump>();
m_Challenger.SendMessage("{0} seems unresponsive.", m_Challenged.Name);
m_Challenged.SendMessage("You decline the challenge.");
}
public static void BeginIgnore(Mobile source, Mobile toIgnore)
{
if (!m_IgnoreLists.TryGetValue(source, out List<IgnoreEntry> list))
m_IgnoreLists[source] = list = new List<IgnoreEntry>();
for (int i = 0; i < list.Count; ++i)
{
IgnoreEntry ie = list[i];
if (ie.Ignored == toIgnore)
{
ie.Refresh();
return;
}
if (ie.Expired)
list.RemoveAt(i--);
}
list.Add(new IgnoreEntry(toIgnore));
}
public static bool IsIgnored(Mobile source, Mobile check)
{
if (!m_IgnoreLists.TryGetValue(source, out List<IgnoreEntry> list))
return false;
for (int i = 0; i < list.Count; ++i)
{
IgnoreEntry ie = list[i];
if (ie.Expired)
list.RemoveAt(i--);
else if (ie.Ignored == check)
return true;
}
return false;
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (info.ButtonID != 1 || !m_Active || !m_Context.Registered)
return;
m_Active = false;
if (!m_Context.Participants.Contains(m_Participant))
return;
if (info.IsSwitched(1))
{
if (!(m_Challenged is PlayerMobile pm))
return;
if (pm.DuelContext != null)
{
if (pm.DuelContext.Initiator == pm)
pm.SendMessage(0x22, "You have already started a duel.");
else
pm.SendMessage(0x22, "You have already been challenged in a duel.");
m_Challenger.SendMessage("{0} cannot fight because they are already assigned to another duel.", pm.Name);
}
else if (DuelContext.CheckCombat(pm))
{
pm.SendMessage(0x22,
"You have recently been in combat with another player and must wait before starting a duel.");
m_Challenger.SendMessage(
"{0} cannot fight because they have recently been in combat with another player.", pm.Name);
}
else if (TournamentController.IsActive)
{
pm.SendMessage(0x22, "A tournament is currently active and you may not duel.");
m_Challenger.SendMessage(0x22, "A tournament is currently active and you may not duel.");
}
else
{
bool added = false;
if (m_Slot >= 0 && m_Slot < m_Participant.Players.Length && m_Participant.Players[m_Slot] == null)
{
added = true;
m_Participant.Players[m_Slot] = new DuelPlayer(m_Challenged, m_Participant);
}
else
{
for (int i = 0; i < m_Participant.Players.Length; ++i)
if (m_Participant.Players[i] == null)
{
added = true;
m_Participant.Players[i] = new DuelPlayer(m_Challenged, m_Participant);
break;
}
}
if (added)
{
m_Challenger.SendMessage("{0} has accepted the request.", m_Challenged.Name);
m_Challenged.SendMessage("You have accepted the request from {0}.", m_Challenger.Name);
NetState ns = m_Challenger.NetState;
if (ns != null)
foreach (Gump g in ns.Gumps)
{
if (g is ParticipantGump pg && pg.Participant == m_Participant)
{
m_Challenger.SendGump(new ParticipantGump(m_Challenger, m_Context, m_Participant));
break;
}
if (g is DuelContextGump dcg && dcg.Context == m_Context)
{
m_Challenger.SendGump(new DuelContextGump(m_Challenger, m_Context));
break;
}
}
}
else
{
m_Challenger.SendMessage("The participant list was full and so {0} could not join.",
m_Challenged.Name);
m_Challenged.SendMessage(
"The participant list was full and so you could not join the fight {1} {0}.", m_Challenger.Name,
m_Participant.Contains(m_Challenger) ? "with" : "against");
}
}
}
else
{
if (info.IsSwitched(3))
BeginIgnore(m_Challenged, m_Challenger);
m_Challenger.SendMessage("{0} does not wish to fight.", m_Challenged.Name);
m_Challenged.SendMessage("You chose not to fight {1} {0}.", m_Challenger.Name,
m_Participant.Contains(m_Challenger) ? "with" : "against");
}
}
private class IgnoreEntry
{
private static TimeSpan ExpireDelay = TimeSpan.FromMinutes(15.0);
public DateTime m_Expire;
public Mobile m_Ignored;
public IgnoreEntry(Mobile ignored)
{
m_Ignored = ignored;
Refresh();
}
public Mobile Ignored => m_Ignored;
public bool Expired => DateTime.UtcNow >= m_Expire;
public void Refresh()
{
m_Expire = DateTime.UtcNow + ExpireDelay;
}
}
}
}

View file

@ -0,0 +1,825 @@
using System;
using System.Collections.Generic;
using Server.Gumps;
using Server.Mobiles;
using Server.Multis;
namespace Server.Engines.ConPVP
{
public class ArenaController : Item
{
[Constructible]
public ArenaController() : base(0x1B7A)
{
Visible = false;
Movable = false;
Arena = new Arena();
Instances.Add(this);
}
public ArenaController(Serial serial) : base(serial)
{
}
[CommandProperty(AccessLevel.GameMaster)]
public Arena Arena{ get; private set; }
[CommandProperty(AccessLevel.GameMaster)]
public bool IsPrivate{ get; set; }
public override string DefaultName => "arena controller";
public static List<ArenaController> Instances{ get; set; } = new List<ArenaController>();
public override void OnDelete()
{
base.OnDelete();
Instances.Remove(this);
Arena.Delete();
}
public override void OnDoubleClick(Mobile from)
{
if (from.AccessLevel >= AccessLevel.GameMaster)
from.SendGump(new PropertiesGump(from, Arena));
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(1);
writer.Write(IsPrivate);
Arena.Serialize(writer);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 1:
{
IsPrivate = reader.ReadBool();
goto case 0;
}
case 0:
{
Arena = new Arena(reader);
break;
}
}
Instances.Add(this);
}
}
[PropertyObject]
public class ArenaStartPoints
{
public ArenaStartPoints(Point3D[] points = null)
{
Points = points ?? new Point3D[8];
}
public ArenaStartPoints(GenericReader reader)
{
Points = new Point3D[reader.ReadEncodedInt()];
for (int i = 0; i < Points.Length; ++i)
Points[i] = reader.ReadPoint3D();
}
public Point3D[] Points{ get; }
[CommandProperty(AccessLevel.GameMaster)]
public Point3D EdgeWest
{
get => Points[0];
set => Points[0] = value;
}
[CommandProperty(AccessLevel.GameMaster)]
public Point3D EdgeEast
{
get => Points[1];
set => Points[1] = value;
}
[CommandProperty(AccessLevel.GameMaster)]
public Point3D EdgeNorth
{
get => Points[2];
set => Points[2] = value;
}
[CommandProperty(AccessLevel.GameMaster)]
public Point3D EdgeSouth
{
get => Points[3];
set => Points[3] = value;
}
[CommandProperty(AccessLevel.GameMaster)]
public Point3D CornerNW
{
get => Points[4];
set => Points[4] = value;
}
[CommandProperty(AccessLevel.GameMaster)]
public Point3D CornerSE
{
get => Points[5];
set => Points[5] = value;
}
[CommandProperty(AccessLevel.GameMaster)]
public Point3D CornerSW
{
get => Points[6];
set => Points[6] = value;
}
[CommandProperty(AccessLevel.GameMaster)]
public Point3D CornerNE
{
get => Points[7];
set => Points[7] = value;
}
public override string ToString()
{
return "...";
}
public void Serialize(GenericWriter writer)
{
writer.WriteEncodedInt(Points.Length);
for (int i = 0; i < Points.Length; ++i)
writer.Write(Points[i]);
}
}
[PropertyObject]
public class Arena : IComparable<Arena>
{
private bool m_Active;
private Rectangle2D m_Bounds;
private Map m_Facet;
private Point3D m_GateOut;
private bool m_IsGuarded;
private string m_Name;
private SafeZone m_Region;
private TournamentController m_Tournament;
private Rectangle2D m_Zone;
public Arena()
{
Points = new ArenaStartPoints();
Players = new List<Mobile>();
}
public Arena(GenericReader reader)
{
int version = reader.ReadEncodedInt();
switch (version)
{
case 7:
{
m_IsGuarded = reader.ReadBool();
goto case 6;
}
case 6:
{
Ladder = reader.ReadItem() as LadderController;
goto case 5;
}
case 5:
{
m_Tournament = reader.ReadItem() as TournamentController;
Announcer = reader.ReadMobile();
goto case 4;
}
case 4:
{
m_Name = reader.ReadString();
goto case 3;
}
case 3:
{
m_Zone = reader.ReadRect2D();
goto case 2;
}
case 2:
{
GateIn = reader.ReadPoint3D();
m_GateOut = reader.ReadPoint3D();
Teleporter = reader.ReadItem();
goto case 1;
}
case 1:
{
Players = reader.ReadStrongMobileList();
goto case 0;
}
case 0:
{
m_Facet = reader.ReadMap();
m_Bounds = reader.ReadRect2D();
Outside = reader.ReadPoint3D();
Wall = reader.ReadPoint3D();
if (version == 0)
{
reader.ReadBool();
Players = new List<Mobile>();
}
m_Active = reader.ReadBool();
Points = new ArenaStartPoints(reader);
if (m_Active)
{
Arenas.Add(this);
Arenas.Sort();
}
break;
}
}
if (m_Zone.Start != Point2D.Zero && m_Zone.End != Point2D.Zero && m_Facet != null)
m_Region = new SafeZone(m_Zone, Outside, m_Facet, m_IsGuarded);
if (IsOccupied)
Timer.DelayCall(TimeSpan.FromSeconds(2.0), Evict);
if (m_Tournament != null)
Timer.DelayCall(TimeSpan.Zero, AttachToTournament_Sandbox);
}
[CommandProperty(AccessLevel.GameMaster)]
public LadderController Ladder{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public bool IsGuarded
{
get => m_IsGuarded;
set
{
m_IsGuarded = value;
if (m_Region != null)
m_Region.Disabled = !m_IsGuarded;
}
}
[CommandProperty(AccessLevel.GameMaster)]
public TournamentController Tournament
{
get => m_Tournament;
set
{
m_Tournament?.Tournament.Arenas.Remove(this);
m_Tournament = value;
m_Tournament?.Tournament.Arenas.Add(this);
}
}
[CommandProperty(AccessLevel.GameMaster)]
public Mobile Announcer{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public string Name
{
get => m_Name;
set
{
m_Name = value;
if (m_Active) Arenas.Sort();
}
}
[CommandProperty(AccessLevel.GameMaster)]
public Map Facet
{
get => m_Facet;
set
{
m_Facet = value;
if (Teleporter != null)
Teleporter.Map = value;
m_Region?.Unregister();
if (m_Zone.Start != Point2D.Zero && m_Zone.End != Point2D.Zero && m_Facet != null)
m_Region = new SafeZone(m_Zone, Outside, m_Facet, m_IsGuarded);
else
m_Region = null;
}
}
[CommandProperty(AccessLevel.GameMaster)]
public Rectangle2D Bounds
{
get => m_Bounds;
set => m_Bounds = value;
}
public int Spectators
{
get
{
if (m_Region == null)
return 0;
int specs = m_Region.GetPlayerCount() - Players.Count;
if (specs < 0)
specs = 0;
return specs;
}
}
[CommandProperty(AccessLevel.GameMaster)]
public Rectangle2D Zone
{
get => m_Zone;
set
{
m_Zone = value;
if (m_Zone.Start != Point2D.Zero && m_Zone.End != Point2D.Zero && m_Facet != null)
{
m_Region?.Unregister();
m_Region = new SafeZone(m_Zone, Outside, m_Facet, m_IsGuarded);
}
else
{
m_Region?.Unregister();
m_Region = null;
}
}
}
[CommandProperty(AccessLevel.GameMaster)]
public Point3D Outside{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public Point3D GateIn{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public Point3D GateOut
{
get => m_GateOut;
set
{
m_GateOut = value;
if (Teleporter != null)
Teleporter.Location = m_GateOut;
}
}
[CommandProperty(AccessLevel.GameMaster)]
public Point3D Wall{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public bool IsOccupied => Players.Count > 0;
[CommandProperty(AccessLevel.GameMaster)]
public ArenaStartPoints Points{ get; private set; }
public Item Teleporter{ get; set; }
public List<Mobile> Players{ get; }
[CommandProperty(AccessLevel.GameMaster)]
public bool Active
{
get => m_Active;
set
{
if (m_Active == value)
return;
m_Active = value;
if (m_Active)
{
Arenas.Add(this);
Arenas.Sort();
}
else
{
Arenas.Remove(this);
}
}
}
[CommandProperty(AccessLevel.Administrator, AccessLevel.Administrator)]
public bool ForceEvict
{
get => false;
set
{
if (value) Evict();
}
}
public static List<Arena> Arenas{ get; } = new List<Arena>();
public int CompareTo(Arena c)
{
string a = m_Name;
string b = c.m_Name;
if (a == null && b == null)
return 0;
if (a == null)
return -1;
if (b == null)
return +1;
return a.CompareTo(b);
}
public Ladder AcquireLadder()
{
return Ladder?.Ladder ?? ConPVP.Ladder.Instance;
}
public void Delete()
{
Active = false;
m_Region?.Unregister();
m_Region = null;
}
public override string ToString()
{
return "...";
}
public Point3D GetBaseStartPoint(int index)
{
if (index < 0)
index = 0;
return Points.Points[index % Points.Points.Length];
}
public void MoveInside(DuelPlayer[] players, int index)
{
if (index < 0)
index = 0;
else
index %= Points.Points.Length;
Point3D start = GetBaseStartPoint(index);
int offset = 0;
Point2D[] offsets = index < 4 ? m_EdgeOffsets : m_CornerOffsets;
int[,] matrix = m_Rotate[index];
for (int i = 0; i < players.Length; ++i)
{
DuelPlayer pl = players[i];
if (pl == null)
continue;
Mobile mob = pl.Mobile;
Point2D p;
if (offset < offsets.Length)
p = offsets[offset++];
else
p = offsets[offsets.Length - 1];
p.X = p.X * matrix[0, 0] + p.Y * matrix[0, 1];
p.Y = p.X * matrix[1, 0] + p.Y * matrix[1, 1];
mob.MoveToWorld(new Point3D(start.X + p.X, start.Y + p.Y, start.Z), m_Facet);
mob.Direction = mob.GetDirectionTo(Wall);
Players.Add(mob);
}
}
private void AttachToTournament_Sandbox()
{
m_Tournament?.Tournament.Arenas.Add(this);
}
public void Evict()
{
Point3D loc;
Map facet;
if (m_Facet == null)
{
loc = new Point3D(2715, 2165, 0);
facet = Map.Felucca;
}
else
{
loc = Outside;
facet = m_Facet;
}
bool hasBounds = m_Bounds.Start != Point2D.Zero && m_Bounds.End != Point2D.Zero;
for (int i = 0; i < Players.Count; ++i)
{
Mobile mob = Players[i];
if (mob == null)
continue;
if (mob.Map == Map.Internal)
{
if ((m_Facet == null || mob.LogoutMap == m_Facet) &&
(!hasBounds || m_Bounds.Contains(mob.LogoutLocation)))
mob.LogoutLocation = loc;
}
else if ((m_Facet == null || mob.Map == m_Facet) && (!hasBounds || m_Bounds.Contains(mob.Location)))
{
mob.MoveToWorld(loc, facet);
}
mob.Combatant = null;
mob.Frozen = false;
DuelContext.Debuff(mob);
DuelContext.CancelSpell(mob);
}
if (hasBounds)
{
List<Mobile> pets = new List<Mobile>();
foreach (Mobile mob in facet.GetMobilesInBounds(m_Bounds))
if (mob is BaseCreature pet && pet.Controlled && pet.ControlMaster != null &&
Players.Contains(pet.ControlMaster))
pets.Add(pet);
foreach (Mobile pet in pets)
{
pet.Combatant = null;
pet.Frozen = false;
pet.MoveToWorld(loc, facet);
}
}
Players.Clear();
}
public void Serialize(GenericWriter writer)
{
writer.WriteEncodedInt(7);
writer.Write(m_IsGuarded);
writer.Write(Ladder);
writer.Write(m_Tournament);
writer.Write(Announcer);
writer.Write(m_Name);
writer.Write(m_Zone);
writer.Write(GateIn);
writer.Write(m_GateOut);
writer.Write(Teleporter);
writer.Write(Players);
writer.Write(m_Facet);
writer.Write(m_Bounds);
writer.Write(Outside);
writer.Write(Wall);
writer.Write(m_Active);
Points.Serialize(writer);
}
public static Arena FindArena(List<Mobile> players)
{
Preferences prefs = Preferences.Instance;
if (prefs == null)
return FindArena();
if (Arenas.Count == 0)
return null;
if (players.Count > 0)
{
Mobile first = players[0];
List<ArenaController> allControllers = ArenaController.Instances;
for (int i = 0; i < allControllers.Count; ++i)
{
ArenaController controller = allControllers[i];
if (controller?.Deleted == false && controller.Arena != null && controller.IsPrivate &&
controller.Map == first.Map && first.InRange(controller, 24))
{
BaseHouse house = BaseHouse.FindHouseAt(controller);
bool allNear = true;
for (int j = 0; j < players.Count; ++j)
{
Mobile check = players[j];
bool isNear;
if (house == null)
isNear = controller.Map == check.Map && check.InRange(controller, 24);
else
isNear = BaseHouse.FindHouseAt(check) == house;
if (!isNear)
{
allNear = false;
break;
}
}
if (allNear)
return controller.Arena;
}
}
}
List<ArenaEntry> arenas = new List<ArenaEntry>();
for (int i = 0; i < Arenas.Count; ++i)
{
Arena arena = Arenas[i];
if (!arena.IsOccupied)
arenas.Add(new ArenaEntry(arena));
}
if (arenas.Count == 0)
return Arenas[0];
int tc = 0;
for (int i = 0; i < arenas.Count; ++i)
{
ArenaEntry ae = arenas[i];
for (int j = 0; j < players.Count; ++j)
{
PreferencesEntry pe = prefs.Find(players[j]);
if (pe.Disliked.Contains(ae.m_Arena.Name))
++ae.m_VotesAgainst;
else
++ae.m_VotesFor;
}
tc += ae.Value;
}
int rn = Utility.Random(tc);
for (int i = 0; i < arenas.Count; ++i)
{
ArenaEntry ae = arenas[i];
if (rn < ae.Value)
return ae.m_Arena;
rn -= ae.Value;
}
return arenas[Utility.Random(arenas.Count)].m_Arena;
}
public static Arena FindArena()
{
if (Arenas.Count == 0)
return null;
int offset = Utility.Random(Arenas.Count);
for (int i = 0; i < Arenas.Count; ++i)
{
Arena arena = Arenas[(i + offset) % Arenas.Count];
if (!arena.IsOccupied)
return arena;
}
return Arenas[offset];
}
private class ArenaEntry
{
public Arena m_Arena;
public int m_VotesAgainst;
public int m_VotesFor;
public ArenaEntry(Arena arena)
{
m_Arena = arena;
}
public int Value => m_VotesFor;
}
#region Offsets & Rotation
private static Point2D[] m_EdgeOffsets =
{
/*
* /\
* /\/\
* /\/\/\
* \/\/\/
* \/\/\
* \/\/
*/
new Point2D(0, 0),
new Point2D(0, -1),
new Point2D(0, +1),
new Point2D(1, 0),
new Point2D(1, -1),
new Point2D(1, +1),
new Point2D(2, 0),
new Point2D(2, -1),
new Point2D(2, +1),
new Point2D(3, 0)
};
// nw corner
private static Point2D[] m_CornerOffsets =
{
/*
* /\
* /\/\
* /\/\/\
* /\/\/\/\
* \/\/\/\/
*/
new Point2D(0, 0),
new Point2D(0, 1),
new Point2D(1, 0),
new Point2D(1, 1),
new Point2D(0, 2),
new Point2D(2, 0),
new Point2D(2, 1),
new Point2D(1, 2),
new Point2D(0, 3),
new Point2D(3, 0)
};
private static int[][,] m_Rotate =
{
new[,] { { +1, 0 }, { 0, +1 } }, // west
new[,] { { -1, 0 }, { 0, -1 } }, // east
new[,] { { 0, +1 }, { +1, 0 } }, // north
new[,] { { 0, -1 }, { -1, 0 } }, // south
new[,] { { +1, 0 }, { 0, +1 } }, // nw
new[,] { { -1, 0 }, { 0, -1 } }, // se
new[,] { { 0, +1 }, { +1, 0 } }, // sw
new[,] { { 0, -1 }, { -1, 0 } } // ne
};
#endregion
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,89 @@
using Server.Items;
namespace Server.Engines.ConPVP
{
public enum DuelTeleporterType
{
Squares = 6095,
Buds = 6104,
Flowers = 6113,
Spikes = 6122,
Arrows = 6140,
Links = 6149
}
public class DuelTeleporterAddon : BaseAddon
{
[Constructible]
public DuelTeleporterAddon(DuelTeleporterType type = DuelTeleporterType.Squares)
{
int itemID = (int)type;
AddComponent(new AddonComponent(itemID + 0), -1, -1, 5);
AddComponent(new AddonComponent(itemID + 1), -1, 0, 5);
AddComponent(new AddonComponent(itemID + 2), 0, -1, 5);
AddComponent(new AddonComponent(itemID + 3), -1, +1, 5);
AddComponent(new AddonComponent(itemID + 4), 0, 0, 5);
AddComponent(new AddonComponent(itemID + 5), +1, -1, 5);
AddComponent(new AddonComponent(itemID + 6), 0, +1, 5);
AddComponent(new AddonComponent(itemID + 7), +1, 0, 5);
AddComponent(new AddonComponent(itemID + 8), +1, +1, 5);
AddComponent(new AddonComponent(0x759), -2, -2, 0);
AddComponent(new AddonComponent(0x75A), +2, +2, 0);
AddComponent(new AddonComponent(0x75B), -2, +2, 0);
AddComponent(new AddonComponent(0x75C), +2, -2, 0);
AddComponent(new AddonComponent(0x751), -1, +2, 0);
AddComponent(new AddonComponent(0x751), 0, +2, 0);
AddComponent(new AddonComponent(0x751), +1, +2, 0);
AddComponent(new AddonComponent(0x752), +2, -1, 0);
AddComponent(new AddonComponent(0x752), +2, 0, 0);
AddComponent(new AddonComponent(0x752), +2, +1, 0);
AddComponent(new AddonComponent(0x753), -1, -2, 0);
AddComponent(new AddonComponent(0x753), 0, -2, 0);
AddComponent(new AddonComponent(0x753), +1, -2, 0);
AddComponent(new AddonComponent(0x754), -2, -1, 0);
AddComponent(new AddonComponent(0x754), -2, 0, 0);
AddComponent(new AddonComponent(0x754), -2, +1, 0);
}
public DuelTeleporterAddon(Serial serial) : base(serial)
{
}
[CommandProperty(AccessLevel.GameMaster)]
public DuelTeleporterType Type
{
get
{
if (Components.Count > 0)
return (DuelTeleporterType)Components[0].ItemID;
return DuelTeleporterType.Squares;
}
set
{
for (int i = 0; i < Components.Count && i < 9; ++i)
Components[i].ItemID = i + (int)value;
}
}
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();
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,77 @@
using Server.Gumps;
using Server.Items;
namespace Server.Engines.ConPVP
{
public abstract class EventController : Item
{
public EventController()
: base(0x1B7A)
{
Visible = false;
Movable = false;
}
public EventController(Serial serial)
: base(serial)
{
}
public abstract string Title{ get; }
public abstract EventGame Construct(DuelContext dc);
public abstract string GetTeamName(int teamID);
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
}
public override void OnDoubleClick(Mobile from)
{
if (from.AccessLevel >= AccessLevel.GameMaster)
from.SendGump(new PropertiesGump(from, this));
}
}
public abstract class EventGame
{
protected DuelContext m_Context;
public EventGame(DuelContext context)
{
m_Context = context;
}
public DuelContext Context => m_Context;
public virtual bool FreeConsume => true;
public virtual bool OnDeath(Mobile mob, Container corpse)
{
return true;
}
public virtual bool CantDoAnything(Mobile mob)
{
return false;
}
public virtual void OnStart()
{
}
public virtual void OnStop()
{
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using System.Text;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class TourneyMatch
{
public TourneyMatch(List<TourneyParticipant> participants)
{
Participants = participants;
for (int i = 0; i < participants.Count; ++i)
{
TourneyParticipant part = participants[i];
StringBuilder sb = new StringBuilder();
sb.Append("Matched in a duel against ");
if (participants.Count > 2)
sb.AppendFormat("{0} other {1}: ", participants.Count - 1,
part.Players.Count == 1 ? "players" : "teams");
bool hasAppended = false;
for (int j = 0; j < participants.Count; ++j)
{
if (i == j)
continue;
if (hasAppended)
sb.Append(", ");
sb.Append(participants[j].NameList);
hasAppended = true;
}
sb.Append(".");
part.AddLog(sb.ToString());
}
}
public List<TourneyParticipant> Participants{ get; set; }
public TourneyParticipant Winner{ get; set; }
public DuelContext Context{ get; set; }
public bool InProgress => Context?.Registered == true;
public void Start(Arena arena, Tournament tourney)
{
TourneyParticipant first = Participants[0];
DuelContext dc = new DuelContext(first.Players[0], tourney.Ruleset.Layout, false);
dc.Ruleset.Options.SetAll(false);
dc.Ruleset.Options.Or(tourney.Ruleset.Options);
for (int i = 0; i < Participants.Count; ++i)
{
TourneyParticipant tourneyPart = Participants[i];
Participant duelPart = new Participant(dc, tourneyPart.Players.Count)
{
TourneyPart = tourneyPart
};
for (int j = 0; j < tourneyPart.Players.Count; ++j)
duelPart.Add(tourneyPart.Players[j]);
for (int j = 0; j < duelPart.Players.Length; ++j)
if (duelPart.Players[j] != null)
duelPart.Players[j].Ready = true;
dc.Participants.Add(duelPart);
}
if (tourney.EventController != null)
dc.m_EventGame = tourney.EventController.Construct(dc);
dc.m_Tournament = tourney;
dc.m_Match = this;
dc.m_OverrideArena = arena;
if (tourney.SuddenDeath > TimeSpan.Zero &&
(tourney.SuddenDeathRounds == 0 || tourney.Pyramid.Levels.Count <= tourney.SuddenDeathRounds))
dc.StartSuddenDeath(tourney.SuddenDeath);
dc.SendReadyGump(0);
if (dc.StartedBeginCountdown)
{
Context = dc;
for (int i = 0; i < Participants.Count; ++i)
{
TourneyParticipant p = Participants[i];
for (int j = 0; j < p.Players.Count; ++j)
{
Mobile mob = p.Players[j];
foreach (Mobile view in mob.GetMobilesInRange(18))
if (!mob.CanSee(view))
mob.Send(view.RemovePacket);
mob.LocalOverheadMessage(MessageType.Emote, 0x3B2, false,
"* Your mind focuses intently on the fight and all other distractions fade away *");
}
}
}
else
{
dc.Unregister();
dc.StopCountdown();
}
}
}
}

View file

@ -0,0 +1,382 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class AcceptTeamGump : Gump
{
private const int BlackColor32 = 0x000008;
private const int LabelColor32 = 0xFFFFFF;
private bool m_Active;
private Mobile m_From;
private List<Mobile> m_Players;
private Mobile m_Registrar;
private Mobile m_Requested;
private Tournament m_Tournament;
public AcceptTeamGump(Mobile from, Mobile requested, Tournament tourney, Mobile registrar, List<Mobile> players) :
base(50, 50)
{
m_From = from;
m_Requested = requested;
m_Tournament = tourney;
m_Registrar = registrar;
m_Players = players;
m_Active = true;
#region Rules
Ruleset ruleset = tourney.Ruleset;
Ruleset basedef = ruleset.Base;
int height = 185 + 35 + 60 + 12;
int changes = 0;
BitArray defs;
if (ruleset.Flavors.Count > 0)
{
defs = new BitArray(basedef.Options);
for (int i = 0; i < ruleset.Flavors.Count; ++i)
defs.Or(ruleset.Flavors[i].Options);
height += ruleset.Flavors.Count * 18;
}
else
{
defs = basedef.Options;
}
BitArray opts = ruleset.Options;
for (int i = 0; i < opts.Length; ++i)
if (defs[i] != opts[i])
++changes;
height += changes * 22;
height += 10 + 22 + 25 + 25;
#endregion
Closable = false;
AddPage(0);
AddBackground(1, 1, 398, height, 3600);
AddImageTiled(16, 15, 369, height - 29, 3604);
AddAlphaRegion(16, 15, 369, height - 29);
AddImage(215, -43, 0xEE40);
StringBuilder sb = new StringBuilder();
if (tourney.TourneyType == TourneyType.FreeForAll)
{
sb.Append("FFA");
}
else if (tourney.TourneyType == TourneyType.RandomTeam)
{
sb.Append(tourney.ParticipantsPerMatch);
sb.Append("-Team");
}
else if (tourney.TourneyType == TourneyType.Faction)
{
sb.Append(tourney.ParticipantsPerMatch);
sb.Append("-Team Faction");
}
else if (tourney.TourneyType == TourneyType.RedVsBlue)
{
sb.Append("Red v Blue");
}
else
{
for (int i = 0; i < tourney.ParticipantsPerMatch; ++i)
{
if (sb.Length > 0)
sb.Append('v');
sb.Append(tourney.PlayersPerParticipant);
}
}
if (tourney.EventController != null)
sb.Append(' ').Append(tourney.EventController.Title);
sb.Append(" Tournament Invitation");
AddBorderedText(22, 22, 294, 20, Center(sb.ToString()), LabelColor32, BlackColor32);
AddBorderedText(22, 50, 294, 40,
$"You have been asked to partner with {from.Name} in a tournament. Do you accept?",
0xB0C868, BlackColor32);
AddImageTiled(32, 88, 264, 1, 9107);
AddImageTiled(42, 90, 264, 1, 9157);
#region Rules
int y = 100;
string groupText = null;
switch (tourney.GroupType)
{
case GroupingType.HighVsLow:
groupText = "High vs Low";
break;
case GroupingType.Nearest:
groupText = "Closest opponent";
break;
case GroupingType.Random:
groupText = "Random";
break;
}
AddBorderedText(35, y, 190, 20, $"Grouping: {groupText}", LabelColor32, BlackColor32);
y += 20;
string tieText = null;
switch (tourney.TieType)
{
case TieType.Random:
tieText = "Random";
break;
case TieType.Highest:
tieText = "Highest advances";
break;
case TieType.Lowest:
tieText = "Lowest advances";
break;
case TieType.FullAdvancement:
tieText = tourney.ParticipantsPerMatch == 2 ? "Both advance" : "Everyone advances";
break;
case TieType.FullElimination:
tieText = tourney.ParticipantsPerMatch == 2 ? "Both eliminated" : "Everyone eliminated";
break;
}
AddBorderedText(35, y, 190, 20, $"Tiebreaker: {tieText}", LabelColor32, BlackColor32);
y += 20;
string sdText = "Off";
if (tourney.SuddenDeath > TimeSpan.Zero)
{
sdText = $"{(int)tourney.SuddenDeath.TotalMinutes}:{tourney.SuddenDeath.Seconds:D2}";
if (tourney.SuddenDeathRounds > 0)
sdText = $"{sdText} (first {tourney.SuddenDeathRounds} rounds)";
else
sdText = $"{sdText} (all rounds)";
}
AddBorderedText(35, y, 240, 20, $"Sudden Death: {sdText}", LabelColor32, BlackColor32);
y += 20;
y += 6;
AddImageTiled(32, y - 1, 264, 1, 9107);
AddImageTiled(42, y + 1, 264, 1, 9157);
y += 6;
AddBorderedText(35, y, 190, 20, $"Ruleset: {basedef.Title}", LabelColor32, BlackColor32);
y += 20;
for (int i = 0; i < ruleset.Flavors.Count; ++i, y += 18)
AddBorderedText(35, y, 190, 20, $" + {ruleset.Flavors[i].Title}", LabelColor32, BlackColor32);
y += 4;
if (changes > 0)
{
AddBorderedText(35, y, 190, 20, "Modifications:", LabelColor32, BlackColor32);
y += 20;
for (int i = 0; i < opts.Length; ++i)
if (defs[i] != opts[i])
{
string name = ruleset.Layout.FindByIndex(i);
if (name != null) // sanity
{
AddImage(35, y, opts[i] ? 0xD3 : 0xD2);
AddBorderedText(60, y, 165, 22, name, LabelColor32, BlackColor32);
}
y += 22;
}
}
else
{
AddBorderedText(35, y, 190, 20, "Modifications: None", LabelColor32, BlackColor32);
y += 20;
}
#endregion
y += 8;
AddImageTiled(32, y - 1, 264, 1, 9107);
AddImageTiled(42, y + 1, 264, 1, 9157);
y += 8;
AddRadio(24, y, 9727, 9730, true, 1);
AddBorderedText(60, y + 5, 250, 20, "Yes, I will join them.", LabelColor32, BlackColor32);
y += 35;
AddRadio(24, y, 9727, 9730, false, 2);
AddBorderedText(60, y + 5, 250, 20, "No, I do not wish to fight.", LabelColor32, BlackColor32);
y += 35;
AddRadio(24, y, 9727, 9730, false, 3);
AddBorderedText(60, y + 5, 270, 20, "No, most certainly not. Do not ask again.", LabelColor32, BlackColor32);
y += 35;
y -= 3;
AddButton(314, y, 247, 248, 1);
Timer.DelayCall(TimeSpan.FromSeconds(15.0), AutoReject);
}
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public string Color(string text, int color)
{
return $"<BASEFONT COLOR=#{color:X6}>{text}</BASEFONT>";
}
private void AddBorderedText(int x, int y, int width, int height, string text, int color, int borderColor)
{
AddColoredText(x - 1, y - 1, width, height, text, borderColor);
AddColoredText(x - 1, y + 1, width, height, text, borderColor);
AddColoredText(x + 1, y - 1, width, height, text, borderColor);
AddColoredText(x + 1, y + 1, width, height, text, borderColor);
AddColoredText(x, y, width, height, text, color);
}
private void AddColoredText(int x, int y, int width, int height, string text, int color)
{
if (color == 0)
AddHtml(x, y, width, height, text);
else
AddHtml(x, y, width, height, Color(text, color));
}
public void AutoReject()
{
if (!m_Active)
return;
m_Active = false;
m_Requested.CloseGump<AcceptTeamGump>();
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
if (m_Registrar != null)
{
m_Registrar.PrivateOverheadMessage(MessageType.Regular,
0x22, false, $"{m_Requested.Name} seems unresponsive.", m_From.NetState);
m_Registrar.PrivateOverheadMessage(MessageType.Regular,
0x22, false, $"You have declined the partnership with {m_From.Name}.", m_Requested.NetState);
}
}
public override void OnResponse(NetState sender, RelayInfo info)
{
Mobile from = m_From;
Mobile mob = m_Requested;
if (info.ButtonID != 1 || !m_Active)
return;
m_Active = false;
if (info.IsSwitched(1))
{
if (!(mob is PlayerMobile pm))
return;
if (AcceptDuelGump.IsIgnored(mob, from) || mob.Blessed)
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "They ignore your invitation.", from.NetState);
}
else if (pm.DuelContext != null)
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "They are already assigned to another duel.", from.NetState);
}
else if (m_Players.Contains(mob))
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "You have already named them as a team member.", from.NetState);
}
else if (m_Tournament.HasParticipant(mob))
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "They have already entered this tournament.", from.NetState);
}
else if (m_Players.Count >= m_Tournament.PlayersPerParticipant)
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "Your team is full.", from.NetState);
}
else
{
m_Players.Add(mob);
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
if (m_Registrar != null)
{
m_Registrar.PrivateOverheadMessage(MessageType.Regular,
0x59, false, $"{mob.Name} has accepted your offer of partnership.", from.NetState);
m_Registrar.PrivateOverheadMessage(MessageType.Regular,
0x59, false, $"You have accepted the partnership with {from.Name}.", mob.NetState);
}
}
}
else
{
if (info.IsSwitched(3))
AcceptDuelGump.BeginIgnore(m_Requested, m_From);
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
if (m_Registrar != null)
{
m_Registrar.PrivateOverheadMessage(MessageType.Regular,
0x22, false, $"{mob.Name} has declined your offer of partnership.", from.NetState);
m_Registrar.PrivateOverheadMessage(MessageType.Regular,
0x22, false, $"You have declined the partnership with {from.Name}.", mob.NetState);
}
}
}
}
}

View file

@ -0,0 +1,285 @@
using System.Collections.Generic;
using System.Text;
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class ArenasMoongate : Item
{
[Constructible]
public ArenasMoongate() : base(0x1FD4)
{
Movable = false;
Light = LightType.Circle300;
}
public ArenasMoongate(Serial serial) : base(serial)
{
}
public override string DefaultName => "arena moongate";
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
Light = LightType.Circle300;
}
public bool UseGate(Mobile from)
{
if (DuelContext.CheckCombat(from))
{
from.SendMessage(0x22, "You have recently been in combat with another player and cannot use this moongate.");
return false;
}
if (from.Spell != null)
{
from.SendLocalizedMessage(1049616); // You are too busy to do that at the moment.
return false;
}
from.CloseGump<ArenaGump>();
from.SendGump(new ArenaGump(from, this));
if (!from.Hidden || from.AccessLevel == AccessLevel.Player)
Effects.PlaySound(from.Location, from.Map, 0x20E);
return true;
}
public override void OnDoubleClick(Mobile from)
{
if (from.InRange(GetWorldLocation(), 1))
UseGate(from);
else
from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019045); // I can't reach that
}
public override bool OnMoveOver(Mobile m)
{
return !m.Player || UseGate(m);
}
}
public class ArenaGump : Gump
{
private List<Arena> m_Arenas;
private int m_ColumnX = 12;
private Mobile m_From;
private ArenasMoongate m_Gate;
public ArenaGump(Mobile from, ArenasMoongate gate) : base(50, 50)
{
m_From = from;
m_Gate = gate;
m_Arenas = Arena.Arenas;
AddPage(0);
int height = 12 + 20 + m_Arenas.Count * 31 + 24 + 12;
AddBackground(0, 0, 499 + 40, height, 0x2436);
List<Arena> list = m_Arenas;
for (int i = 1; i < list.Count; i += 2)
AddImageTiled(12, 32 + i * 31, 475 + 40, 30, 0x2430);
AddAlphaRegion(10, 10, 479 + 40, height - 20);
AddColumnHeader(35, null);
AddColumnHeader(115, "Arena");
AddColumnHeader(325, "Participants");
AddColumnHeader(40, "Obs");
AddButton(499 + 40 - 12 - 63 - 4 - 63, height - 12 - 24, 247, 248, 1);
AddButton(499 + 40 - 12 - 63, height - 12 - 24, 241, 242, 2);
for (int i = 0; i < list.Count; ++i)
{
Arena ar = list[i];
int x = 12;
int y = 32 + i * 31;
int color = ar.Players.Count > 0 ? 0xCCFFCC : 0xCCCCCC;
AddRadio(x + 3, y + 1, 9727, 9730, false, i);
x += 35;
AddBorderedText(x + 5, y + 5, 115 - 5, ar.Name ?? "(no name)", color, 0);
x += 115;
StringBuilder sb = new StringBuilder();
if (ar.Players.Count > 0)
{
Ladder ladder = Ladder.Instance;
if (ladder == null)
continue;
LadderEntry p1 = null, p2 = null, p3 = null, p4 = null;
for (int j = 0; j < ar.Players.Count; ++j)
{
Mobile mob = ar.Players[j];
LadderEntry c = ladder.Find(mob);
if (p1 == null || c.Index < p1.Index)
{
p4 = p3;
p3 = p2;
p2 = p1;
p1 = c;
}
else if (p2 == null || c.Index < p2.Index)
{
p4 = p3;
p3 = p2;
p2 = c;
}
else if (p3 == null || c.Index < p3.Index)
{
p4 = p3;
p3 = c;
}
else if (p4 == null || c.Index < p4.Index)
{
p4 = c;
}
}
Append(sb, p1);
Append(sb, p2);
Append(sb, p3);
Append(sb, p4);
if (ar.Players.Count > 4)
sb.Append(", ...");
}
else
{
sb.Append("Empty");
}
AddBorderedText(x + 5, y + 5, 325 - 5, sb.ToString(), color, 0);
x += 325;
AddBorderedText(x, y + 5, 40, Center(ar.Spectators.ToString()), color, 0);
}
}
private void Append(StringBuilder sb, LadderEntry le)
{
if (le == null)
return;
if (sb.Length > 0)
sb.Append(", ");
sb.Append(le.Mobile.Name);
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (info.ButtonID != 1)
return;
int[] switches = info.Switches;
if (switches.Length == 0)
return;
int opt = switches[0];
if (opt < 0 || opt >= m_Arenas.Count)
return;
Arena arena = m_Arenas[opt];
if (!m_From.InRange(m_Gate.GetWorldLocation(), 1) || m_From.Map != m_Gate.Map)
{
m_From.SendLocalizedMessage(1019002); // You are too far away to use the gate.
}
else if (DuelContext.CheckCombat(m_From))
{
m_From.SendMessage(0x22,
"You have recently been in combat with another player and cannot use this moongate.");
}
else if (m_From.Spell != null)
{
m_From.SendLocalizedMessage(1049616); // You are too busy to do that at the moment.
}
else if (m_From.Map == arena.Facet && arena.Zone.Contains(m_From))
{
m_From.SendLocalizedMessage(1019003); // You are already there.
}
else
{
BaseCreature.TeleportPets(m_From, arena.GateIn, arena.Facet);
m_From.Combatant = null;
m_From.Warmode = false;
m_From.Hidden = true;
m_From.MoveToWorld(arena.GateIn, arena.Facet);
Effects.PlaySound(arena.GateIn, arena.Facet, 0x1FE);
}
}
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public string Color(string text, int color)
{
return $"<BASEFONT COLOR=#{color:X6}>{text}</BASEFONT>";
}
private void AddBorderedText(int x, int y, int width, string text, int color, int borderColor)
{
/*AddColoredText( x - 1, y, width, text, borderColor );
AddColoredText( x + 1, y, width, text, borderColor );
AddColoredText( x, y - 1, width, text, borderColor );
AddColoredText( x, y + 1, width, text, borderColor );*/
/*AddColoredText( x - 1, y - 1, width, text, borderColor );
AddColoredText( x + 1, y + 1, width, text, borderColor );*/
AddColoredText(x, y, width, text, color);
}
private void AddColoredText(int x, int y, int width, string text, int color)
{
if (color == 0)
AddHtml(x, y, width, 20, text);
else
AddHtml(x, y, width, 20, Color(text, color));
}
private void AddColumnHeader(int width, string name)
{
AddBackground(m_ColumnX, 12, width, 20, 0x242C);
AddImageTiled(m_ColumnX + 2, 14, width - 4, 16, 0x2430);
if (name != null)
AddBorderedText(m_ColumnX, 13, width, Center(name), 0xFFFFFF, 0);
m_ColumnX += width;
}
}
}

View file

@ -0,0 +1,72 @@
using Server.Gumps;
namespace Server.Engines.ConPVP
{
public class BeginGump : Gump
{
private const int LabelColor32 = 0xFFFFFF;
private const int BlackColor32 = 0x000008;
public BeginGump(int count) : base(50, 50)
{
AddPage(0);
const int offset = 50;
AddBackground(1, 1, 398, 202 - offset, 3600);
AddImageTiled(16, 15, 369, 173 - offset, 3604);
AddAlphaRegion(16, 15, 369, 173 - offset);
AddImage(215, -43, 0xEE40);
AddHtml(22 - 1, 22, 294, 20, Color(Center("Duel Countdown"), BlackColor32));
AddHtml(22 + 1, 22, 294, 20, Color(Center("Duel Countdown"), BlackColor32));
AddHtml(22, 22 - 1, 294, 20, Color(Center("Duel Countdown"), BlackColor32));
AddHtml(22, 22 + 1, 294, 20, Color(Center("Duel Countdown"), BlackColor32));
AddHtml(22, 22, 294, 20, Color(Center("Duel Countdown"), LabelColor32));
AddHtml(22 - 1, 50, 294, 80,
Color(
"The arranged duel is about to begin. During this countdown period you may not cast spells and you may not move. This message will close automatically when the period ends.",
BlackColor32));
AddHtml(22 + 1, 50, 294, 80,
Color(
"The arranged duel is about to begin. During this countdown period you may not cast spells and you may not move. This message will close automatically when the period ends.",
BlackColor32));
AddHtml(22, 50 - 1, 294, 80,
Color(
"The arranged duel is about to begin. During this countdown period you may not cast spells and you may not move. This message will close automatically when the period ends.",
BlackColor32));
AddHtml(22, 50 + 1, 294, 80,
Color(
"The arranged duel is about to begin. During this countdown period you may not cast spells and you may not move. This message will close automatically when the period ends.",
BlackColor32));
AddHtml(22, 50, 294, 80,
Color(
"The arranged duel is about to begin. During this countdown period you may not cast spells and you may not move. This message will close automatically when the period ends.",
0xFFCC66));
/*AddImageTiled( 32, 128, 264, 1, 9107 );
AddImageTiled( 42, 130, 264, 1, 9157 );
AddHtml( 60-1, 140, 250, 20, Color( String.Format( "Duel will begin in <BASEFONT COLOR=#{2:X6}>{0} <BASEFONT COLOR=#{2:X6}>second{1}.", count, count==1?"":"s", BlackColor32 ), BlackColor32 ), false, false );
AddHtml( 60+1, 140, 250, 20, Color( String.Format( "Duel will begin in <BASEFONT COLOR=#{2:X6}>{0} <BASEFONT COLOR=#{2:X6}>second{1}.", count, count==1?"":"s", BlackColor32 ), BlackColor32 ), false, false );
AddHtml( 60, 140-1, 250, 20, Color( String.Format( "Duel will begin in <BASEFONT COLOR=#{2:X6}>{0} <BASEFONT COLOR=#{2:X6}>second{1}.", count, count==1?"":"s", BlackColor32 ), BlackColor32 ), false, false );
AddHtml( 60, 140+1, 250, 20, Color( String.Format( "Duel will begin in <BASEFONT COLOR=#{2:X6}>{0} <BASEFONT COLOR=#{2:X6}>second{1}.", count, count==1?"":"s", BlackColor32 ), BlackColor32 ), false, false );
AddHtml( 60, 140, 250, 20, Color( String.Format( "Duel will begin in <BASEFONT COLOR=#FF6666>{0} <BASEFONT COLOR=#{2:X6}>second{1}.", count, count==1?"":"s", 0x66AACC ), 0x66AACC ), false, false );*/
AddButton(314 - 50, 157 - offset, 247, 248, 1);
}
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public string Color(string text, int color)
{
return $"<BASEFONT COLOR=#{color:X6}>{text}</BASEFONT>";
}
}
}

View file

@ -0,0 +1,566 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Server.Factions;
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
using Server.Targeting;
namespace Server.Engines.ConPVP
{
public class ConfirmSignupGump : Gump
{
private const int BlackColor32 = 0x000008;
private const int LabelColor32 = 0xFFFFFF;
private Mobile m_From;
private List<Mobile> m_Players;
private Mobile m_Registrar;
private Tournament m_Tournament;
public ConfirmSignupGump(Mobile from, Mobile registrar, Tournament tourney, List<Mobile> players) : base(50, 50)
{
m_From = from;
m_Registrar = registrar;
m_Tournament = tourney;
m_Players = players;
m_From.CloseGump<AcceptTeamGump>();
m_From.CloseGump<AcceptDuelGump>();
m_From.CloseGump<DuelContextGump>();
m_From.CloseGump<ConfirmSignupGump>();
#region Rules
Ruleset ruleset = tourney.Ruleset;
Ruleset basedef = ruleset.Base;
int height = 185 + 60 + 12;
int changes = 0;
BitArray defs;
if (ruleset.Flavors.Count > 0)
{
defs = new BitArray(basedef.Options);
for (int i = 0; i < ruleset.Flavors.Count; ++i)
defs.Or(ruleset.Flavors[i].Options);
height += ruleset.Flavors.Count * 18;
}
else
{
defs = basedef.Options;
}
BitArray opts = ruleset.Options;
for (int i = 0; i < opts.Length; ++i)
if (defs[i] != opts[i])
++changes;
height += changes * 22;
height += 10 + 22 + 25 + 25;
if (tourney.PlayersPerParticipant > 1)
height += 36 + tourney.PlayersPerParticipant * 20;
#endregion
Closable = false;
AddPage(0);
//AddBackground( 0, 0, 400, 220, 9150 );
AddBackground(1, 1, 398, height, 3600);
//AddBackground( 16, 15, 369, 189, 9100 );
AddImageTiled(16, 15, 369, height - 29, 3604);
AddAlphaRegion(16, 15, 369, height - 29);
AddImage(215, -43, 0xEE40);
//AddImage( 330, 141, 0x8BA );
StringBuilder sb = new StringBuilder();
if (tourney.TourneyType == TourneyType.FreeForAll)
{
sb.Append("FFA");
}
else if (tourney.TourneyType == TourneyType.RandomTeam)
{
sb.Append(tourney.ParticipantsPerMatch);
sb.Append("-Team");
}
else if (tourney.TourneyType == TourneyType.Faction)
{
sb.Append(tourney.ParticipantsPerMatch);
sb.Append("-Team Faction");
}
else if (tourney.TourneyType == TourneyType.RedVsBlue)
{
sb.Append("Red v Blue");
}
else
{
for (int i = 0; i < tourney.ParticipantsPerMatch; ++i)
{
if (sb.Length > 0)
sb.Append('v');
sb.Append(tourney.PlayersPerParticipant);
}
}
if (tourney.EventController != null)
sb.Append(' ').Append(tourney.EventController.Title);
sb.Append(" Tournament Signup");
AddBorderedText(22, 22, 294, 20, Center(sb.ToString()), LabelColor32, BlackColor32);
AddBorderedText(22, 50, 294, 40, "You have requested to join the tournament. Do you accept the rules?", 0xB0C868,
BlackColor32);
AddImageTiled(32, 88, 264, 1, 9107);
AddImageTiled(42, 90, 264, 1, 9157);
#region Rules
int y = 100;
string groupText = null;
switch (tourney.GroupType)
{
case GroupingType.HighVsLow:
groupText = "High vs Low";
break;
case GroupingType.Nearest:
groupText = "Closest opponent";
break;
case GroupingType.Random:
groupText = "Random";
break;
}
AddBorderedText(35, y, 190, 20, $"Grouping: {groupText}", LabelColor32, BlackColor32);
y += 20;
string tieText = null;
switch (tourney.TieType)
{
case TieType.Random:
tieText = "Random";
break;
case TieType.Highest:
tieText = "Highest advances";
break;
case TieType.Lowest:
tieText = "Lowest advances";
break;
case TieType.FullAdvancement:
tieText = tourney.ParticipantsPerMatch == 2 ? "Both advance" : "Everyone advances";
break;
case TieType.FullElimination:
tieText = tourney.ParticipantsPerMatch == 2 ? "Both eliminated" : "Everyone eliminated";
break;
}
AddBorderedText(35, y, 190, 20, $"Tiebreaker: {tieText}", LabelColor32, BlackColor32);
y += 20;
string sdText = "Off";
if (tourney.SuddenDeath > TimeSpan.Zero)
{
sdText = $"{(int)tourney.SuddenDeath.TotalMinutes}:{tourney.SuddenDeath.Seconds:D2}";
if (tourney.SuddenDeathRounds > 0)
sdText = $"{sdText} (first {tourney.SuddenDeathRounds} rounds)";
else
sdText = $"{sdText} (all rounds)";
}
AddBorderedText(35, y, 240, 20, $"Sudden Death: {sdText}", LabelColor32, BlackColor32);
y += 20;
y += 6;
AddImageTiled(32, y - 1, 264, 1, 9107);
AddImageTiled(42, y + 1, 264, 1, 9157);
y += 6;
AddBorderedText(35, y, 190, 20, $"Ruleset: {basedef.Title}", LabelColor32, BlackColor32);
y += 20;
for (int i = 0; i < ruleset.Flavors.Count; ++i, y += 18)
AddBorderedText(35, y, 190, 20, $" + {ruleset.Flavors[i].Title}", LabelColor32, BlackColor32);
y += 4;
if (changes > 0)
{
AddBorderedText(35, y, 190, 20, "Modifications:", LabelColor32, BlackColor32);
y += 20;
for (int i = 0; i < opts.Length; ++i)
if (defs[i] != opts[i])
{
string name = ruleset.Layout.FindByIndex(i);
if (name != null) // sanity
{
AddImage(35, y, opts[i] ? 0xD3 : 0xD2);
AddBorderedText(60, y, 165, 22, name, LabelColor32, BlackColor32);
}
y += 22;
}
}
else
{
AddBorderedText(35, y, 190, 20, "Modifications: None", LabelColor32, BlackColor32);
y += 20;
}
#endregion
#region Team
if (tourney.PlayersPerParticipant > 1)
{
y += 8;
AddImageTiled(32, y - 1, 264, 1, 9107);
AddImageTiled(42, y + 1, 264, 1, 9157);
y += 8;
AddBorderedText(35, y, 190, 20, "Your Team", LabelColor32, BlackColor32);
y += 20;
for (int i = 0; i < players.Count; ++i, y += 20)
{
if (i == 0)
AddImage(35, y, 0xD2);
else
AddGoldenButton(35, y, 1 + i);
AddBorderedText(60, y, 200, 20, players[i].Name, LabelColor32, BlackColor32);
}
for (int i = players.Count; i < tourney.PlayersPerParticipant; ++i, y += 20)
{
if (i == 0)
AddImage(35, y, 0xD2);
else
AddGoldenButton(35, y, 1 + i);
AddBorderedText(60, y, 200, 20, "(Empty)", LabelColor32, BlackColor32);
}
}
#endregion
y += 8;
AddImageTiled(32, y - 1, 264, 1, 9107);
AddImageTiled(42, y + 1, 264, 1, 9157);
y += 8;
AddRadio(24, y, 9727, 9730, true, 1);
AddBorderedText(60, y + 5, 250, 20, "Yes, I wish to join the tournament.", LabelColor32, BlackColor32);
y += 35;
AddRadio(24, y, 9727, 9730, false, 2);
AddBorderedText(60, y + 5, 250, 20, "No, I do not wish to join.", LabelColor32, BlackColor32);
y += 35;
y -= 3;
AddButton(314, y, 247, 248, 1);
}
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public string Color(string text, int color)
{
return $"<BASEFONT COLOR=#{color:X6}>{text}</BASEFONT>";
}
private void AddBorderedText(int x, int y, int width, int height, string text, int color, int borderColor)
{
AddColoredText(x - 1, y - 1, width, height, text, borderColor);
AddColoredText(x - 1, y + 1, width, height, text, borderColor);
AddColoredText(x + 1, y - 1, width, height, text, borderColor);
AddColoredText(x + 1, y + 1, width, height, text, borderColor);
AddColoredText(x, y, width, height, text, color);
}
private void AddColoredText(int x, int y, int width, int height, string text, int color)
{
if (color == 0)
AddHtml(x, y, width, height, text);
else
AddHtml(x, y, width, height, Color(text, color));
}
public void AddGoldenButton(int x, int y, int bid)
{
AddButton(x, y, 0xD2, 0xD2, bid);
AddButton(x + 3, y + 3, 0xD8, 0xD8, bid);
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (info.ButtonID == 1 && info.IsSwitched(1))
{
Tournament tourney = m_Tournament;
Mobile from = m_From;
switch (tourney.Stage)
{
case TournamentStage.Fighting:
{
if (m_Registrar != null)
{
if (m_Tournament.HasParticipant(from))
m_Registrar.PrivateOverheadMessage(MessageType.Regular,
0x35, false, "Excuse me? You are already signed up.", from.NetState);
else
m_Registrar.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "The tournament has already begun. You are too late to signup now.",
from.NetState);
}
break;
}
case TournamentStage.Inactive:
{
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x35, false, "The tournament is closed.", from.NetState);
break;
}
case TournamentStage.Signup:
{
if (m_Players.Count != tourney.PlayersPerParticipant)
{
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x35, false, "You have not yet chosen your team.", from.NetState);
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
break;
}
Ladder ladder = Ladder.Instance;
for (int i = 0; i < m_Players.Count; ++i)
{
Mobile mob = m_Players[i];
LadderEntry entry = ladder?.Find(mob);
if (entry != null && Ladder.GetLevel(entry.Experience) < tourney.LevelRequirement)
{
if (m_Registrar != null)
{
if (mob == from)
m_Registrar.PrivateOverheadMessage(MessageType.Regular,
0x35, false, "You have not yet proven yourself a worthy dueler.", from.NetState);
else
m_Registrar.PrivateOverheadMessage(MessageType.Regular,
0x35, false, $"{mob.Name} has not yet proven themselves a worthy dueler.",
from.NetState);
}
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
return;
}
if (tourney.IsFactionRestricted && Faction.Find(mob) == null)
{
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x35, false, "Only those who have declared their faction allegiance may participate.",
from.NetState);
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
return;
}
if (tourney.HasParticipant(mob))
{
if (m_Registrar != null)
{
if (mob == from)
m_Registrar.PrivateOverheadMessage(MessageType.Regular,
0x35, false, "You have already entered this tournament.", from.NetState);
else
m_Registrar.PrivateOverheadMessage(MessageType.Regular,
0x35, false, $"{mob.Name} has already entered this tournament.", from.NetState);
}
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
return;
}
if (mob is PlayerMobile mobile && mobile.DuelContext != null)
{
if (mob == from)
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x35, false,
"You are already assigned to a duel. You must yield it before joining this tournament.",
from.NetState);
else
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x35, false,
$"{mobile.Name} is already assigned to a duel. They must yield it before joining this tournament.",
from.NetState);
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
return;
}
}
if (m_Registrar != null)
{
string fmt;
if (tourney.PlayersPerParticipant == 1)
fmt =
"As you say m'{0}. I've written your name to the bracket. The tournament will begin {1}.";
else if (tourney.PlayersPerParticipant == 2)
fmt =
"As you wish m'{0}. The tournament will begin {1}, but first you must name your partner.";
else
fmt = "As you wish m'{0}. The tournament will begin {1}, but first you must name your team.";
string timeUntil;
int minutesUntil = (int)Math.Round((tourney.SignupStart + tourney.SignupPeriod - DateTime.UtcNow)
.TotalMinutes);
if (minutesUntil == 0)
timeUntil = "momentarily";
else
timeUntil = $"in {minutesUntil} minute{(minutesUntil == 1 ? "" : "s")}";
m_Registrar.PrivateOverheadMessage(MessageType.Regular,
0x35, false, string.Format(fmt, from.Female ? "Lady" : "Lord", timeUntil), from.NetState);
}
TourneyParticipant part = new TourneyParticipant(from);
part.Players.Clear();
part.Players.AddRange(m_Players);
tourney.Participants.Add(part);
break;
}
}
}
else if (info.ButtonID > 1)
{
int index = info.ButtonID - 1;
if (index > 0 && index < m_Players.Count)
{
m_Players.RemoveAt(index);
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
}
else if (m_Players.Count < m_Tournament.PlayersPerParticipant)
{
m_From.BeginTarget(12, false, TargetFlags.None, AddPlayer_OnTarget);
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
}
}
}
private void AddPlayer_OnTarget(Mobile from, object obj)
{
if (!(obj is Mobile mob) || mob == from)
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "Excuse me?", from.NetState);
}
else if (!mob.Player)
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
if (mob.Body.IsHuman)
mob.SayTo(from, 1005443); // Nay, I would rather stay here and watch a nail rust.
else
mob.SayTo(from, 1005444); // The creature ignores your offer.
}
else if (AcceptDuelGump.IsIgnored(mob, from) || mob.Blessed)
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "They ignore your invitation.", from.NetState);
}
else
{
if (!(mob is PlayerMobile pm))
return;
if (pm.DuelContext != null)
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "They are already assigned to another duel.", from.NetState);
}
else if (mob.HasGump<AcceptTeamGump>())
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "They have already been offered a partnership.", from.NetState);
}
else if (mob.HasGump<ConfirmSignupGump>())
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "They are already trying to join this tournament.", from.NetState);
}
else if (m_Players.Contains(mob))
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "You have already named them as a team member.", from.NetState);
}
else if (m_Tournament.HasParticipant(mob))
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "They have already entered this tournament.", from.NetState);
}
else if (m_Players.Count >= m_Tournament.PlayersPerParticipant)
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "Your team is full.", from.NetState);
}
else
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
mob.SendGump(new AcceptTeamGump(from, mob, m_Tournament, m_Registrar, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x59, false,
$"As you command m'{(from.Female ? "Lady" : "Lord")}. I've given your offer to {mob.Name}.",
from.NetState);
}
}
}
}
}

View file

@ -0,0 +1,139 @@
using Server.Gumps;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class DuelContextGump : Gump
{
public DuelContextGump(Mobile from, DuelContext context) : base(50, 50)
{
From = from;
Context = context;
from.CloseGump<RulesetGump>();
from.CloseGump<DuelContextGump>();
from.CloseGump<ParticipantGump>();
int count = context.Participants.Count;
if (count < 3)
count = 3;
int height = 35 + 10 + 22 + 30 + 22 + 22 + 2 + count * 22 + 2 + 30;
AddPage(0);
AddBackground(0, 0, 300, height, 9250);
AddBackground(10, 10, 280, height - 20, 0xDAC);
AddHtml(35, 25, 230, 20, Center("Duel Setup"));
int x = 35;
int y = 47;
AddGoldenButtonLabeled(x, y, 1, "Rules");
y += 22;
AddGoldenButtonLabeled(x, y, 2, "Start");
y += 22;
AddGoldenButtonLabeled(x, y, 3, "Add Participant");
y += 30;
AddHtml(35, y, 230, 20, Center("Participants"));
y += 22;
for (int i = 0; i < context.Participants.Count; ++i)
{
Participant p = context.Participants[i];
AddGoldenButtonLabeled(x, y, 4 + i,
string.Format(p.Count == 1 ? "Player {0}: {3}" : "Team {0}: {1}/{2}: {3}", 1 + i, p.FilledSlots, p.Count,
p.NameList));
y += 22;
}
}
public Mobile From{ get; }
public DuelContext Context{ get; }
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public void AddGoldenButton(int x, int y, int bid)
{
AddButton(x, y, 0xD2, 0xD2, bid);
AddButton(x + 3, y + 3, 0xD8, 0xD8, bid);
}
public void AddGoldenButtonLabeled(int x, int y, int bid, string text)
{
AddGoldenButton(x, y, bid);
AddHtml(x + 25, y, 200, 20, text);
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (!Context.Registered)
return;
int index = info.ButtonID;
switch (index)
{
case -1: // CloseGump
{
break;
}
case 0: // closed
{
Context.Unregister();
break;
}
case 1: // Rules
{
//m_From.SendGump( new RulesetGump( m_From, m_Context.Ruleset, m_Context.Ruleset.Layout, m_Context ) );
From.SendGump(new PickRulesetGump(From, Context, Context.Ruleset));
break;
}
case 2: // Start
{
if (Context.CheckFull())
{
Context.CloseAllGumps();
Context.SendReadyUpGump();
//m_Context.SendReadyGump();
}
else
{
From.SendMessage("You cannot start the duel before all participating players have been assigned.");
From.SendGump(new DuelContextGump(From, Context));
}
break;
}
case 3: // New Participant
{
if (Context.Participants.Count < 10)
Context.Participants.Add(new Participant(Context, 1));
else
From.SendMessage("The number of participating parties may not be increased further.");
From.SendGump(new DuelContextGump(From, Context));
break;
}
default: // Participant
{
index -= 4;
if (index >= 0 && index < Context.Participants.Count)
From.SendGump(new ParticipantGump(From, Context, Context.Participants[index]));
break;
}
}
}
}
}

View file

@ -0,0 +1,248 @@
using System;
using System.Collections.Generic;
using Server.Gumps;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class LadderItem : Item
{
[Constructible]
public LadderItem() : base(0x117F)
{
Movable = false;
}
public LadderItem(Serial serial) : base(serial)
{
}
[CommandProperty(AccessLevel.GameMaster)]
public LadderController Ladder{ get; set; }
public override string DefaultName => "1v1 leaderboard";
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(1);
writer.Write(Ladder);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 1:
{
Ladder = reader.ReadItem<LadderController>();
break;
}
}
}
public override void OnDoubleClick(Mobile from)
{
if (from.InRange(GetWorldLocation(), 2))
{
Ladder ladder = ConPVP.Ladder.Instance ?? Ladder.Ladder;
if (ladder != null)
{
from.CloseGump<LadderGump>();
from.SendGump(new LadderGump(ladder));
}
}
else
{
from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019045); // I can't reach that
}
}
}
public class LadderGump : Gump
{
private int m_ColumnX = 12;
private Ladder m_Ladder;
private List<LadderEntry> m_List;
private int m_Page;
public LadderGump(Ladder ladder, int page = 0) : base(50, 50)
{
m_Ladder = ladder;
m_Page = page;
AddPage(0);
m_List = new List<LadderEntry>(ladder.Entries);
int lc = Math.Min(m_List.Count, 150);
int start = page * 15;
int end = start + 15;
if (end > lc)
end = lc;
int ct = end - start;
int height = 12 + 20 + ct * 20 + 23 + 12;
AddBackground(0, 0, 499, height, 0x2436);
for (int i = start + 1; i < end; i += 2)
AddImageTiled(12, 32 + (i - start) * 20, 475, 20, 0x2430);
AddAlphaRegion(10, 10, 479, height - 20);
if (page > 0)
AddButton(446, height - 12 - 2 - 16, 0x15E3, 0x15E7, 1);
else
AddImage(446, height - 12 - 2 - 16, 0x2626);
if ((page + 1) * 15 < lc)
AddButton(466, height - 12 - 2 - 16, 0x15E1, 0x15E5, 2);
else
AddImage(466, height - 12 - 2 - 16, 0x2622);
AddHtml(16, height - 12 - 2 - 18, 400, 20,
Color(string.Format("Top {3} of {0:N0} duelists, page {1} of {2}", m_List.Count, page + 1, (lc + 14) / 15, lc),
0xFFC000));
AddColumnHeader(75, "Rank");
AddColumnHeader(115, "Level");
AddColumnHeader(50, "Guild");
AddColumnHeader(115, "Name");
AddColumnHeader(60, "Wins");
AddColumnHeader(60, "Losses");
for (int i = start; i < end && i < lc; ++i)
{
LadderEntry entry = m_List[i];
int y = 32 + (i - start) * 20;
int x = 12;
AddBorderedText(x, y, 75, Center(Rank(i + 1)), 0xFFFFFF, 0);
x += 75;
/*AddImage( 20, y + 5, 0x2616, 0x96C );
AddImage( 22, y + 5, 0x2616, 0x96C );
AddImage( 20, y + 7, 0x2616, 0x96C );
AddImage( 22, y + 7, 0x2616, 0x96C );
AddImage( 21, y + 6, 0x2616, 0x454 );*/
AddImage(x + 3, y + 4, 0x805);
int xp = entry.Experience;
int level = Ladder.GetLevel(xp);
Ladder.GetLevelInfo(level, out int xpBase, out int xpAdvance);
int width;
int xpOffset = xp - xpBase;
if (xpOffset >= xpAdvance)
width = 109; // level 50
else
width = (109 * xpOffset + xpAdvance / 2) / (xpAdvance - 1);
//AddImageTiled( 21, y + 6, width, 8, 0x2617 );
AddImageTiled(x + 3, y + 4, width, 11, 0x806);
AddBorderedText(x, y, 115, Center(level.ToString()), 0xFFFFFF, 0);
x += 115;
Mobile mob = entry.Mobile;
if (mob.Guild != null)
AddBorderedText(x, y, 50, Center(mob.Guild.Abbreviation), 0xFFFFFF, 0);
x += 50;
AddBorderedText(x + 5, y, 115 - 5, mob.Name, 0xFFFFFF, 0);
x += 115;
AddBorderedText(x, y, 60, Center(entry.Wins.ToString()), 0xFFFFFF, 0);
x += 60;
AddBorderedText(x, y, 60, Center(entry.Losses.ToString()), 0xFFFFFF, 0);
x += 60;
//AddBorderedText( 292 + 15, y, 115 - 30, String.Format( "{0} <DIV ALIGN=CENTER>/</DIV> <DIV ALIGN=RIGHT>{1}</DIV>", entry.Wins, entry.Losses ), 0xFFC000, 0 );
}
}
public static string Rank(int num)
{
string numStr = num.ToString("N0");
if (num % 100 > 10 && num % 100 < 20)
return numStr + "th";
switch (num % 10)
{
case 1: return numStr + "st";
case 2: return numStr + "nd";
case 3: return numStr + "rd";
default: return numStr + "th";
}
}
public override void OnResponse(NetState sender, RelayInfo info)
{
Mobile from = sender.Mobile;
if (info.ButtonID == 1 && m_Page > 0)
from.SendGump(new LadderGump(m_Ladder, m_Page - 1));
else if (info.ButtonID == 2 && (m_Page + 1) * 15 < Math.Min(m_List.Count, 150))
from.SendGump(new LadderGump(m_Ladder, m_Page + 1));
}
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public string Color(string text, int color)
{
return $"<BASEFONT COLOR=#{color:X6}>{text}</BASEFONT>";
}
private void AddBorderedText(int x, int y, int width, string text, int color, int borderColor)
{
/*AddColoredText( x - 1, y, width, text, borderColor );
AddColoredText( x + 1, y, width, text, borderColor );
AddColoredText( x, y - 1, width, text, borderColor );
AddColoredText( x, y + 1, width, text, borderColor );*/
/*AddColoredText( x - 1, y - 1, width, text, borderColor );
AddColoredText( x + 1, y + 1, width, text, borderColor );*/
AddColoredText(x, y, width, text, color);
}
private void AddColoredText(int x, int y, int width, string text, int color)
{
if (color == 0)
AddHtml(x, y, width, 20, text);
else
AddHtml(x, y, width, 20, Color(text, color));
}
private void AddColumnHeader(int width, string name)
{
AddBackground(m_ColumnX, 12, width, 20, 0x242C);
AddImageTiled(m_ColumnX + 2, 14, width - 4, 16, 0x2430);
AddBorderedText(m_ColumnX, 13, width, Center(name), 0xFFFFFF, 0);
m_ColumnX += width;
}
}
}

View file

@ -0,0 +1,253 @@
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
using Server.Targeting;
namespace Server.Engines.ConPVP
{
public class ParticipantGump : Gump
{
public ParticipantGump(Mobile from, DuelContext context, Participant p) : base(50, 50)
{
From = from;
Context = context;
Participant = p;
from.CloseGump<RulesetGump>();
from.CloseGump<DuelContextGump>();
from.CloseGump<ParticipantGump>();
int count = p.Players.Length;
if (count < 4)
count = 4;
AddPage(0);
int height = 35 + 10 + 22 + 22 + 30 + 22 + 2 + count * 22 + 2 + 30;
AddBackground(0, 0, 300, height, 9250);
AddBackground(10, 10, 280, height - 20, 0xDAC);
AddButton(240, 25, 0xFB1, 0xFB3, 3);
//AddButton( 223, 54, 0x265A, 0x265A, 4, );
AddHtml(35, 25, 230, 20, Center("Participant Setup"));
int x = 35;
int y = 47;
AddHtml(x, y, 200, 20, $"Team Size: {p.Players.Length}");
y += 22;
AddGoldenButtonLabeled(x + 20, y, 1, "Increase");
y += 22;
AddGoldenButtonLabeled(x + 20, y, 2, "Decrease");
y += 30;
AddHtml(35, y, 230, 20, Center("Players"));
y += 22;
for (int i = 0; i < p.Players.Length; ++i)
{
DuelPlayer pl = p.Players[i];
AddGoldenButtonLabeled(x, y, 5 + i, $"{1 + i}: {(pl == null ? "Empty" : pl.Mobile.Name)}");
y += 22;
}
}
public Mobile From{ get; }
public DuelContext Context{ get; }
public Participant Participant{ get; }
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public void AddGoldenButton(int x, int y, int bid)
{
AddButton(x, y, 0xD2, 0xD2, bid);
AddButton(x + 3, y + 3, 0xD8, 0xD8, bid);
}
public void AddGoldenButtonLabeled(int x, int y, int bid, string text)
{
AddGoldenButton(x, y, bid);
AddHtml(x + 25, y, 200, 20, text);
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (!Context.Registered)
return;
int bid = info.ButtonID;
if (bid == 0)
{
From.SendGump(new DuelContextGump(From, Context));
}
else if (bid == 1)
{
if (Participant.Count < 8)
Participant.Resize(Participant.Count + 1);
else
From.SendMessage("You may not raise the team size any further.");
From.SendGump(new ParticipantGump(From, Context, Participant));
}
else if (bid == 2)
{
if (Participant.Count > 1 && Participant.Count > Participant.FilledSlots)
Participant.Resize(Participant.Count - 1);
else
From.SendMessage("You may not lower the team size any further.");
From.SendGump(new ParticipantGump(From, Context, Participant));
}
else if (bid == 3)
{
if (Participant.FilledSlots > 0)
{
From.SendMessage("There is at least one currently active player. You must remove them first.");
From.SendGump(new ParticipantGump(From, Context, Participant));
}
else if (Context.Participants.Count > 2)
{
/*Container cont = m_Participant.Stakes;
if ( cont != null )
cont.Delete();*/
Context.Participants.Remove(Participant);
From.SendGump(new DuelContextGump(From, Context));
}
else
{
From.SendMessage("Duels must have at least two participating parties.");
From.SendGump(new ParticipantGump(From, Context, Participant));
}
}
/*else if ( bid == 4 )
{
m_From.SendGump( new ParticipantGump( m_From, m_Context, m_Participant ) );
Container cont = m_Participant.Stakes;
if ( cont != null && !cont.Deleted )
{
cont.DisplayTo( m_From );
Item[] checks = cont.FindItemsByType( typeof( BankCheck ) );
int gold = cont.TotalGold;
for ( int i = 0; i < checks.Length; ++i )
gold += ((BankCheck)checks[i]).Worth;
m_From.SendMessage( "This container has {0} item{1} and {2} stone{3}. In gold or check form there is a total of {4:D}gp.", cont.TotalItems, cont.TotalItems==1?"":"s", cont.TotalWeight, cont.TotalWeight==1?"":"s", gold );
}
}*/
else
{
bid -= 5;
if (bid >= 0 && bid < Participant.Players.Length)
{
if (Participant.Players[bid] == null)
{
From.Target = new ParticipantTarget(Context, Participant, bid);
From.SendMessage("Target a player.");
}
else
{
Participant.Players[bid].Mobile.SendMessage("You have been removed from the duel.");
if (Participant.Players[bid].Mobile is PlayerMobile)
((PlayerMobile)Participant.Players[bid].Mobile).DuelPlayer = null;
Participant.Players[bid] = null;
From.SendMessage("They have been removed from the duel.");
From.SendGump(new ParticipantGump(From, Context, Participant));
}
}
}
}
private class ParticipantTarget : Target
{
private DuelContext m_Context;
private int m_Index;
private Participant m_Participant;
public ParticipantTarget(DuelContext context, Participant p, int index) : base(12, false, TargetFlags.None)
{
m_Context = context;
m_Participant = p;
m_Index = index;
}
protected override void OnTarget(Mobile from, object targeted)
{
if (!m_Context.Registered)
return;
int index = m_Index;
if (index < 0 || index >= m_Participant.Players.Length)
return;
if (!(targeted is Mobile mob))
{
from.SendMessage("That is not a player.");
}
else if (!mob.Player)
{
if (mob.Body.IsHuman)
mob.SayTo(from, 1005443); // Nay, I would rather stay here and watch a nail rust.
else
mob.SayTo(from, 1005444); // The creature ignores your offer.
}
else if (AcceptDuelGump.IsIgnored(mob, from) || mob.Blessed)
{
from.SendMessage("They ignore your offer.");
}
else
{
if (!(mob is PlayerMobile pm))
return;
if (pm.DuelContext != null)
{
from.SendMessage("{0} cannot fight because they are already assigned to another duel.", pm.Name);
}
else if (DuelContext.CheckCombat(pm))
{
from.SendMessage("{0} cannot fight because they have recently been in combat with another player.",
pm.Name);
}
else if (mob.HasGump<AcceptDuelGump>())
{
from.SendMessage("{0} has already been offered a duel.");
}
else
{
from.SendMessage("You send {0} to {1}.",
m_Participant.Find(from) == null ? "a challenge" : "an invitation", mob.Name);
mob.SendGump(new AcceptDuelGump(from, mob, m_Context, m_Participant, m_Index));
}
}
}
protected override void OnTargetFinish(Mobile from)
{
from.SendGump(new ParticipantGump(from, m_Context, m_Participant));
}
}
}
}

View file

@ -0,0 +1,126 @@
using Server.Gumps;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class PickRulesetGump : Gump
{
private DuelContext m_Context;
private Ruleset[] m_Defaults;
private Ruleset[] m_Flavors;
private Mobile m_From;
private Ruleset m_Ruleset;
public PickRulesetGump(Mobile from, DuelContext context, Ruleset ruleset) : base(50, 50)
{
m_From = from;
m_Context = context;
m_Ruleset = ruleset;
m_Defaults = ruleset.Layout.Defaults;
m_Flavors = ruleset.Layout.Flavors;
int height = 25 + 20 + (m_Defaults.Length + 1) * 22 + 6 + 20 + m_Flavors.Length * 22 + 25;
AddPage(0);
AddBackground(0, 0, 260, height, 9250);
AddBackground(10, 10, 240, height - 20, 0xDAC);
AddHtml(35, 25, 190, 20, Center("Rules"));
int y = 25 + 20;
for (int i = 0; i < m_Defaults.Length; ++i)
{
Ruleset cur = m_Defaults[i];
AddHtml(35 + 14, y, 176, 20, cur.Title);
if (ruleset.Base == cur && !ruleset.Changed)
AddImage(35, y + 4, 0x939);
else if (ruleset.Base == cur)
AddButton(35, y + 4, 0x93A, 0x939, 2 + i);
else
AddButton(35, y + 4, 0x938, 0x939, 2 + i);
y += 22;
}
AddHtml(35 + 14, y, 176, 20, "Custom");
AddButton(35, y + 4, ruleset.Changed ? 0x939 : 0x938, 0x939, 1);
y += 22;
y += 6;
AddHtml(35, y, 190, 20, Center("Flavors"));
y += 20;
for (int i = 0; i < m_Flavors.Length; ++i)
{
Ruleset cur = m_Flavors[i];
AddHtml(35 + 14, y, 176, 20, cur.Title);
if (ruleset.Flavors.Contains(cur))
AddButton(35, y + 4, 0x939, 0x938, 2 + m_Defaults.Length + i);
else
AddButton(35, y + 4, 0x938, 0x939, 2 + m_Defaults.Length + i);
y += 22;
}
}
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (m_Context?.Registered == false)
return;
switch (info.ButtonID)
{
case 0: // closed
{
if (m_Context != null)
m_From.SendGump(new DuelContextGump(m_From, m_Context));
break;
}
case 1: // customize
{
m_From.SendGump(new RulesetGump(m_From, m_Ruleset, m_Ruleset.Layout, m_Context));
break;
}
default:
{
int idx = info.ButtonID - 2;
if (idx >= 0 && idx < m_Defaults.Length)
{
m_Ruleset.ApplyDefault(m_Defaults[idx]);
m_From.SendGump(new PickRulesetGump(m_From, m_Context, m_Ruleset));
}
else
{
idx -= m_Defaults.Length;
if (idx >= 0 && idx < m_Flavors.Length)
{
if (m_Ruleset.Flavors.Contains(m_Flavors[idx]))
m_Ruleset.RemoveFlavor(m_Flavors[idx]);
else
m_Ruleset.AddFlavor(m_Flavors[idx]);
m_From.SendGump(new PickRulesetGump(m_From, m_Context, m_Ruleset));
}
}
break;
}
}
}
}
}

View file

@ -0,0 +1,109 @@
using System.Collections.Generic;
using Server.Gumps;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class ReadyGump : Gump
{
private DuelContext m_Context;
private int m_Count;
private Mobile m_From;
public ReadyGump(Mobile from, DuelContext context, int count) : base(50, 50)
{
m_From = from;
m_Context = context;
m_Count = count;
List<Participant> parts = context.Participants;
int height = 25 + 20;
for (int i = 0; i < parts.Count; ++i)
{
Participant p = parts[i];
height += 4;
if (p.Players.Length > 1)
height += 22;
height += p.Players.Length * 22;
}
height += 25;
Closable = false;
Draggable = false;
AddPage(0);
AddBackground(0, 0, 260, height, 9250);
AddBackground(10, 10, 240, height - 20, 0xDAC);
if (count == -1)
{
AddHtml(35, 25, 190, 20, Center("Ready"));
}
else
{
AddHtml(35, 25, 190, 20, Center("Starting"));
AddHtml(35, 25, 190, 20, "<DIV ALIGN=RIGHT>" + count);
}
int y = 25 + 20;
for (int i = 0; i < parts.Count; ++i)
{
Participant p = parts[i];
y += 4;
bool isAllReady = true;
int yStore = y;
int offset = 0;
if (p.Players.Length > 1)
{
AddHtml(35 + 14, y, 176, 20, $"Participant #{i + 1}");
y += 22;
offset = 10;
}
for (int j = 0; j < p.Players.Length; ++j)
{
DuelPlayer pl = p.Players[j];
if (pl?.Ready == true)
{
AddImage(35 + offset, y + 4, 0x939);
}
else
{
AddImage(35 + offset, y + 4, 0x938);
isAllReady = false;
}
string name = pl == null ? "(Empty)" : pl.Mobile.Name;
AddHtml(35 + offset + 14, y, 166, 20, name);
y += 22;
}
if (p.Players.Length > 1)
AddImage(35, yStore + 4, isAllReady ? 0x939 : 0x938);
}
}
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public override void OnResponse(NetState sender, RelayInfo info)
{
}
}
}

View file

@ -0,0 +1,233 @@
using System.Collections;
using System.Collections.Generic;
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class ReadyUpGump : Gump
{
private DuelContext m_Context;
private Mobile m_From;
public ReadyUpGump(Mobile from, DuelContext context) : base(50, 50)
{
m_From = from;
m_Context = context;
Closable = false;
AddPage(0);
if (context.Rematch)
{
int height = 25 + 20 + 10 + 22 + 25;
AddBackground(0, 0, 210, height, 9250);
AddBackground(10, 10, 190, height - 20, 0xDAC);
AddHtml(35, 25, 140, 20, Center("Rematch?"));
AddButton(35, 55, 247, 248, 1);
AddButton(115, 55, 242, 241, 2);
}
else
{
#region Participants
AddPage(1);
List<Participant> parts = context.Participants;
int height = 25 + 20;
for (int i = 0; i < parts.Count; ++i)
{
Participant p = parts[i];
height += 4;
if (p.Players.Length > 1)
height += 22;
height += p.Players.Length * 22;
}
height += 10 + 22 + 25;
AddBackground(0, 0, 260, height, 9250);
AddBackground(10, 10, 240, height - 20, 0xDAC);
AddHtml(35, 25, 190, 20, Center("Participants"));
int y = 20 + 25;
for (int i = 0; i < parts.Count; ++i)
{
Participant p = parts[i];
y += 4;
int offset = 0;
if (p.Players.Length > 1)
{
AddHtml(35, y, 176, 20, $"Team #{i + 1}");
y += 22;
offset = 10;
}
for (int j = 0; j < p.Players.Length; ++j)
{
DuelPlayer pl = p.Players[j];
string name = pl == null ? "(Empty)" : pl.Mobile.Name;
AddHtml(35 + offset, y, 166, 20, name);
y += 22;
}
}
y += 8;
AddHtml(35, y, 176, 20, "Continue?");
y -= 2;
AddButton(102, y, 247, 248, 0, GumpButtonType.Page, 2);
AddButton(169, y, 242, 241, 2);
#endregion
#region Rules
AddPage(2);
Ruleset ruleset = context.Ruleset;
Ruleset basedef = ruleset.Base;
height = 25 + 20 + 5 + 20 + 20 + 4;
int changes = 0;
BitArray defs;
if (ruleset.Flavors.Count > 0)
{
defs = new BitArray(basedef.Options);
for (int i = 0; i < ruleset.Flavors.Count; ++i)
defs.Or(ruleset.Flavors[i].Options);
height += ruleset.Flavors.Count * 18;
}
else
{
defs = basedef.Options;
}
BitArray opts = ruleset.Options;
for (int i = 0; i < opts.Length; ++i)
if (defs[i] != opts[i])
++changes;
height += changes * 22;
height += 10 + 22 + 25;
AddBackground(0, 0, 260, height, 9250);
AddBackground(10, 10, 240, height - 20, 0xDAC);
AddHtml(35, 25, 190, 20, Center("Rules"));
AddHtml(35, 50, 190, 20, $"Set: {basedef.Title}");
y = 70;
for (int i = 0; i < ruleset.Flavors.Count; ++i, y += 18)
AddHtml(35, y, 190, 20, $" + {ruleset.Flavors[i].Title}");
y += 4;
if (changes > 0)
{
AddHtml(35, y, 190, 20, "Modifications:");
y += 20;
for (int i = 0; i < opts.Length; ++i)
if (defs[i] != opts[i])
{
string name = ruleset.Layout.FindByIndex(i);
if (name != null) // sanity
{
AddImage(35, y, opts[i] ? 0xD3 : 0xD2);
AddHtml(60, y, 165, 22, name);
}
y += 22;
}
}
else
{
AddHtml(35, y, 190, 20, "Modifications: None");
y += 20;
}
y += 8;
AddHtml(35, y, 176, 20, "Continue?");
y -= 2;
AddButton(102, y, 247, 248, 1);
AddButton(169, y, 242, 241, 3);
#endregion
}
}
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public void AddGoldenButton(int x, int y, int bid)
{
AddButton(x, y, 0xD2, 0xD2, bid);
AddButton(x + 3, y + 3, 0xD8, 0xD8, bid);
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (!m_Context.Registered || !m_Context.ReadyWait)
return;
switch (info.ButtonID)
{
case 1: // okay
{
if (!(m_From is PlayerMobile pm))
break;
pm.DuelPlayer.Ready = true;
m_Context.SendReadyGump();
break;
}
case 2: // reject participants
{
m_Context.RejectReady(m_From, "participants");
break;
}
case 3: // reject rules
{
m_Context.RejectReady(m_From, "rules");
break;
}
}
}
}
}

View file

@ -0,0 +1,130 @@
using System.Collections;
using Server.Gumps;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class RulesetGump : Gump
{
private DuelContext m_DuelContext;
private Mobile m_From;
private RulesetLayout m_Page;
private bool m_ReadOnly;
private Ruleset m_Ruleset;
public RulesetGump(Mobile from, Ruleset ruleset, RulesetLayout page, DuelContext duelContext, bool readOnly = false)
: base(readOnly ? 310 : 50, 50)
{
m_From = from;
m_Ruleset = ruleset;
m_Page = page;
m_DuelContext = duelContext;
m_ReadOnly = readOnly;
Draggable = !readOnly;
from.CloseGump<RulesetGump>();
from.CloseGump<DuelContextGump>();
from.CloseGump<ParticipantGump>();
RulesetLayout depthCounter = page;
int depth = 0;
while (depthCounter != null)
{
++depth;
depthCounter = depthCounter.Parent;
}
int count = page.Children.Length + page.Options.Length;
AddPage(0);
int height = 35 + 10 + 2 + count * 22 + 2 + 30;
AddBackground(0, 0, 260, height, 9250);
AddBackground(10, 10, 240, height - 20, 0xDAC);
AddHtml(35, 25, 190, 20, Center(page.Title));
int x = 35;
int y = 47;
for (int i = 0; i < page.Children.Length; ++i)
{
AddGoldenButton(x, y, 1 + i);
AddHtml(x + 25, y, 250, 22, page.Children[i].Title);
y += 22;
}
for (int i = 0; i < page.Options.Length; ++i)
{
bool enabled = ruleset.Options[page.Offset + i];
if (readOnly)
AddImage(x, y, enabled ? 0xD3 : 0xD2);
else
AddCheck(x, y, 0xD2, 0xD3, enabled, i);
AddHtml(x + 25, y, 250, 22, page.Options[i]);
y += 22;
}
}
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public void AddGoldenButton(int x, int y, int bid)
{
AddButton(x, y, 0xD2, 0xD2, bid);
AddButton(x + 3, y + 3, 0xD8, 0xD8, bid);
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (m_DuelContext?.Registered == false)
return;
if (!m_ReadOnly)
{
BitArray opts = new BitArray(m_Page.Options.Length);
for (int i = 0; i < info.Switches.Length; ++i)
{
int sid = info.Switches[i];
if (sid >= 0 && sid < m_Page.Options.Length)
opts[sid] = true;
}
for (int i = 0; i < opts.Length; ++i)
if (m_Ruleset.Options[m_Page.Offset + i] != opts[i])
{
m_Ruleset.Options[m_Page.Offset + i] = opts[i];
m_Ruleset.Changed = true;
}
}
int bid = info.ButtonID;
if (bid == 0)
{
if (m_Page.Parent != null)
m_From.SendGump(new RulesetGump(m_From, m_Ruleset, m_Page.Parent, m_DuelContext, m_ReadOnly));
else if (!m_ReadOnly)
m_From.SendGump(new PickRulesetGump(m_From, m_DuelContext, m_Ruleset));
}
else
{
bid -= 1;
if (bid >= 0 && bid < m_Page.Children.Length)
m_From.SendGump(new RulesetGump(m_From, m_Ruleset, m_Page.Children[bid], m_DuelContext, m_ReadOnly));
}
}
}
}

View file

@ -0,0 +1,856 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.ConPVP
{
public enum TourneyBracketGumpType
{
Index,
Rules_Info,
Participant_List,
Participant_Info,
Round_List,
Round_Info,
Match_Info,
Player_Info
}
public class TournamentBracketGump : Gump
{
private const int BlackColor32 = 0x000008;
private const int LabelColor32 = 0xFFFFFF;
private Mobile m_From;
private List<object> m_List;
private object m_Object;
private int m_Page;
private int m_PerPage;
private Tournament m_Tournament;
private TourneyBracketGumpType m_Type;
public TournamentBracketGump(Mobile from, Tournament tourney, TourneyBracketGumpType type,
List<object> list = null, int page = 0, object obj = null) : base(50, 50)
{
m_From = from;
m_Tournament = tourney;
m_Type = type;
m_List = list;
m_Page = page;
m_Object = obj;
m_PerPage = 12;
switch (type)
{
case TourneyBracketGumpType.Index:
{
AddPage(0);
AddBackground(0, 0, 300, 300, 9380);
StringBuilder sb = new StringBuilder();
if (tourney.TourneyType == TourneyType.FreeForAll)
{
sb.Append("FFA");
}
else if (tourney.TourneyType == TourneyType.RandomTeam)
{
sb.Append(tourney.ParticipantsPerMatch);
sb.Append("-Team");
}
else if (tourney.TourneyType == TourneyType.RedVsBlue)
{
sb.Append("Red v Blue");
}
else if (tourney.TourneyType == TourneyType.Faction)
{
sb.Append(tourney.ParticipantsPerMatch);
sb.Append("-Team Faction");
}
else
{
for (int i = 0; i < tourney.ParticipantsPerMatch; ++i)
{
if (sb.Length > 0)
sb.Append('v');
sb.Append(tourney.PlayersPerParticipant);
}
}
if (tourney.EventController != null)
sb.Append(' ').Append(tourney.EventController.Title);
sb.Append(" Tournament Bracket");
AddHtml(25, 35, 250, 20, Center(sb.ToString()));
AddRightArrow(25, 53, ToButtonID(0, 4), "Rules");
AddRightArrow(25, 71, ToButtonID(0, 1), "Participants");
if (m_Tournament.Stage == TournamentStage.Signup)
{
TimeSpan until = m_Tournament.SignupStart + m_Tournament.SignupPeriod - DateTime.UtcNow;
string text;
int secs = (int)until.TotalSeconds;
if (secs > 0)
{
int mins = secs / 60;
secs %= 60;
if (mins > 0 && secs > 0)
text =
$"The tournament will begin in {mins} minute{(mins == 1 ? "" : "s")} and {secs} second{(secs == 1 ? "" : "s")}.";
else if (mins > 0)
text = $"The tournament will begin in {mins} minute{(mins == 1 ? "" : "s")}.";
else if (secs > 0)
text = $"The tournament will begin in {secs} second{(secs == 1 ? "" : "s")}.";
else
text = "The tournament will begin shortly.";
}
else
{
text = "The tournament will begin shortly.";
}
AddHtml(25, 92, 250, 40, text);
}
else
{
AddRightArrow(25, 89, ToButtonID(0, 2), "Rounds");
}
break;
}
case TourneyBracketGumpType.Rules_Info:
{
Ruleset ruleset = tourney.Ruleset;
Ruleset basedef = ruleset.Base;
BitArray defs;
if (ruleset.Flavors.Count > 0)
{
defs = new BitArray(basedef.Options);
for (int i = 0; i < ruleset.Flavors.Count; ++i)
defs.Or(ruleset.Flavors[i].Options);
}
else
{
defs = basedef.Options;
}
int changes = 0;
BitArray opts = ruleset.Options;
for (int i = 0; i < opts.Length; ++i)
if (defs[i] != opts[i])
++changes;
AddPage(0);
AddBackground(0, 0, 300,
60 + 18 + 20 + 20 + 20 + 8 + 20 + ruleset.Flavors.Count * 18 + 4 + 20 + changes * 22 + 6, 9380);
AddLeftArrow(25, 11, ToButtonID(0, 0));
AddHtml(25, 35, 250, 20, Center("Rules"));
int y = 53;
string groupText = null;
switch (tourney.GroupType)
{
case GroupingType.HighVsLow:
groupText = "High vs Low";
break;
case GroupingType.Nearest:
groupText = "Closest opponent";
break;
case GroupingType.Random:
groupText = "Random";
break;
}
AddHtml(35, y, 190, 20, $"Grouping: {groupText}");
y += 20;
string tieText = null;
switch (tourney.TieType)
{
case TieType.Random:
tieText = "Random";
break;
case TieType.Highest:
tieText = "Highest advances";
break;
case TieType.Lowest:
tieText = "Lowest advances";
break;
case TieType.FullAdvancement:
tieText = tourney.ParticipantsPerMatch == 2 ? "Both advance" : "Everyone advances";
break;
case TieType.FullElimination:
tieText = tourney.ParticipantsPerMatch == 2 ? "Both eliminated" : "Everyone eliminated";
break;
}
AddHtml(35, y, 190, 20, $"Tiebreaker: {tieText}");
y += 20;
string sdText = "Off";
if (tourney.SuddenDeath > TimeSpan.Zero)
{
sdText = $"{(int)tourney.SuddenDeath.TotalMinutes}:{tourney.SuddenDeath.Seconds:D2}";
if (tourney.SuddenDeathRounds > 0)
sdText = $"{sdText} (first {tourney.SuddenDeathRounds} rounds)";
else
sdText = $"{sdText} (all rounds)";
}
AddHtml(35, y, 240, 20, $"Sudden Death: {sdText}");
y += 20;
y += 8;
AddHtml(35, y, 190, 20, $"Ruleset: {basedef.Title}");
y += 20;
for (int i = 0; i < ruleset.Flavors.Count; ++i, y += 18)
AddHtml(35, y, 190, 20, $" + {ruleset.Flavors[i].Title}");
y += 4;
if (changes > 0)
{
AddHtml(35, y, 190, 20, "Modifications:");
y += 20;
for (int i = 0; i < opts.Length; ++i)
if (defs[i] != opts[i])
{
string name = ruleset.Layout.FindByIndex(i);
if (name != null) // sanity
{
AddImage(35, y, opts[i] ? 0xD3 : 0xD2);
AddHtml(60, y, 165, 22, name);
}
y += 22;
}
}
else
{
AddHtml(35, y, 190, 20, "Modifications: None");
}
break;
}
case TourneyBracketGumpType.Participant_List:
{
AddPage(0);
AddBackground(0, 0, 300, 300, 9380);
List<TourneyParticipant> pList = m_List != null
? Utility.CastListCovariant<object, TourneyParticipant>(m_List)
: new List<TourneyParticipant>(tourney.Participants);
AddLeftArrow(25, 11, ToButtonID(0, 0));
AddHtml(25, 35, 250, 20, Center($"{pList.Count} Participant{(pList.Count == 1 ? "" : "s")}"));
StartPage(out int index, out int count, out int y, 12);
for (int i = 0; i < count; ++i, y += 18)
{
TourneyParticipant part = pList[index + i];
string name = part.NameList;
if (m_Tournament.TourneyType != TourneyType.Standard && part.Players.Count == 1)
if (part.Players[0] is PlayerMobile pm && pm.DuelPlayer != null)
name = Color(name, pm.DuelPlayer.Eliminated ? 0x6633333 : 0x336666);
AddRightArrow(25, y, ToButtonID(2, index + i), name);
}
break;
}
case TourneyBracketGumpType.Participant_Info:
{
if (!(obj is TourneyParticipant part))
break;
AddPage(0);
AddBackground(0, 0, 300, 60 + 18 + 20 + part.Players.Count * 18 + 20 + 20 + 160, 9380);
AddLeftArrow(25, 11, ToButtonID(0, 1));
AddHtml(25, 35, 250, 20, Center("Participants"));
int y = 53;
AddHtml(25, y, 200, 20, part.Players.Count == 1 ? "Players" : "Team");
y += 20;
for (int i = 0; i < part.Players.Count; ++i)
{
Mobile mob = part.Players[i];
string name = mob.Name;
if (m_Tournament.TourneyType != TourneyType.Standard)
if (mob is PlayerMobile pm && pm.DuelPlayer != null)
name = Color(name, pm.DuelPlayer.Eliminated ? 0x6633333 : 0x336666);
AddRightArrow(35, y, ToButtonID(4, i), name);
y += 18;
}
AddHtml(25, y, 200, 20,
$"Free Advances: {(part.FreeAdvances == 0 ? "None" : part.FreeAdvances.ToString())}");
y += 20;
AddHtml(25, y, 200, 20, "Log:");
y += 20;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < part.Log.Count; ++i)
{
if (sb.Length > 0)
sb.Append("<br>");
sb.Append(part.Log[i]);
}
if (sb.Length == 0)
sb.Append("Nothing logged yet.");
AddHtml(25, y, 250, 150, Color(sb.ToString(), BlackColor32), false, true);
break;
}
case TourneyBracketGumpType.Player_Info:
{
AddPage(0);
AddBackground(0, 0, 300, 300, 9380);
AddLeftArrow(25, 11, ToButtonID(0, 3));
AddHtml(25, 35, 250, 20, Center("Participants"));
if (!(obj is Mobile mob))
break;
Ladder ladder = Ladder.Instance;
LadderEntry entry = ladder?.Find(mob);
AddHtml(25, 53, 250, 20, $"Name: {mob.Name}");
AddHtml(25, 73, 250, 20,
$"Guild: {(mob.Guild == null ? "None" : mob.Guild.Name + " [" + mob.Guild.Abbreviation + "]")}");
AddHtml(25, 93, 250, 20, $"Rank: {(entry == null ? "N/A" : LadderGump.Rank(entry.Index + 1))}");
AddHtml(25, 113, 250, 20, $"Level: {(entry == null ? 0 : Ladder.GetLevel(entry.Experience))}");
AddHtml(25, 133, 250, 20, $"Wins: {entry?.Wins ?? 0:N0}");
AddHtml(25, 153, 250, 20, $"Losses: {entry?.Losses ?? 0:N0}");
break;
}
case TourneyBracketGumpType.Round_List:
{
AddPage(0);
AddBackground(0, 0, 300, 300, 9380);
AddLeftArrow(25, 11, ToButtonID(0, 0));
AddHtml(25, 35, 250, 20, Center("Rounds"));
// List<PyramidLevel> levelsList = m_List != null
// ? Utility.CastListCovariant<object, PyramidLevel>(m_List)
// : new List<PyramidLevel>(tourney.Pyramid.Levels);
StartPage(out int index, out int count, out int y, 12);
for (int i = 0; i < count; ++i, y += 18)
AddRightArrow(25, y, ToButtonID(3, index + i), "Round #" + (index + i + 1));
break;
}
case TourneyBracketGumpType.Round_Info:
{
AddPage(0);
AddBackground(0, 0, 300, 300, 9380);
AddLeftArrow(25, 11, ToButtonID(0, 2));
AddHtml(25, 35, 250, 20, Center("Rounds"));
if (!(m_Object is PyramidLevel level))
break;
List<TourneyMatch> matchesList = m_List != null
? Utility.CastListCovariant<object, TourneyMatch>(m_List)
: new List<TourneyMatch>(level.Matches);
AddRightArrow(25, 53, ToButtonID(5, 0),
$"Free Advance: {(level.FreeAdvance == null ? "None" : level.FreeAdvance.NameList)}");
AddHtml(25, 73, 200, 20, $"{matchesList.Count} Match{(matchesList.Count == 1 ? "" : "es")}");
StartPage(out int index, out int count, out int y, 10);
for (int i = 0; i < count; ++i, y += 18)
{
TourneyMatch match = matchesList[index + i];
int color = -1;
if (match.InProgress)
color = 0x336666;
else if (match.Context != null && match.Winner == null)
color = 0x666666;
StringBuilder sb = new StringBuilder();
if (m_Tournament.TourneyType == TourneyType.Standard)
for (int j = 0; j < match.Participants.Count; ++j)
{
if (sb.Length > 0)
sb.Append(" vs ");
TourneyParticipant part = match.Participants[j];
string txt = part.NameList;
if (color == -1 && match.Context != null && match.Winner == part)
txt = Color(txt, 0x336633);
else if (color == -1 && match.Context != null)
txt = Color(txt, 0x663333);
sb.Append(txt);
}
else if (m_Tournament.EventController != null || m_Tournament.TourneyType == TourneyType.RandomTeam ||
m_Tournament.TourneyType == TourneyType.RedVsBlue ||
m_Tournament.TourneyType == TourneyType.Faction)
for (int j = 0; j < match.Participants.Count; ++j)
{
if (sb.Length > 0)
sb.Append(" vs ");
TourneyParticipant part = match.Participants[j];
string txt;
if (m_Tournament.EventController != null)
{
txt = $"Team {m_Tournament.EventController.GetTeamName(j)} ({part.Players.Count})";
}
else if (m_Tournament.TourneyType == TourneyType.RandomTeam)
{
txt = $"Team {j + 1} ({part.Players.Count})";
}
else if (m_Tournament.TourneyType == TourneyType.Faction)
{
if (m_Tournament.ParticipantsPerMatch == 4)
{
string name = "(null)";
switch (j)
{
case 0:
{
name = "Minax";
break;
}
case 1:
{
name = "Council of Mages";
break;
}
case 2:
{
name = "True Britannians";
break;
}
case 3:
{
name = "Shadowlords";
break;
}
}
txt = $"{name} ({part.Players.Count})";
}
else if (m_Tournament.ParticipantsPerMatch == 2)
{
txt = $"{(j == 0 ? "Evil" : "Hero")} Team ({part.Players.Count})";
}
else
{
txt = $"Team {j + 1} ({part.Players.Count})";
}
}
else
{
txt = $"Team {(j == 0 ? "Red" : "Blue")} ({part.Players.Count})";
}
if (color == -1 && match.Context != null && match.Winner == part)
txt = Color(txt, 0x336633);
else if (color == -1 && match.Context != null)
txt = Color(txt, 0x663333);
sb.Append(txt);
}
else if (m_Tournament.TourneyType == TourneyType.FreeForAll) sb.Append("Free For All");
string str = sb.ToString();
if (color >= 0)
str = Color(str, color);
AddRightArrow(25, y, ToButtonID(5, index + i + 1), str);
}
break;
}
case TourneyBracketGumpType.Match_Info:
{
if (!(obj is TourneyMatch match))
break;
int ct = m_Tournament.TourneyType == TourneyType.FreeForAll ? 2 : match.Participants.Count;
AddPage(0);
AddBackground(0, 0, 300, 60 + 18 + 20 + 20 + 20 + ct * 18 + 6, 9380);
AddLeftArrow(25, 11, ToButtonID(0, 5));
AddHtml(25, 35, 250, 20, Center("Rounds"));
AddHtml(25, 53, 250, 20, $"Winner: {(match.Winner == null ? "N/A" : match.Winner.NameList)}");
AddHtml(25, 73, 250, 20,
$"State: {(match.InProgress ? "In progress" : match.Context != null ? "Complete" : "Waiting")}");
AddHtml(25, 93, 250, 20, "Participants:");
if (m_Tournament.TourneyType == TourneyType.Standard)
for (int i = 0; i < match.Participants.Count; ++i)
{
TourneyParticipant part = match.Participants[i];
AddRightArrow(25, 113 + i * 18, ToButtonID(6, i), part.NameList);
}
else if (m_Tournament.EventController != null || m_Tournament.TourneyType == TourneyType.RandomTeam ||
m_Tournament.TourneyType == TourneyType.RedVsBlue ||
m_Tournament.TourneyType == TourneyType.Faction)
for (int i = 0; i < match.Participants.Count; ++i)
{
TourneyParticipant part = match.Participants[i];
if (m_Tournament.EventController != null)
{
AddRightArrow(25, 113 + i * 18, ToButtonID(6, i),
$"Team {m_Tournament.EventController.GetTeamName(i)} ({part.Players.Count})");
}
else if (m_Tournament.TourneyType == TourneyType.RandomTeam)
{
AddRightArrow(25, 113 + i * 18, ToButtonID(6, i),
$"Team {i + 1} ({part.Players.Count})");
}
else if (m_Tournament.TourneyType == TourneyType.Faction)
{
if (m_Tournament.ParticipantsPerMatch == 4)
{
string name = "(null)";
switch (i)
{
case 0:
{
name = "Minax";
break;
}
case 1:
{
name = "Council of Mages";
break;
}
case 2:
{
name = "True Britannians";
break;
}
case 3:
{
name = "Shadowlords";
break;
}
}
AddRightArrow(25, 113 + i * 18, ToButtonID(6, i),
$"{name} ({part.Players.Count})");
}
else if (m_Tournament.ParticipantsPerMatch == 2)
{
AddRightArrow(25, 113 + i * 18, ToButtonID(6, i),
$"{(i == 0 ? "Evil" : "Hero")} Team ({part.Players.Count})");
}
else
{
AddRightArrow(25, 113 + i * 18, ToButtonID(6, i),
$"Team {i + 1} ({part.Players.Count})");
}
}
else
{
AddRightArrow(25, 113 + i * 18, ToButtonID(6, i),
$"Team {(i == 0 ? "Red" : "Blue")} ({part.Players.Count})");
}
}
else if (m_Tournament.TourneyType == TourneyType.FreeForAll)
AddHtml(25, 113, 250, 20, "Free For All");
break;
}
}
}
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public string Color(string text, int color)
{
return $"<BASEFONT COLOR=#{color:X6}>{text}</BASEFONT>";
}
private void AddBorderedText(int x, int y, int width, int height, string text, int color, int borderColor)
{
AddColoredText(x - 1, y - 1, width, height, text, borderColor);
AddColoredText(x - 1, y + 1, width, height, text, borderColor);
AddColoredText(x + 1, y - 1, width, height, text, borderColor);
AddColoredText(x + 1, y + 1, width, height, text, borderColor);
AddColoredText(x, y, width, height, text, color);
}
private void AddColoredText(int x, int y, int width, int height, string text, int color)
{
if (color == 0)
AddHtml(x, y, width, height, text);
else
AddHtml(x, y, width, height, Color(text, color));
}
public void AddRightArrow(int x, int y, int bid, string text)
{
AddButton(x, y, 0x15E1, 0x15E5, bid);
if (text != null)
AddHtml(x + 20, y - 1, 230, 20, text);
}
public void AddRightArrow(int x, int y, int bid)
{
AddRightArrow(x, y, bid, null);
}
public void AddLeftArrow(int x, int y, int bid, string text)
{
AddButton(x, y, 0x15E3, 0x15E7, bid);
if (text != null)
AddHtml(x + 20, y - 1, 230, 20, text);
}
public void AddLeftArrow(int x, int y, int bid)
{
AddLeftArrow(x, y, bid, null);
}
public int ToButtonID(int type, int index)
{
return 1 + index * 7 + type;
}
public bool FromButtonID(int bid, out int type, out int index)
{
type = (bid - 1) % 7;
index = (bid - 1) / 7;
return bid >= 1;
}
public void StartPage(out int index, out int count, out int y, int perPage)
{
m_PerPage = perPage;
index = Math.Max(m_Page * perPage, 0);
count = Math.Max(Math.Min(m_List.Count - index, perPage), 0);
y = 53 + (12 - perPage) * 18;
if (m_Page > 0)
AddLeftArrow(242, 35, ToButtonID(1, 0));
if ((m_Page + 1) * perPage < m_List.Count)
AddRightArrow(260, 35, ToButtonID(1, 1));
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (!FromButtonID(info.ButtonID, out int type, out int index))
return;
switch (type)
{
case 0:
{
switch (index)
{
case 0:
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament, TourneyBracketGumpType.Index));
break;
case 1:
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament,
TourneyBracketGumpType.Participant_List));
break;
case 2:
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament, TourneyBracketGumpType.Round_List));
break;
case 4:
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament, TourneyBracketGumpType.Rules_Info));
break;
case 3:
{
Mobile mob = m_Object as Mobile;
for (int i = 0; i < m_Tournament.Participants.Count; ++i)
{
TourneyParticipant part = m_Tournament.Participants[i];
if (part.Players.Contains(mob))
{
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament,
TourneyBracketGumpType.Participant_Info, null, 0, part));
break;
}
}
break;
}
case 5:
{
if (!(m_Object is TourneyMatch match))
break;
for (int i = 0; i < m_Tournament.Pyramid.Levels.Count; ++i)
{
PyramidLevel level = m_Tournament.Pyramid.Levels[i];
if (level.Matches.Contains(match))
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament,
TourneyBracketGumpType.Round_Info, null, 0, level));
}
break;
}
}
break;
}
case 1:
{
switch (index)
{
case 0:
{
if (m_List != null && m_Page > 0)
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament, m_Type, m_List, m_Page - 1,
m_Object));
break;
}
case 1:
{
if (m_List != null && (m_Page + 1) * m_PerPage < m_List.Count)
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament, m_Type, m_List, m_Page + 1,
m_Object));
break;
}
}
break;
}
case 2:
{
if (m_Type != TourneyBracketGumpType.Participant_List)
break;
if (index >= 0 && index < m_List.Count)
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament,
TourneyBracketGumpType.Participant_Info, null, 0, m_List[index]));
break;
}
case 3:
{
if (m_Type != TourneyBracketGumpType.Round_List)
break;
if (index >= 0 && index < m_List.Count)
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament, TourneyBracketGumpType.Round_Info,
null, 0, m_List[index]));
break;
}
case 4:
{
if (m_Type != TourneyBracketGumpType.Participant_Info)
break;
if (m_Object is TourneyParticipant part && index >= 0 && index < part.Players.Count)
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament, TourneyBracketGumpType.Player_Info,
null, 0, part.Players[index]));
break;
}
case 5:
{
if (m_Type != TourneyBracketGumpType.Round_Info)
break;
if (!(m_Object is PyramidLevel level))
break;
if (index == 0)
{
if (level.FreeAdvance != null)
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament,
TourneyBracketGumpType.Participant_Info, null, 0, level.FreeAdvance));
else
m_From.SendGump(
new TournamentBracketGump(m_From, m_Tournament, m_Type, m_List, m_Page, m_Object));
}
else if (index >= 1 && index <= level.Matches.Count)
{
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament, TourneyBracketGumpType.Match_Info,
null, 0, level.Matches[index - 1]));
}
break;
}
case 6:
{
if (m_Type != TourneyBracketGumpType.Match_Info)
break;
if (m_Object is TourneyMatch match && index >= 0 && index < match.Participants.Count)
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament,
TourneyBracketGumpType.Participant_Info, null, 0, match.Participants[index]));
break;
}
}
}
}
}

View file

@ -0,0 +1,366 @@
using System;
using System.Collections.Generic;
namespace Server.Engines.ConPVP
{
public class LadderController : Item
{
[Constructible]
public LadderController() : base(0x1B7A)
{
Visible = false;
Movable = false;
Ladder = new Ladder();
if (Ladder.Instance == null)
Ladder.Instance = Ladder;
}
public LadderController(Serial serial) : base(serial)
{
}
[CommandProperty( AccessLevel.Administrator )]
public Ladder Ladder{ get; private set; }
public override string DefaultName => "ladder controller";
public override void Delete()
{
if (Ladder.Instance == Ladder)
Ladder.Instance = null;
base.Delete();
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(1);
Ladder.Serialize(writer);
writer.Write(Ladder.Instance == Ladder);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 1:
case 0:
{
Ladder = new Ladder(reader);
if (version < 1 || reader.ReadBool())
Ladder.Instance = Ladder;
break;
}
}
}
}
public class Ladder
{
private static int[] m_ShortLevels =
{
1,
2,
3, 3,
4, 4,
5, 5, 5,
6, 6, 6,
7, 7, 7, 7,
8, 8, 8, 8,
9, 9, 9, 9, 9
};
private static int[] m_BaseXP =
{
0, 100, 200, 400, 600, 900, 1200, 1600, 2000, 2500
};
private static int[] m_LossFactors =
{
10,
11, 11,
25, 25,
43, 43,
67, 67
};
private static int[,] m_OffsetScalar =
{
/* { win, los } */
/* -6 */ { 175, 25 },
/* -5 */ { 165, 35 },
/* -4 */ { 155, 45 },
/* -3 */ { 145, 55 },
/* -2 */ { 130, 70 },
/* -1 */ { 115, 85 },
/* 0 */ { 100, 100 },
/* +1 */ { 90, 110 },
/* +2 */ { 80, 120 },
/* +3 */ { 70, 130 },
/* +4 */ { 60, 140 },
/* +5 */ { 50, 150 },
/* +6 */ { 40, 160 }
};
public List<LadderEntry> Entries{ get; } = new List<LadderEntry>();
private Dictionary<Mobile, LadderEntry> m_Table;
public Ladder()
{
m_Table = new Dictionary<Mobile, LadderEntry>();
}
public Ladder(GenericReader reader)
{
int version = reader.ReadEncodedInt();
switch (version)
{
case 1:
case 0:
{
int count = reader.ReadEncodedInt();
m_Table = new Dictionary<Mobile, LadderEntry>(count);
Entries = new List<LadderEntry>(count);
for (int i = 0; i < count; ++i)
{
LadderEntry entry = new LadderEntry(reader, this, version);
if (entry.Mobile != null)
{
m_Table[entry.Mobile] = entry;
entry.Index = Entries.Count;
Entries.Add(entry);
}
}
if (version == 0)
{
Entries.Sort();
for (int i = 0; i < Entries.Count; ++i)
{
LadderEntry entry = Entries[i];
entry.Index = i;
}
}
break;
}
}
}
public static Ladder Instance{ get; set; }
public static int GetLevel(int xp)
{
if (xp >= 22500)
return 50;
if (xp >= 2500)
return 10 + (xp - 2500) / 500;
if (xp < 0)
xp = 0;
return m_ShortLevels[xp / 100];
}
public static void GetLevelInfo(int level, out int xpBase, out int xpAdvance)
{
if (level >= 10)
{
xpBase = 2500 + (level - 10) * 500;
xpAdvance = 500;
}
else
{
xpBase = m_BaseXP[level - 1];
xpAdvance = m_BaseXP[level] - xpBase;
}
}
public static int GetLossFactor(int level)
{
if (level >= 10)
return 100;
return m_LossFactors[level - 1];
}
public static int GetOffsetScalar(int ourLevel, int theirLevel, bool win)
{
int x = ourLevel - theirLevel;
if (x < -6 || x > +6)
return 0;
int y = win ? 0 : 1;
return m_OffsetScalar[x + 6, y];
}
public static int GetExperienceGain(LadderEntry us, LadderEntry them, bool weWon)
{
if (us == null || them == null)
return 0;
int ourLevel = GetLevel(us.Experience);
int theirLevel = GetLevel(them.Experience);
int scalar = GetOffsetScalar(ourLevel, theirLevel, weWon);
if (scalar == 0)
return 0;
int xp = 25 * scalar;
if (!weWon)
xp = xp * GetLossFactor(ourLevel) / 100;
xp /= 100;
if (xp <= 0)
xp = 1;
return xp * (weWon ? 1 : -1);
}
private int Swap(int idx, int newIdx)
{
LadderEntry hold = Entries[idx];
Entries[idx] = Entries[newIdx];
Entries[newIdx] = hold;
Entries[idx].Index = idx;
Entries[newIdx].Index = newIdx;
return newIdx;
}
public void UpdateEntry(LadderEntry entry)
{
int index = entry.Index;
if (index >= 0 && index < Entries.Count)
{
while (index - 1 >= 0 && (entry.CompareTo(Entries[index - 1])) < 0)
index = Swap(index, index - 1);
while (index + 1 < Entries.Count && (entry.CompareTo(Entries[index + 1])) > 0)
index = Swap(index, index + 1);
}
}
public LadderEntry Find(Mobile mob)
{
if (m_Table.TryGetValue(mob, out LadderEntry entry))
{
m_Table[mob] = entry = new LadderEntry(mob, this);
entry.Index = Entries.Count;
Entries.Add(entry);
}
return entry;
}
public LadderEntry FindNoCreate(Mobile mob)
{
m_Table.TryGetValue(mob, out LadderEntry entry);
return entry;
}
public void Serialize(GenericWriter writer)
{
writer.WriteEncodedInt(1); // version;
writer.WriteEncodedInt(Entries.Count);
for (int i = 0; i < Entries.Count; ++i)
Entries[i].Serialize(writer);
}
}
public class LadderEntry : IComparable<LadderEntry>
{
private int m_Experience;
private Ladder m_Ladder;
public LadderEntry(Mobile mob, Ladder ladder)
{
m_Ladder = ladder;
Mobile = mob;
}
public LadderEntry(GenericReader reader, Ladder ladder, int version)
{
m_Ladder = ladder;
switch (version)
{
case 1:
case 0:
{
Mobile = reader.ReadMobile();
m_Experience = reader.ReadEncodedInt();
Wins = reader.ReadEncodedInt();
Losses = reader.ReadEncodedInt();
break;
}
}
}
public Mobile Mobile{ get; }
[CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)]
public int Experience
{
get => m_Experience;
set
{
m_Experience = value;
m_Ladder.UpdateEntry(this);
}
}
[CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)]
public int Wins{ get; set; }
[CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)]
public int Losses{ get; set; }
public int Index{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public int Rank => Index;
public int CompareTo(LadderEntry l)
{
return (l?.m_Experience ?? 0) - m_Experience;
}
public void Serialize(GenericWriter writer)
{
writer.Write(Mobile);
writer.WriteEncodedInt(m_Experience);
writer.WriteEncodedInt(Wins);
writer.WriteEncodedInt(Losses);
}
}
}

View file

@ -0,0 +1,231 @@
using System;
using System.Text;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class Participant
{
public Participant(DuelContext context, int count)
{
Context = context;
//m_Stakes = new StakesContainer( context, this );
Resize(count);
}
public int Count => Players.Length;
public DuelPlayer[] Players{ get; private set; }
public DuelContext Context{ get; }
public TourneyParticipant TourneyPart{ get; set; }
public int FilledSlots
{
get
{
int count = 0;
for (int i = 0; i < Players.Length; ++i)
if (Players[i] != null)
++count;
return count;
}
}
public bool HasOpenSlot
{
get
{
for (int i = 0; i < Players.Length; ++i)
if (Players[i] == null)
return true;
return false;
}
}
public bool Eliminated
{
get
{
for (int i = 0; i < Players.Length; ++i)
if (Players[i]?.Eliminated == false)
return false;
return true;
}
}
public string NameList
{
get
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < Players.Length; ++i)
{
if (Players[i] == null)
continue;
Mobile mob = Players[i].Mobile;
if (sb.Length > 0)
sb.Append(", ");
sb.Append(mob.Name);
}
if (sb.Length == 0)
return "Empty";
return sb.ToString();
}
}
public DuelPlayer Find(Mobile mob)
{
if (mob is PlayerMobile pm)
{
if (pm.DuelContext == Context && pm.DuelPlayer.Participant == this)
return pm.DuelPlayer;
return null;
}
for (int i = 0; i < Players.Length; ++i)
if (Players[i]?.Mobile == mob)
return Players[i];
return null;
}
public bool Contains(Mobile mob)
{
return Find(mob) != null;
}
public void Broadcast(int hue, string message, string nonLocalOverhead, string localOverhead)
{
for (int i = 0; i < Players.Length; ++i)
if (Players[i] != null)
{
if (message != null)
Players[i].Mobile.SendMessage(hue, message);
if (nonLocalOverhead != null)
Players[i].Mobile.NonlocalOverheadMessage(MessageType.Regular, hue, false,
string.Format(nonLocalOverhead, Players[i].Mobile.Name,
Players[i].Mobile.Female ? "her" : "his"));
if (localOverhead != null)
Players[i].Mobile.LocalOverheadMessage(MessageType.Regular, hue, false, localOverhead);
}
}
public void Nullify(DuelPlayer player)
{
if (player == null)
return;
int index = Array.IndexOf(Players, player);
if (index == -1)
return;
Players[index] = null;
}
public void Remove(DuelPlayer player)
{
if (player == null)
return;
int index = Array.IndexOf(Players, player);
if (index == -1)
return;
DuelPlayer[] old = Players;
Players = new DuelPlayer[old.Length - 1];
for (int i = 0; i < index; ++i)
Players[i] = old[i];
for (int i = index + 1; i < old.Length; ++i)
Players[i - 1] = old[i];
}
public void Remove(Mobile player)
{
Remove(Find(player));
}
public void Add(Mobile player)
{
if (Contains(player))
return;
for (int i = 0; i < Players.Length; ++i)
if (Players[i] == null)
{
Players[i] = new DuelPlayer(player, this);
return;
}
Resize(Players.Length + 1);
Players[Players.Length - 1] = new DuelPlayer(player, this);
}
public void Resize(int count)
{
DuelPlayer[] old = Players;
Players = new DuelPlayer[count];
if (old != null)
{
int ct = 0;
for (int i = 0; i < old.Length; ++i)
if (old[i] != null && ct < count)
Players[ct++] = old[i];
}
}
}
public class DuelPlayer
{
private bool m_Eliminated;
public DuelPlayer(Mobile mob, Participant p)
{
Mobile = mob;
Participant = p;
if (mob is PlayerMobile mobile)
mobile.DuelPlayer = this;
}
public Mobile Mobile{ get; }
public bool Ready{ get; set; }
public bool Eliminated
{
get => m_Eliminated;
set
{
m_Eliminated = value;
if (Participant.Context.m_Tournament != null && m_Eliminated)
{
Participant.Context.m_Tournament.OnEliminated(this);
Mobile.SendEverything();
}
}
}
public Participant Participant{ get; set; }
}
}

View file

@ -0,0 +1,277 @@
using System.Collections.Generic;
using Server.Gumps;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class PreferencesController : Item
{
[Constructible]
public PreferencesController() : base(0x1B7A)
{
Visible = false;
Movable = false;
Preferences = new Preferences();
if (Preferences.Instance == null)
Preferences.Instance = Preferences;
else
Delete();
}
public PreferencesController(Serial serial) : base(serial)
{
}
[CommandProperty( AccessLevel.Administrator )]
public Preferences Preferences{ get; private set; }
public override string DefaultName => "preferences controller";
public override void Delete()
{
if (Preferences.Instance != Preferences)
base.Delete();
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
Preferences.Serialize(writer);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 0:
{
Preferences = new Preferences(reader);
Preferences.Instance = Preferences;
break;
}
}
}
}
public class Preferences
{
private Dictionary<Mobile, PreferencesEntry> m_Table;
public Preferences()
{
m_Table = new Dictionary<Mobile, PreferencesEntry>();
Entries = new List<PreferencesEntry>();
}
public Preferences(GenericReader reader)
{
int version = reader.ReadEncodedInt();
switch (version)
{
case 0:
{
int count = reader.ReadEncodedInt();
m_Table = new Dictionary<Mobile, PreferencesEntry>(count);
Entries = new List<PreferencesEntry>(count);
for (int i = 0; i < count; ++i)
{
PreferencesEntry entry = new PreferencesEntry(reader, version);
if (entry.Mobile != null)
{
m_Table[entry.Mobile] = entry;
Entries.Add(entry);
}
}
break;
}
}
}
public List<PreferencesEntry> Entries{ get; }
public static Preferences Instance{ get; set; }
public PreferencesEntry Find(Mobile mob)
{
if (m_Table.TryGetValue(mob, out PreferencesEntry entry))
{
m_Table[mob] = entry = new PreferencesEntry(mob);
Entries.Add(entry);
}
return entry;
}
public void Serialize(GenericWriter writer)
{
writer.WriteEncodedInt(0); // version;
writer.WriteEncodedInt(Entries.Count);
for (int i = 0; i < Entries.Count; ++i)
Entries[i].Serialize(writer);
}
}
public class PreferencesEntry
{
public PreferencesEntry(Mobile mob)
{
Mobile = mob;
Disliked = new List<string>();
}
public PreferencesEntry(GenericReader reader, int version)
{
switch (version)
{
case 0:
{
Mobile = reader.ReadMobile();
int count = reader.ReadEncodedInt();
Disliked = new List<string>(count);
for (int i = 0; i < count; ++i)
Disliked.Add(reader.ReadString());
break;
}
}
}
public Mobile Mobile{ get; }
public List<string> Disliked{ get; }
public void Serialize(GenericWriter writer)
{
writer.Write(Mobile);
writer.WriteEncodedInt(Disliked.Count);
for (int i = 0; i < Disliked.Count; ++i)
writer.Write(Disliked[i]);
}
}
public class PreferencesGump : Gump
{
private int m_ColumnX = 12;
private PreferencesEntry m_Entry;
public PreferencesGump(Mobile from, Preferences prefs) : base(50, 50)
{
m_Entry = prefs.Find(from);
if (m_Entry == null)
return;
List<Arena> arenas = Arena.Arenas;
AddPage(0);
int height = 12 + 20 + arenas.Count * 31 + 24 + 12;
AddBackground(0, 0, 499 + 40 - 365, height, 0x2436);
for (int i = 1; i < arenas.Count; i += 2)
AddImageTiled(12, 32 + i * 31, 475 + 40 - 365, 30, 0x2430);
AddAlphaRegion(10, 10, 479 + 40 - 365, height - 20);
AddColumnHeader(35, null);
AddColumnHeader(115, "Arena");
AddButton(499 + 40 - 365 - 12 - 63 - 4 - 63, height - 12 - 24, 247, 248, 1);
AddButton(499 + 40 - 365 - 12 - 63, height - 12 - 24, 241, 242, 2);
for (int i = 0; i < arenas.Count; ++i)
{
Arena ar = arenas[i];
string name = ar.Name ?? "(no name)";
int x = 12;
int y = 32 + i * 31;
int color = 0xCCFFCC;
AddCheck(x + 3, y + 1, 9730, 9727, m_Entry.Disliked.Contains(name), i);
x += 35;
AddBorderedText(x + 5, y + 5, 115 - 5, name, color, 0);
}
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (m_Entry == null)
return;
if (info.ButtonID != 1)
return;
m_Entry.Disliked.Clear();
List<Arena> arenas = Arena.Arenas;
for (int i = 0; i < info.Switches.Length; ++i)
{
int idx = info.Switches[i];
if (idx >= 0 && idx < arenas.Count)
m_Entry.Disliked.Add(arenas[idx].Name);
}
}
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public string Color(string text, int color)
{
return $"<BASEFONT COLOR=#{color:X6}>{text}</BASEFONT>";
}
private void AddBorderedText(int x, int y, int width, string text, int color, int borderColor)
{
AddColoredText(x, y, width, text, color);
}
private void AddColoredText(int x, int y, int width, string text, int color)
{
if (color == 0)
AddHtml(x, y, width, 20, text);
else
AddHtml(x, y, width, 20, Color(text, color));
}
private void AddColumnHeader(int width, string name)
{
AddBackground(m_ColumnX, 12, width, 20, 0x242C);
AddImageTiled(m_ColumnX + 2, 14, width - 4, 16, 0x2430);
if (name != null)
AddBorderedText(m_ColumnX, 13, width, Center(name), 0xFFFFFF, 0);
m_ColumnX += width;
}
}
}

View file

@ -0,0 +1,102 @@
using System.Collections;
using System.Collections.Generic;
namespace Server.Engines.ConPVP
{
public class Ruleset
{
public Ruleset(RulesetLayout layout)
{
Layout = layout;
Options = new BitArray(layout.TotalLength);
}
public RulesetLayout Layout{ get; }
public BitArray Options{ get; private set; }
public string Title{ get; set; }
public Ruleset Base{ get; private set; }
public List<Ruleset> Flavors{ get; } = new List<Ruleset>();
public bool Changed{ get; set; }
public void ApplyDefault(Ruleset newDefault)
{
Base = newDefault;
Changed = false;
Options = new BitArray(newDefault.Options);
ApplyFlavorsTo(this);
}
public void ApplyFlavorsTo(Ruleset ruleset)
{
for (int i = 0; i < Flavors.Count; ++i)
{
Ruleset flavor = Flavors[i];
Options.Or(flavor.Options);
}
}
public void AddFlavor(Ruleset flavor)
{
if (Flavors.Contains(flavor))
return;
Flavors.Add(flavor);
Options.Or(flavor.Options);
}
public void RemoveFlavor(Ruleset flavor)
{
if (!Flavors.Contains(flavor))
return;
Flavors.Remove(flavor);
Options.And(flavor.Options.Not());
flavor.Options.Not();
}
public void SetOptionRange(string title, bool value)
{
RulesetLayout layout = Layout.FindByTitle(title);
if (layout == null)
return;
for (int i = 0; i < layout.TotalLength; ++i)
Options[i + layout.Offset] = value;
Changed = true;
}
public bool GetOption(string title, string option)
{
int index = 0;
RulesetLayout layout = Layout.FindByOption(title, option, ref index);
if (layout == null)
return true;
return Options[layout.Offset + index];
}
public void SetOption(string title, string option, bool value)
{
int index = 0;
RulesetLayout layout = Layout.FindByOption(title, option, ref index);
if (layout == null)
return;
Options[layout.Offset + index] = value;
Changed = true;
}
}
}

View file

@ -0,0 +1,764 @@
using System;
using System.Collections.Generic;
namespace Server.Engines.ConPVP
{
public class RulesetLayout
{
private static RulesetLayout m_Root;
public RulesetLayout(string title, string[] options) : this(title, title, new RulesetLayout[0], options)
{
}
public RulesetLayout(string title, string description, string[] options) : this(title, description,
new RulesetLayout[0], options)
{
}
public RulesetLayout(string title, RulesetLayout[] children) : this(title, title, children, new string[0])
{
}
public RulesetLayout(string title, string description, RulesetLayout[] children) : this(title, description, children,
new string[0])
{
}
public RulesetLayout(string title, RulesetLayout[] children, string[] options) : this(title, title, children,
options)
{
}
public RulesetLayout(string title, string description, RulesetLayout[] children, string[] options)
{
Title = title;
Description = description;
Children = children;
Options = options;
for (int i = 0; i < children.Length; ++i)
children[i].Parent = this;
}
public static RulesetLayout Root
{
get
{
if (m_Root != null)
return m_Root;
List<RulesetLayout> entries = new List<RulesetLayout>
{
new RulesetLayout("Spells",
new[]
{
new RulesetLayout("1st Circle", "Spells",
new[]
{
"Reactive Armor", "Clumsy", "Create Food", "Feeblemind", "Heal", "Magic Arrow", "Night Sight",
"Weaken"
}),
new RulesetLayout("2nd Circle", "Spells",
new[] { "Agility", "Cunning", "Cure", "Harm", "Magic Trap", "Untrap", "Protection", "Strength" }),
new RulesetLayout("3rd Circle", "Spells",
new[]
{
"Bless", "Fireball", "Magic Lock", "Poison", "Telekinesis", "Teleport", "Unlock Spell",
"Wall of Stone"
}),
new RulesetLayout("4th Circle", "Spells",
new[]
{
"Arch Cure", "Arch Protection", "Curse", "Fire Field", "Greater Heal", "Lightning", "Mana Drain",
"Recall"
}),
new RulesetLayout("5th Circle", "Spells",
new[]
{
"Blade Spirits", "Dispel Field", "Incognito", "Magic Reflection", "Mind Blast", "Paralyze",
"Poison Field", "Summon Creature"
}),
new RulesetLayout("6th Circle", "Spells",
new[]
{
"Dispel", "Energy Bolt", "Explosion", "Invisibility", "Mark", "Mass Curse", "Paralyze Field",
"Reveal"
}),
new RulesetLayout("7th Circle", "Spells",
new[]
{
"Chain Lightning", "Energy Field", "Flame Strike", "Gate Travel", "Mana Vampire", "Mass Dispel",
"Meteor Swarm", "Polymorph"
}),
new RulesetLayout("8th Circle", "Spells",
new[]
{
"Earthquake", "Energy Vortex", "Resurrection", "Air Elemental", "Summon Daemon", "Earth Elemental",
"Fire Elemental", "Water Elemental"
})
})
};
if (Core.AOS)
{
entries.Add(new RulesetLayout("Chivalry", new[]
{
"Cleanse by Fire",
"Close Wounds",
"Consecrate Weapon",
"Dispel Evil",
"Divine Fury",
"Enemy of One",
"Holy Light",
"Noble Sacrifice",
"Remove Curse",
"Sacred Journey"
}));
entries.Add(new RulesetLayout("Necromancy", new[]
{
"Animate Dead",
"Blood Oath",
"Corpse Skin",
"Curse Weapon",
"Evil Omen",
"Horrific Beast",
"Lich Form",
"Mind Rot",
"Pain Spike",
"Poison Strike",
"Strangle",
"Summon Familiar",
"Vampiric Embrace",
"Vengeful Spirit",
"Wither",
"Wraith Form"
}));
if (Core.SE)
{
entries.Add(new RulesetLayout("Bushido", new[]
{
"Confidence",
"Counter Attack",
"Evasion",
"Honorable Execution",
"Lightning Strike",
"Momentum Strike"
}));
entries.Add(new RulesetLayout("Ninjitsu", new[]
{
"Animal Form",
"Backstab",
"Death Strike",
"Focus Attack",
"Ki Attack",
"Mirror Image",
"Shadow Jump",
"Suprise Attack"
}));
if (Core.ML)
entries.Add(new RulesetLayout("Spellweaving", new[]
{
"Arcane Circle",
"Arcane Empowerment",
"Attune Weapon",
"Dryad Allure",
"Essence of Wind",
"Ethereal Voyage",
"Gift of Life",
"Gift of Renewal",
"Immolating Weapon",
"Nature's Fury",
"Reaper Form",
"Summon Fey",
"Summon Fiend",
"Thunderstorm",
"Wildfire",
"Word of Death"
}));
}
}
if (Core.AOS)
{
if (Core.SE)
entries.Add(new RulesetLayout("Combat Abilities", new[]
{
"Stun",
"Disarm",
"Armor Ignore",
"Bleed Attack",
"Concussion Blow",
"Crushing Blow",
"Disarm",
"Dismount",
"Double Strike",
"Infectious Strike",
"Mortal Strike",
"Moving Shot",
"Paralyzing Blow",
"Shadow Strike",
"Whirlwind Attack",
"Riding Swipe",
"Frenzied Whirlwind",
"Block",
"Defense Mastery",
"Nerve Strike",
"Talon Strike",
"Feint",
"Dual Wield",
"Double Shot",
"Armor Pierce"
}));
else
entries.Add(new RulesetLayout("Combat Abilities", new[]
{
"Stun",
"Disarm",
"Armor Ignore",
"Bleed Attack",
"Concussion Blow",
"Crushing Blow",
"Disarm",
"Dismount",
"Double Strike",
"Infectious Strike",
"Mortal Strike",
"Moving Shot",
"Paralyzing Blow",
"Shadow Strike",
"Whirlwind Attack"
}));
}
else
{
entries.Add(new RulesetLayout("Combat Abilities", new[]
{
"Stun",
"Disarm",
"Concussion Blow",
"Crushing Blow",
"Paralyzing Blow"
}));
}
entries.Add(new RulesetLayout("Skills", new[]
{
"Anatomy",
"Detect Hidden",
"Evaluating Intelligence",
"Hiding",
"Poisoning",
"Snooping",
"Stealing",
"Spirit Speak",
"Stealth"
}));
if (Core.AOS)
{
entries.Add(new RulesetLayout("Weapons", new[]
{
"Magical",
"Melee",
"Ranged",
"Poisoned",
"Wrestling"
}));
entries.Add(new RulesetLayout("Armor", new[]
{
"Magical",
"Shields"
}));
}
else
{
entries.Add(new RulesetLayout("Weapons", new[]
{
"Magical",
"Melee",
"Ranged",
"Poisoned",
"Wrestling",
"Runics"
}));
entries.Add(new RulesetLayout("Armor", new[]
{
"Magical",
"Shields",
"Colored"
}));
}
if (Core.SE)
entries.Add(new RulesetLayout("Items", new[]
{
new RulesetLayout("Potions", new[]
{
"Agility",
"Cure",
"Explosion",
"Heal",
"Nightsight",
"Poison",
"Refresh",
"Strength"
})
},
new[]
{
"Bandages",
"Wands",
"Trapped Containers",
"Bolas",
"Mounts",
"Orange Petals",
"Shurikens",
"Fukiya Darts",
"Fire Horns"
}));
else
entries.Add(new RulesetLayout("Items", new[]
{
new RulesetLayout("Potions", new[]
{
"Agility",
"Cure",
"Explosion",
"Heal",
"Nightsight",
"Poison",
"Refresh",
"Strength"
})
},
new[]
{
"Bandages",
"Wands",
"Trapped Containers",
"Bolas",
"Mounts",
"Orange Petals",
"Fire Horns"
}));
m_Root = new RulesetLayout("Rules", entries.ToArray());
m_Root.ComputeOffsets();
// Set up default rulesets
if (!Core.AOS)
{
#region Mage 5x
Ruleset m5x = new Ruleset(m_Root);
m5x.Title = "Mage 5x";
m5x.SetOptionRange("Spells", true);
m5x.SetOption("Spells", "Wall of Stone", false);
m5x.SetOption("Spells", "Fire Field", false);
m5x.SetOption("Spells", "Poison Field", false);
m5x.SetOption("Spells", "Energy Field", false);
m5x.SetOption("Spells", "Reactive Armor", false);
m5x.SetOption("Spells", "Protection", false);
m5x.SetOption("Spells", "Teleport", false);
m5x.SetOption("Spells", "Wall of Stone", false);
m5x.SetOption("Spells", "Arch Protection", false);
m5x.SetOption("Spells", "Recall", false);
m5x.SetOption("Spells", "Blade Spirits", false);
m5x.SetOption("Spells", "Incognito", false);
m5x.SetOption("Spells", "Magic Reflection", false);
m5x.SetOption("Spells", "Paralyze", false);
m5x.SetOption("Spells", "Summon Creature", false);
m5x.SetOption("Spells", "Invisibility", false);
m5x.SetOption("Spells", "Mark", false);
m5x.SetOption("Spells", "Paralyze Field", false);
m5x.SetOption("Spells", "Energy Field", false);
m5x.SetOption("Spells", "Gate Travel", false);
m5x.SetOption("Spells", "Polymorph", false);
m5x.SetOption("Spells", "Energy Vortex", false);
m5x.SetOption("Spells", "Air Elemental", false);
m5x.SetOption("Spells", "Summon Daemon", false);
m5x.SetOption("Spells", "Earth Elemental", false);
m5x.SetOption("Spells", "Fire Elemental", false);
m5x.SetOption("Spells", "Water Elemental", false);
m5x.SetOption("Spells", "Earthquake", false);
m5x.SetOption("Spells", "Meteor Swarm", false);
m5x.SetOption("Spells", "Chain Lightning", false);
m5x.SetOption("Spells", "Resurrection", false);
m5x.SetOption("Weapons", "Wrestling", true);
m5x.SetOption("Skills", "Anatomy", true);
m5x.SetOption("Skills", "Detect Hidden", true);
m5x.SetOption("Skills", "Evaluating Intelligence", true);
m5x.SetOption("Items", "Trapped Containers", true);
#endregion
#region Mage 7x
Ruleset m7x = new Ruleset(m_Root);
m7x.Title = "Mage 7x";
m7x.SetOptionRange("Spells", true);
m7x.SetOption("Spells", "Wall of Stone", false);
m7x.SetOption("Spells", "Fire Field", false);
m7x.SetOption("Spells", "Poison Field", false);
m7x.SetOption("Spells", "Energy Field", false);
m7x.SetOption("Spells", "Reactive Armor", false);
m7x.SetOption("Spells", "Protection", false);
m7x.SetOption("Spells", "Teleport", false);
m7x.SetOption("Spells", "Wall of Stone", false);
m7x.SetOption("Spells", "Arch Protection", false);
m7x.SetOption("Spells", "Recall", false);
m7x.SetOption("Spells", "Blade Spirits", false);
m7x.SetOption("Spells", "Incognito", false);
m7x.SetOption("Spells", "Magic Reflection", false);
m7x.SetOption("Spells", "Paralyze", false);
m7x.SetOption("Spells", "Summon Creature", false);
m7x.SetOption("Spells", "Invisibility", false);
m7x.SetOption("Spells", "Mark", false);
m7x.SetOption("Spells", "Paralyze Field", false);
m7x.SetOption("Spells", "Energy Field", false);
m7x.SetOption("Spells", "Gate Travel", false);
m7x.SetOption("Spells", "Polymorph", false);
m7x.SetOption("Spells", "Energy Vortex", false);
m7x.SetOption("Spells", "Air Elemental", false);
m7x.SetOption("Spells", "Summon Daemon", false);
m7x.SetOption("Spells", "Earth Elemental", false);
m7x.SetOption("Spells", "Fire Elemental", false);
m7x.SetOption("Spells", "Water Elemental", false);
m7x.SetOption("Spells", "Earthquake", false);
m7x.SetOption("Spells", "Meteor Swarm", false);
m7x.SetOption("Spells", "Chain Lightning", false);
m7x.SetOption("Spells", "Resurrection", false);
m7x.SetOption("Combat Abilities", "Stun", true);
m7x.SetOption("Skills", "Anatomy", true);
m7x.SetOption("Skills", "Detect Hidden", true);
m7x.SetOption("Skills", "Poisoning", true);
m7x.SetOption("Skills", "Evaluating Intelligence", true);
m7x.SetOption("Weapons", "Wrestling", true);
m7x.SetOption("Potions", "Refresh", true);
m7x.SetOption("Items", "Trapped Containers", true);
m7x.SetOption("Items", "Bandages", true);
#endregion
#region Standard 7x
Ruleset s7x = new Ruleset(m_Root);
s7x.Title = "Standard 7x";
s7x.SetOptionRange("Spells", true);
s7x.SetOption("Spells", "Wall of Stone", false);
s7x.SetOption("Spells", "Fire Field", false);
s7x.SetOption("Spells", "Poison Field", false);
s7x.SetOption("Spells", "Energy Field", false);
s7x.SetOption("Spells", "Teleport", false);
s7x.SetOption("Spells", "Wall of Stone", false);
s7x.SetOption("Spells", "Arch Protection", false);
s7x.SetOption("Spells", "Recall", false);
s7x.SetOption("Spells", "Blade Spirits", false);
s7x.SetOption("Spells", "Incognito", false);
s7x.SetOption("Spells", "Magic Reflection", false);
s7x.SetOption("Spells", "Paralyze", false);
s7x.SetOption("Spells", "Summon Creature", false);
s7x.SetOption("Spells", "Invisibility", false);
s7x.SetOption("Spells", "Mark", false);
s7x.SetOption("Spells", "Paralyze Field", false);
s7x.SetOption("Spells", "Energy Field", false);
s7x.SetOption("Spells", "Gate Travel", false);
s7x.SetOption("Spells", "Polymorph", false);
s7x.SetOption("Spells", "Energy Vortex", false);
s7x.SetOption("Spells", "Air Elemental", false);
s7x.SetOption("Spells", "Summon Daemon", false);
s7x.SetOption("Spells", "Earth Elemental", false);
s7x.SetOption("Spells", "Fire Elemental", false);
s7x.SetOption("Spells", "Water Elemental", false);
s7x.SetOption("Spells", "Earthquake", false);
s7x.SetOption("Spells", "Meteor Swarm", false);
s7x.SetOption("Spells", "Chain Lightning", false);
s7x.SetOption("Spells", "Resurrection", false);
s7x.SetOptionRange("Combat Abilities", true);
s7x.SetOption("Skills", "Anatomy", true);
s7x.SetOption("Skills", "Detect Hidden", true);
s7x.SetOption("Skills", "Poisoning", true);
s7x.SetOption("Skills", "Evaluating Intelligence", true);
s7x.SetOptionRange("Weapons", true);
s7x.SetOption("Weapons", "Runics", false);
s7x.SetOptionRange("Armor", true);
s7x.SetOption("Potions", "Refresh", true);
s7x.SetOption("Items", "Bandages", true);
s7x.SetOption("Items", "Trapped Containers", true);
#endregion
m_Root.Defaults = new[] { m5x, m7x, s7x };
}
else
{
#region Standard All Skills
Ruleset all = new Ruleset(m_Root);
all.Title = "Standard All Skills";
all.SetOptionRange("Spells", true);
all.SetOption("Spells", "Wall of Stone", false);
all.SetOption("Spells", "Fire Field", false);
all.SetOption("Spells", "Poison Field", false);
all.SetOption("Spells", "Energy Field", false);
all.SetOption("Spells", "Teleport", false);
all.SetOption("Spells", "Wall of Stone", false);
all.SetOption("Spells", "Arch Protection", false);
all.SetOption("Spells", "Recall", false);
all.SetOption("Spells", "Blade Spirits", false);
all.SetOption("Spells", "Incognito", false);
all.SetOption("Spells", "Magic Reflection", false);
all.SetOption("Spells", "Paralyze", false);
all.SetOption("Spells", "Summon Creature", false);
all.SetOption("Spells", "Invisibility", false);
all.SetOption("Spells", "Mark", false);
all.SetOption("Spells", "Paralyze Field", false);
all.SetOption("Spells", "Energy Field", false);
all.SetOption("Spells", "Gate Travel", false);
all.SetOption("Spells", "Polymorph", false);
all.SetOption("Spells", "Energy Vortex", false);
all.SetOption("Spells", "Air Elemental", false);
all.SetOption("Spells", "Summon Daemon", false);
all.SetOption("Spells", "Earth Elemental", false);
all.SetOption("Spells", "Fire Elemental", false);
all.SetOption("Spells", "Water Elemental", false);
all.SetOption("Spells", "Earthquake", false);
all.SetOption("Spells", "Meteor Swarm", false);
all.SetOption("Spells", "Chain Lightning", false);
all.SetOption("Spells", "Resurrection", false);
all.SetOptionRange("Necromancy", true);
all.SetOption("Necromancy", "Summon Familiar", false);
all.SetOption("Necromancy", "Vengeful Spirit", false);
all.SetOption("Necromancy", "Animate Dead", false);
all.SetOption("Necromancy", "Wither", false);
all.SetOption("Necromancy", "Poison Strike", false);
all.SetOptionRange("Chivalry", true);
all.SetOption("Chivalry", "Sacred Journey", false);
all.SetOption("Chivalry", "Enemy of One", false);
all.SetOption("Chivalry", "Noble Sacrifice", false);
all.SetOptionRange("Combat Abilities", true);
all.SetOption("Combat Abilities", "Paralyzing Blow", false);
all.SetOption("Combat Abilities", "Shadow Strike", false);
all.SetOption("Skills", "Anatomy", true);
all.SetOption("Skills", "Detect Hidden", true);
all.SetOption("Skills", "Poisoning", true);
all.SetOption("Skills", "Spirit Speak", true);
all.SetOption("Skills", "Evaluating Intelligence", true);
all.SetOptionRange("Weapons", true);
all.SetOption("Weapons", "Poisoned", false);
all.SetOptionRange("Armor", true);
all.SetOptionRange("Ninjitsu", true);
all.SetOption("Ninjitsu", "Animal Form", false);
all.SetOption("Ninjitsu", "Mirror Image", false);
all.SetOption("Ninjitsu", "Backstab", false);
all.SetOption("Ninjitsu", "Suprise Attack", false);
all.SetOption("Ninjitsu", "Shadow Jump", false);
all.SetOptionRange("Bushido", true);
all.SetOptionRange("Spellweaving", true);
all.SetOption("Spellweaving", "Gift of Life", false);
all.SetOption("Spellweaving", "Summon Fey", false);
all.SetOption("Spellweaving", "Summon Fiend", false);
all.SetOption("Spellweaving", "Nature's Fury", false);
all.SetOption("Potions", "Refresh", true);
all.SetOption("Items", "Bandages", true);
all.SetOption("Items", "Trapped Containers", true);
m_Root.Defaults = new[] { all };
#endregion
}
// Set up flavors
Ruleset pots = new Ruleset(m_Root) { Title = "Potions" };
pots.SetOptionRange("Potions", true);
pots.SetOption("Potions", "Explosion", false);
Ruleset para = new Ruleset(m_Root) { Title = "Paralyze" };
para.SetOption("Spells", "Paralyze", true);
para.SetOption("Spells", "Paralyze Field", true);
para.SetOption("Combat Abilities", "Paralyzing Blow", true);
Ruleset fields = new Ruleset(m_Root) { Title = "Fields" };
fields.SetOption("Spells", "Wall of Stone", true);
fields.SetOption("Spells", "Fire Field", true);
fields.SetOption("Spells", "Poison Field", true);
fields.SetOption("Spells", "Energy Field", true);
fields.SetOption("Spells", "Wildfire", true);
Ruleset area = new Ruleset(m_Root) { Title = "Area Effect" };
area.SetOption("Spells", "Earthquake", true);
area.SetOption("Spells", "Meteor Swarm", true);
area.SetOption("Spells", "Chain Lightning", true);
area.SetOption("Necromancy", "Wither", true);
area.SetOption("Necromancy", "Poison Strike", true);
Ruleset summons = new Ruleset(m_Root) { Title = "Summons" };
summons.SetOption("Spells", "Blade Spirits", true);
summons.SetOption("Spells", "Energy Vortex", true);
summons.SetOption("Spells", "Air Elemental", true);
summons.SetOption("Spells", "Summon Daemon", true);
summons.SetOption("Spells", "Earth Elemental", true);
summons.SetOption("Spells", "Fire Elemental", true);
summons.SetOption("Spells", "Water Elemental", true);
summons.SetOption("Necromancy", "Summon Familiar", true);
summons.SetOption("Necromancy", "Vengeful Spirit", true);
summons.SetOption("Necromancy", "Animate Dead", true);
summons.SetOption("Ninjitsu", "Mirror Image", true);
summons.SetOption("Spellweaving", "Summon Fey", true);
summons.SetOption("Spellweaving", "Summon Fiend", true);
summons.SetOption("Spellweaving", "Nature's Fury", true);
m_Root.Flavors = new[] { pots, para, fields, area, summons };
return m_Root;
}
}
public string Title{ get; }
public string Description{ get; }
public string[] Options{ get; }
public int Offset{ get; private set; }
public int TotalLength{ get; private set; }
public RulesetLayout Parent{ get; private set; }
public RulesetLayout[] Children{ get; }
public Ruleset[] Defaults{ get; set; }
public Ruleset[] Flavors{ get; set; }
public RulesetLayout FindByTitle(string title)
{
if (Title == title)
return this;
for (int i = 0; i < Children.Length; ++i)
{
RulesetLayout layout = Children[i].FindByTitle(title);
if (layout != null)
return layout;
}
return null;
}
public string FindByIndex(int index)
{
if (index >= Offset && index < Offset + Options.Length)
return Description + ": " + Options[index - Offset];
for (int i = 0; i < Children.Length; ++i)
{
string opt = Children[i].FindByIndex(index);
if (opt != null)
return opt;
}
return null;
}
public RulesetLayout FindByOption(string title, string option, ref int index)
{
if (title == null || Title == title)
{
index = GetOptionIndex(option);
if (index >= 0)
return this;
title = null;
}
for (int i = 0; i < Children.Length; ++i)
{
RulesetLayout layout = Children[i].FindByOption(title, option, ref index);
if (layout != null)
return layout;
}
return null;
}
public int GetOptionIndex(string option)
{
return Array.IndexOf(Options, option);
}
public void ComputeOffsets()
{
int offset = 0;
RecurseComputeOffsets(ref offset);
}
private int RecurseComputeOffsets(ref int offset)
{
Offset = offset;
offset += Options.Length;
TotalLength += Options.Length;
for (int i = 0; i < Children.Length; ++i)
TotalLength += Children[i].RecurseComputeOffsets(ref offset);
return TotalLength;
}
}
}

View file

@ -0,0 +1,66 @@
using Server.Factions;
using Server.Mobiles;
using Server.Regions;
namespace Server.Engines.ConPVP
{
public class SafeZone : GuardedRegion
{
public static readonly int SafeZonePriority = HouseRegion.HousePriority + 1;
/*public override bool AllowReds => true;*/
public SafeZone(Rectangle2D area, Point3D goloc, Map map, bool isGuarded) : base(null, map, SafeZonePriority, area)
{
GoLocation = goloc;
Disabled = !isGuarded;
Register();
}
public override bool AllowHousing(Mobile from, Point3D p)
{
return from.AccessLevel >= AccessLevel.GameMaster && base.AllowHousing(from, p);
}
public override bool OnMoveInto(Mobile m, Direction d, Point3D newLocation, Point3D oldLocation)
{
if (m.Player && Sigil.ExistsOn(m))
{
m.SendMessage(0x22, "You are holding a sigil and cannot enter this zone.");
return false;
}
PlayerMobile pm = m as PlayerMobile ??
(m is BaseCreature bc && bc.Summoned ?
bc.SummonMaster as PlayerMobile : null);
if (pm?.DuelContext?.StartedBeginCountdown == true)
return true;
if (DuelContext.CheckCombat(m))
{
m.SendMessage(0x22, "You have recently been in combat and cannot enter this zone.");
return false;
}
return base.OnMoveInto(m, d, newLocation, oldLocation);
}
public override void OnEnter(Mobile m)
{
m.SendMessage("You have entered a dueling safezone. No combat other than duels are allowed in this zone.");
}
public override void OnExit(Mobile m)
{
m.SendMessage("You have left a dueling safezone. Combat is now unrestricted.");
}
public override bool CanUseStuckMenu(Mobile m)
{
return false;
}
}
}

View file

@ -0,0 +1,910 @@
using System;
using System.Collections.Generic;
using System.Text;
using Server.Factions;
using Server.Items;
using Server.Network;
using Server.Regions;
namespace Server.Engines.ConPVP
{
public enum TournamentStage
{
Inactive,
Signup,
Fighting
}
public enum GroupingType
{
HighVsLow,
Nearest,
Random
}
public enum TieType
{
Random,
Highest,
Lowest,
FullElimination,
FullAdvancement
}
public enum TourneyType
{
Standard,
FreeForAll,
RandomTeam,
RedVsBlue,
Faction
}
[PropertyObject]
public class Tournament
{
private static readonly TimeSpan SliceInterval = TimeSpan.FromSeconds(12.0);
private int m_ParticipantsPerMatch;
private int m_PlayersPerParticipant;
public Tournament(GenericReader reader)
{
int version = reader.ReadEncodedInt();
switch (version)
{
case 5:
{
FactionRestricted = reader.ReadBool();
goto case 4;
}
case 4:
{
EventController = reader.ReadItem() as EventController;
goto case 3;
}
case 3:
{
SuddenDeathRounds = reader.ReadEncodedInt();
goto case 2;
}
case 2:
{
TourneyType = (TourneyType)reader.ReadEncodedInt();
goto case 1;
}
case 1:
{
GroupType = (GroupingType)reader.ReadEncodedInt();
TieType = (TieType)reader.ReadEncodedInt();
SignupPeriod = reader.ReadTimeSpan();
goto case 0;
}
case 0:
{
if (version < 3)
SuddenDeathRounds = 3;
m_ParticipantsPerMatch = reader.ReadEncodedInt();
m_PlayersPerParticipant = reader.ReadEncodedInt();
SignupPeriod = reader.ReadTimeSpan();
CurrentStage = TournamentStage.Inactive;
Pyramid = new TourneyPyramid();
Ruleset = new Ruleset(RulesetLayout.Root);
Ruleset.ApplyDefault(Ruleset.Layout.Defaults[0]);
Participants = new List<TourneyParticipant>();
Undefeated = new List<TourneyParticipant>();
Arenas = new List<Arena>();
break;
}
}
Timer.DelayCall(SliceInterval, SliceInterval, Slice);
}
public Tournament()
{
m_ParticipantsPerMatch = 2;
m_PlayersPerParticipant = 1;
Pyramid = new TourneyPyramid();
Ruleset = new Ruleset(RulesetLayout.Root);
Ruleset.ApplyDefault(Ruleset.Layout.Defaults[0]);
Participants = new List<TourneyParticipant>();
Undefeated = new List<TourneyParticipant>();
Arenas = new List<Arena>();
SignupPeriod = TimeSpan.FromMinutes(10.0);
Timer.DelayCall(SliceInterval, SliceInterval, Slice);
}
public bool IsNotoRestricted => TourneyType != TourneyType.Standard;
[CommandProperty(AccessLevel.GameMaster)]
public EventController EventController{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public int SuddenDeathRounds{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public TourneyType TourneyType{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public GroupingType GroupType{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public TieType TieType{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public TimeSpan SuddenDeath{ get; set; }
public Ruleset Ruleset{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public int ParticipantsPerMatch
{
get => m_ParticipantsPerMatch;
set
{
if (value < 2) value = 2;
else if (value > 10) value = 10;
m_ParticipantsPerMatch = value;
}
}
[CommandProperty(AccessLevel.GameMaster)]
public int PlayersPerParticipant
{
get => m_PlayersPerParticipant;
set
{
if (value < 1) value = 1;
else if (value > 10) value = 10;
m_PlayersPerParticipant = value;
}
}
[CommandProperty(AccessLevel.GameMaster)]
public int LevelRequirement{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public bool FactionRestricted{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public TimeSpan SignupPeriod{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public DateTime SignupStart{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public TournamentStage CurrentStage{ get; private set; }
public TournamentStage Stage
{
get => CurrentStage;
set => CurrentStage = value;
}
public TourneyPyramid Pyramid{ get; set; }
public List<Arena> Arenas{ get; set; }
public List<TourneyParticipant> Participants{ get; set; }
public List<TourneyParticipant> Undefeated{ get; set; }
public bool IsFactionRestricted => FactionRestricted || TourneyType == TourneyType.Faction;
public bool HasParticipant(Mobile mob)
{
for (int i = 0; i < Participants.Count; ++i)
{
if (Participants[i].Players.Contains(mob))
return true;
}
return false;
}
public void Serialize(GenericWriter writer)
{
writer.WriteEncodedInt(5); // version
writer.Write(FactionRestricted);
writer.Write(EventController);
writer.WriteEncodedInt(SuddenDeathRounds);
writer.WriteEncodedInt((int)TourneyType);
writer.WriteEncodedInt((int)GroupType);
writer.WriteEncodedInt((int)TieType);
writer.Write(SuddenDeath);
writer.WriteEncodedInt(m_ParticipantsPerMatch);
writer.WriteEncodedInt(m_PlayersPerParticipant);
writer.Write(SignupPeriod);
}
public void HandleTie(Arena arena, TourneyMatch match, List<TourneyParticipant> remaining)
{
if (remaining.Count == 1)
HandleWon(arena, match, remaining[0]);
if (remaining.Count < 2)
return;
StringBuilder sb = new StringBuilder();
sb.Append("The match has ended in a tie ");
sb.Append(remaining.Count == 2 ? "between " : "among ");
sb.Append(remaining.Count);
sb.Append(remaining[0].Players.Count == 1 ? " players: " : " teams: ");
bool hasAppended = false;
for (int j = 0; j < match.Participants.Count; ++j)
{
TourneyParticipant part = match.Participants[j];
if (remaining.Contains(part))
{
if (hasAppended)
sb.Append(", ");
sb.Append(part.NameList);
hasAppended = true;
}
else
{
Undefeated.Remove(part);
}
}
sb.Append(". ");
string whole = remaining.Count == 2 ? "both" : "all";
TieType tieType = TieType;
if (tieType == TieType.FullElimination && remaining.Count >= Undefeated.Count)
tieType = TieType.FullAdvancement;
switch (tieType)
{
case TieType.FullAdvancement:
{
sb.AppendFormat("In accordance with the rules, {0} parties are advanced.", whole);
break;
}
case TieType.FullElimination:
{
for (int j = 0; j < remaining.Count; ++j)
Undefeated.Remove(remaining[j]);
sb.AppendFormat("In accordance with the rules, {0} parties are eliminated.", whole);
break;
}
case TieType.Random:
{
TourneyParticipant advanced = remaining[Utility.Random(remaining.Count)];
for (int i = 0; i < remaining.Count; ++i)
if (remaining[i] != advanced)
Undefeated.Remove(remaining[i]);
if (advanced != null)
sb.AppendFormat("In accordance with the rules, {0} {1} advanced.", advanced.NameList,
advanced.Players.Count == 1 ? "is" : "are");
break;
}
case TieType.Highest:
{
TourneyParticipant advanced = null;
for (int i = 0; i < remaining.Count; ++i)
{
TourneyParticipant part = remaining[i];
if (advanced == null || part.TotalLadderXP > advanced.TotalLadderXP)
advanced = part;
}
for (int i = 0; i < remaining.Count; ++i)
if (remaining[i] != advanced)
Undefeated.Remove(remaining[i]);
if (advanced != null)
sb.AppendFormat("In accordance with the rules, {0} {1} advanced.", advanced.NameList,
advanced.Players.Count == 1 ? "is" : "are");
break;
}
case TieType.Lowest:
{
TourneyParticipant advanced = null;
for (int i = 0; i < remaining.Count; ++i)
{
TourneyParticipant part = remaining[i];
if (advanced == null || part.TotalLadderXP < advanced.TotalLadderXP)
advanced = part;
}
for (int i = 0; i < remaining.Count; ++i)
if (remaining[i] != advanced)
Undefeated.Remove(remaining[i]);
if (advanced != null)
sb.AppendFormat("In accordance with the rules, {0} {1} advanced.", advanced.NameList,
advanced.Players.Count == 1 ? "is" : "are");
break;
}
}
Alert(arena, sb.ToString());
}
public void OnEliminated(DuelPlayer player)
{
Participant part = player.Participant;
if (!part.Eliminated)
return;
if (TourneyType == TourneyType.FreeForAll)
{
int rem = 0;
for (int i = 0; i < part.Context.Participants.Count; ++i)
{
if (part.Context.Participants[i]?.Eliminated == false)
++rem;
}
TourneyParticipant tp = part.TourneyPart;
if (tp == null)
return;
if (rem == 1)
GiveAwards(tp.Players, TrophyRank.Silver, ComputeCashAward() / 2);
else if (rem == 2)
GiveAwards(tp.Players, TrophyRank.Bronze, ComputeCashAward() / 4);
}
}
public void HandleWon(Arena arena, TourneyMatch match, TourneyParticipant winner)
{
StringBuilder sb = new StringBuilder();
sb.Append("The match is complete. ");
sb.Append(winner.NameList);
if (winner.Players.Count > 1)
sb.Append(" have bested ");
else
sb.Append(" has bested ");
if (match.Participants.Count > 2)
sb.AppendFormat("{0} other {1}: ", match.Participants.Count - 1,
winner.Players.Count == 1 ? "players" : "teams");
bool hasAppended = false;
for (int j = 0; j < match.Participants.Count; ++j)
{
TourneyParticipant part = match.Participants[j];
if (part == winner)
continue;
Undefeated.Remove(part);
if (hasAppended)
sb.Append(", ");
sb.Append(part.NameList);
hasAppended = true;
}
sb.Append(".");
if (TourneyType == TourneyType.Standard)
Alert(arena, sb.ToString());
}
private int ComputeCashAward()
{
return Participants.Count * m_PlayersPerParticipant * 2500;
}
private void GiveAwards()
{
switch (TourneyType)
{
case TourneyType.FreeForAll:
{
if (Pyramid.Levels.Count < 1)
break;
PyramidLevel top = Pyramid.Levels[Pyramid.Levels.Count - 1];
if (top.FreeAdvance != null || top.Matches.Count != 1)
break;
TourneyMatch match = top.Matches[0];
TourneyParticipant winner = match.Winner;
if (winner != null)
GiveAwards(winner.Players, TrophyRank.Gold, ComputeCashAward());
break;
}
case TourneyType.Standard:
{
if (Pyramid.Levels.Count < 2)
break;
PyramidLevel top = Pyramid.Levels[Pyramid.Levels.Count - 1];
if (top.FreeAdvance != null || top.Matches.Count != 1)
break;
int cash = ComputeCashAward();
TourneyMatch match = top.Matches[0];
TourneyParticipant winner = match.Winner;
for (int i = 0; i < match.Participants.Count; ++i)
{
TourneyParticipant part = match.Participants[i];
if (part == winner)
GiveAwards(part.Players, TrophyRank.Gold, cash);
else
GiveAwards(part.Players, TrophyRank.Silver, cash / 2);
}
PyramidLevel next = Pyramid.Levels[Pyramid.Levels.Count - 2];
if (next.Matches.Count > 2)
break;
for (int i = 0; i < next.Matches.Count; ++i)
{
match = next.Matches[i];
winner = match.Winner;
for (int j = 0; j < match.Participants.Count; ++j)
{
TourneyParticipant part = match.Participants[j];
if (part != winner)
GiveAwards(part.Players, TrophyRank.Bronze, cash / 4);
}
}
break;
}
}
}
private void GiveAwards(List<Mobile> players, TrophyRank rank, int cash)
{
if (players.Count == 0)
return;
if (players.Count > 1)
cash /= players.Count - 1;
cash += 500;
cash /= 1000;
cash *= 1000;
StringBuilder sb = new StringBuilder();
if (TourneyType == TourneyType.FreeForAll)
{
sb.Append(Participants.Count * m_PlayersPerParticipant);
sb.Append("-man FFA");
}
else if (TourneyType == TourneyType.RandomTeam)
{
sb.Append(m_ParticipantsPerMatch);
sb.Append("-Team");
}
else if (TourneyType == TourneyType.Faction)
{
sb.Append(m_ParticipantsPerMatch);
sb.Append("-Team Faction");
}
else if (TourneyType == TourneyType.RedVsBlue)
{
sb.Append("Red v Blue");
}
else
{
for (int i = 0; i < m_ParticipantsPerMatch; ++i)
{
if (sb.Length > 0)
sb.Append('v');
sb.Append(m_PlayersPerParticipant);
}
}
if (EventController != null)
sb.Append(' ').Append(EventController.Title);
sb.Append(" Champion");
string title = sb.ToString();
for (int i = 0; i < players.Count; ++i)
{
Mobile mob = players[i];
if (mob?.Deleted != false)
continue;
Item item = new Trophy(title, rank);
if (!mob.PlaceInBackpack(item))
mob.BankBox.DropItem(item);
if (cash > 0)
{
item = new BankCheck(cash);
if (!mob.PlaceInBackpack(item))
mob.BankBox.DropItem(item);
mob.SendMessage(
"You have been awarded a {0} trophy and {1:N0}gp for your participation in this tournament.",
rank.ToString().ToLower(), cash);
}
else
{
mob.SendMessage("You have been awarded a {0} trophy for your participation in this tournament.",
rank.ToString().ToLower());
}
}
}
public void Slice()
{
if (CurrentStage == TournamentStage.Signup)
{
TimeSpan until = SignupStart + SignupPeriod - DateTime.UtcNow;
if (until <= TimeSpan.Zero)
{
for (int i = Participants.Count - 1; i >= 0; --i)
{
TourneyParticipant part = Participants[i];
bool bad = false;
for (int j = 0; j < part.Players.Count; ++j)
{
Mobile check = part.Players[j];
if (check.Deleted || check.Map == null || check.Map == Map.Internal || !check.Alive ||
Sigil.ExistsOn(check) || check.Region.IsPartOf<Jail>())
{
bad = true;
break;
}
}
if (bad)
{
for (int j = 0; j < part.Players.Count; ++j)
part.Players[j].SendMessage("You have been disqualified from the tournament.");
Participants.RemoveAt(i);
}
}
if (Participants.Count >= 2)
{
CurrentStage = TournamentStage.Fighting;
Undefeated.Clear();
Pyramid.Levels.Clear();
Pyramid.AddLevel(m_ParticipantsPerMatch, Participants, GroupType, TourneyType);
PyramidLevel level = Pyramid.Levels[0];
if (level.FreeAdvance != null)
Undefeated.Add(level.FreeAdvance);
for (int i = 0; i < level.Matches.Count; ++i)
{
TourneyMatch match = level.Matches[i];
Undefeated.AddRange(match.Participants);
}
Alert("Hear ye! Hear ye!", "The tournament will begin shortly.");
}
else
{
/*Alert( "Is this all?", "Pitiful. Signup extended." );
m_SignupStart = DateTime.UtcNow;*/
Alert("Is this all?", "Pitiful. Tournament cancelled.");
CurrentStage = TournamentStage.Inactive;
}
}
else if (Math.Abs(until.TotalSeconds - TimeSpan.FromMinutes(1.0).TotalSeconds) <
SliceInterval.TotalSeconds / 2)
{
Alert("Last call!", "If you wish to enter the tournament, sign up with the registrar now.");
}
else if (Math.Abs(until.TotalSeconds - TimeSpan.FromMinutes(5.0).TotalSeconds) <
SliceInterval.TotalSeconds / 2)
{
Alert("The tournament will begin in 5 minutes.", "Sign up now before it's too late.");
}
}
else if (CurrentStage == TournamentStage.Fighting)
{
if (Undefeated.Count == 1)
{
TourneyParticipant winner = Undefeated[0];
try
{
if (EventController != null)
{
Alert("The tournament has completed!",
$"Team {EventController.GetTeamName(Pyramid.Levels[0].Matches[0].Participants.IndexOf(winner))} has won!");
}
else if (TourneyType == TourneyType.RandomTeam)
{
Alert("The tournament has completed!",
$"Team {Pyramid.Levels[0].Matches[0].Participants.IndexOf(winner) + 1} has won!");
}
else if (TourneyType == TourneyType.Faction)
{
if (m_ParticipantsPerMatch == 4)
{
string name = "(null)";
switch (Pyramid.Levels[0].Matches[0].Participants.IndexOf(
winner))
{
case 0:
{
name = "Minax";
break;
}
case 1:
{
name = "Council of Mages";
break;
}
case 2:
{
name = "True Britannians";
break;
}
case 3:
{
name = "Shadowlords";
break;
}
}
Alert("The tournament has completed!", $"The {name} team has won!");
}
else if (m_ParticipantsPerMatch == 2)
{
Alert("The tournament has completed!",
$"The {(Pyramid.Levels[0].Matches[0].Participants.IndexOf(winner) == 0 ? "Evil" : "Hero")} team has won!");
}
else
{
Alert("The tournament has completed!",
$"Team {Pyramid.Levels[0].Matches[0].Participants.IndexOf(winner) + 1} has won!");
}
}
else if (TourneyType == TourneyType.RedVsBlue)
{
Alert("The tournament has completed!",
$"Team {(Pyramid.Levels[0].Matches[0].Participants.IndexOf(winner) == 0 ? "Red" : "Blue")} has won!");
}
else
{
Alert("The tournament has completed!",
$"{winner.NameList} {(winner.Players.Count > 1 ? "are" : "is")} the champion{(winner.Players.Count == 1 ? "" : "s")}.");
}
}
catch
{
// ignored
}
GiveAwards();
CurrentStage = TournamentStage.Inactive;
Undefeated.Clear();
}
else if (Pyramid.Levels.Count > 0)
{
PyramidLevel activeLevel = Pyramid.Levels[Pyramid.Levels.Count - 1];
bool stillGoing = false;
for (int i = 0; i < activeLevel.Matches.Count; ++i)
{
TourneyMatch match = activeLevel.Matches[i];
if (match.Winner == null)
{
stillGoing = true;
if (!match.InProgress)
for (int j = 0; j < Arenas.Count; ++j)
{
Arena arena = Arenas[j];
if (!arena.IsOccupied)
{
match.Start(arena, this);
break;
}
}
}
}
if (!stillGoing)
{
for (int i = Undefeated.Count - 1; i >= 0; --i)
{
TourneyParticipant part = Undefeated[i];
bool bad = false;
for (int j = 0; j < part.Players.Count; ++j)
{
Mobile check = part.Players[j];
if (check.Deleted || check.Map == null || check.Map == Map.Internal || !check.Alive ||
Sigil.ExistsOn(check) || check.Region.IsPartOf<Jail>())
{
bad = true;
break;
}
}
if (!bad)
continue;
for (int j = 0; j < part.Players.Count; ++j)
part.Players[j].SendMessage("You have been disqualified from the tournament.");
Undefeated.RemoveAt(i);
if (Undefeated.Count == 1)
{
TourneyParticipant winner = Undefeated[0];
try
{
if (EventController != null)
{
Alert("The tournament has completed!",
$"Team {EventController.GetTeamName(Pyramid.Levels[0].Matches[0].Participants.IndexOf(winner))} has won");
}
else if (TourneyType == TourneyType.RandomTeam)
{
Alert("The tournament has completed!",
$"Team {Pyramid.Levels[0].Matches[0].Participants.IndexOf(winner) + 1} has won!");
}
else if (TourneyType == TourneyType.Faction)
{
if (m_ParticipantsPerMatch == 4)
{
string name = "(null)";
switch (Pyramid.Levels[0].Matches[0]
.Participants.IndexOf(winner))
{
case 0:
{
name = "Minax";
break;
}
case 1:
{
name = "Council of Mages";
break;
}
case 2:
{
name = "True Britannians";
break;
}
case 3:
{
name = "Shadowlords";
break;
}
}
Alert("The tournament has completed!", $"The {name} team has won!");
}
else if (m_ParticipantsPerMatch == 2)
{
Alert("The tournament has completed!",
$"The {(Pyramid.Levels[0].Matches[0].Participants.IndexOf(winner) == 0 ? "Evil" : "Hero")} team has won!");
}
else
{
Alert("The tournament has completed!",
$"Team {Pyramid.Levels[0].Matches[0].Participants.IndexOf(winner) + 1} has won!");
}
}
else if (TourneyType == TourneyType.RedVsBlue)
{
Alert("The tournament has completed!",
$"Team {(Pyramid.Levels[0].Matches[0].Participants.IndexOf(winner) == 0 ? "Red" : "Blue")} has won!");
}
else
{
Alert("The tournament has completed!",
$"{winner.NameList} {(winner.Players.Count > 1 ? "are" : "is")} the champion{(winner.Players.Count == 1 ? "" : "s")}.");
}
}
catch
{
// ignored
}
GiveAwards();
CurrentStage = TournamentStage.Inactive;
Undefeated.Clear();
break;
}
}
if (Undefeated.Count > 1)
Pyramid.AddLevel(m_ParticipantsPerMatch, Undefeated, GroupType, TourneyType);
}
}
}
}
public void Alert(params string[] alerts)
{
for (int i = 0; i < Arenas.Count; ++i)
Alert(Arenas[i], alerts);
}
public void Alert(Arena arena, params string[] alerts)
{
if (arena?.Announcer != null)
for (int j = 0; j < alerts.Length; ++j)
{
string alert = alerts[j];
Timer.DelayCall(TimeSpan.FromSeconds(Math.Max(j - 0.5, 0.0)),
() => arena.Announcer.PublicOverheadMessage(MessageType.Regular, 0x35, false, alert));
}
}
}
}

View file

@ -0,0 +1,65 @@
using Server.Network;
namespace Server.Engines.ConPVP
{
public class TournamentBracketItem : Item
{
[Constructible]
public TournamentBracketItem() : base(3774)
{
Movable = false;
}
public TournamentBracketItem(Serial serial) : base(serial)
{
}
[CommandProperty(AccessLevel.GameMaster)]
public TournamentController Tournament{ get; set; }
public override string DefaultName => "tournament bracket";
public override void OnDoubleClick(Mobile from)
{
if (!from.InRange(GetWorldLocation(), 2))
{
from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019045); // I can't reach that
}
else
{
Tournament tourney = Tournament?.Tournament;
if (tourney != null)
{
from.CloseGump<TournamentBracketGump>();
from.SendGump(new TournamentBracketGump(from, tourney, TourneyBracketGumpType.Index));
}
}
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
writer.Write(Tournament);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 0:
{
Tournament = reader.ReadItem() as TournamentController;
break;
}
}
}
}
}

View file

@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using Server.ContextMenus;
using Server.Gumps;
namespace Server.Engines.ConPVP
{
public class TournamentController : Item
{
private static List<TournamentController> m_Instances = new List<TournamentController>();
[Constructible]
public TournamentController() : base(0x1B7A)
{
Visible = false;
Movable = false;
Tournament = new Tournament();
m_Instances.Add(this);
}
public TournamentController(Serial serial) : base(serial)
{
}
[CommandProperty(AccessLevel.GameMaster)]
public Tournament Tournament{ get; private set; }
public static bool IsActive
{
get
{
for (int i = 0; i < m_Instances.Count; ++i)
{
TournamentController controller = m_Instances[i];
if (controller?.Deleted == false && controller.Tournament != null &&
controller.Tournament.Stage != TournamentStage.Inactive)
return true;
}
return false;
}
}
public override string DefaultName => "tournament controller";
public override void GetContextMenuEntries(Mobile from, List<ContextMenuEntry> list)
{
base.GetContextMenuEntries(from, list);
if (from.AccessLevel >= AccessLevel.GameMaster && Tournament != null)
{
list.Add(new EditEntry(Tournament));
if (Tournament.CurrentStage == TournamentStage.Inactive)
list.Add(new StartEntry(Tournament));
}
}
public override void OnDoubleClick(Mobile from)
{
if (from.AccessLevel >= AccessLevel.GameMaster && Tournament != null)
{
from.CloseGump<PickRulesetGump>();
from.CloseGump<RulesetGump>();
from.SendGump(new PickRulesetGump(from, null, Tournament.Ruleset));
}
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
Tournament.Serialize(writer);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 0:
{
Tournament = new Tournament(reader);
break;
}
}
m_Instances.Add(this);
}
public override void OnDelete()
{
base.OnDelete();
m_Instances.Remove(this);
}
private class EditEntry : ContextMenuEntry
{
private Tournament m_Tournament;
public EditEntry(Tournament tourney) : base(5101)
{
m_Tournament = tourney;
}
public override void OnClick()
{
Owner.From.SendGump(new PropertiesGump(Owner.From, m_Tournament));
}
}
private class StartEntry : ContextMenuEntry
{
private Tournament m_Tournament;
public StartEntry(Tournament tourney) : base(5113)
{
m_Tournament = tourney;
}
public override void OnClick()
{
if (m_Tournament.Stage == TournamentStage.Inactive)
{
m_Tournament.SignupStart = DateTime.UtcNow;
m_Tournament.Stage = TournamentStage.Signup;
m_Tournament.Participants.Clear();
m_Tournament.Pyramid.Levels.Clear();
m_Tournament.Alert("Hear ye! Hear ye!",
"Tournament signup has opened. You can enter by signing up with the registrar.");
}
}
}
}
}

View file

@ -0,0 +1,190 @@
using System.Collections.Generic;
using Server.Ethics;
using Server.Factions;
namespace Server.Engines.ConPVP
{
public class TourneyPyramid
{
public TourneyPyramid()
{
Levels = new List<PyramidLevel>();
}
public List<PyramidLevel> Levels{ get; set; }
public void AddLevel(int partsPerMatch, List<TourneyParticipant> participants, GroupingType groupType, TourneyType tourneyType)
{
List<TourneyParticipant> copy = new List<TourneyParticipant>(participants);
if (groupType == GroupingType.Nearest || groupType == GroupingType.HighVsLow)
copy.Sort();
PyramidLevel level = new PyramidLevel();
switch (tourneyType)
{
case TourneyType.RedVsBlue:
{
TourneyParticipant[] parts = new TourneyParticipant[2];
for (int i = 0; i < parts.Length; ++i)
parts[i] = new TourneyParticipant(new List<Mobile>());
for (int i = 0; i < copy.Count; ++i)
{
List<Mobile> players = copy[i].Players;
for (int j = 0; j < players.Count; ++j)
{
Mobile mob = players[j];
if (mob.Kills >= 5)
parts[0].Players.Add(mob);
else
parts[1].Players.Add(mob);
}
}
level.Matches.Add(new TourneyMatch(new List<TourneyParticipant>(parts)));
break;
}
case TourneyType.Faction:
{
TourneyParticipant[] parts = new TourneyParticipant[partsPerMatch];
for (int i = 0; i < parts.Length; ++i)
parts[i] = new TourneyParticipant(new List<Mobile>());
for (int i = 0; i < copy.Count; ++i)
{
List<Mobile> players = copy[i].Players;
for (int j = 0; j < players.Count; ++j)
{
Mobile mob = players[j];
int index = -1;
if (partsPerMatch == 4)
{
Faction fac = Faction.Find(mob);
if (fac != null)
index = fac.Definition.Sort;
}
else if (partsPerMatch == 2)
{
if (Ethic.Evil.IsEligible(mob))
index = 0;
else if (Ethic.Hero.IsEligible(mob)) index = 1;
}
if (index < 0 || index >= partsPerMatch) index = i % partsPerMatch;
parts[index].Players.Add(mob);
}
}
level.Matches.Add(new TourneyMatch(new List<TourneyParticipant>(parts)));
break;
}
case TourneyType.RandomTeam:
{
TourneyParticipant[] parts = new TourneyParticipant[partsPerMatch];
for (int i = 0; i < partsPerMatch; ++i)
parts[i] = new TourneyParticipant(new List<Mobile>());
for (int i = 0; i < copy.Count; ++i)
parts[i % parts.Length].Players.AddRange(copy[i].Players);
level.Matches.Add(new TourneyMatch(new List<TourneyParticipant>(parts)));
break;
}
case TourneyType.FreeForAll:
{
level.Matches.Add(new TourneyMatch(copy));
break;
}
case TourneyType.Standard:
{
if (partsPerMatch >= 2 && participants.Count % partsPerMatch == 1)
{
int lowAdvances = int.MaxValue;
for (int i = 0; i < participants.Count; ++i)
{
TourneyParticipant p = participants[i];
if (p.FreeAdvances < lowAdvances)
lowAdvances = p.FreeAdvances;
}
List<TourneyParticipant> toAdvance = new List<TourneyParticipant>();
for (int i = 0; i < participants.Count; ++i)
{
TourneyParticipant p = participants[i];
if (p.FreeAdvances == lowAdvances)
toAdvance.Add(p);
}
if (toAdvance.Count == 0)
toAdvance = copy; // sanity
int idx = Utility.Random(toAdvance.Count);
toAdvance[idx].AddLog(
"Advanced automatically due to an odd number of challengers.");
level.FreeAdvance = toAdvance[idx];
++level.FreeAdvance.FreeAdvances;
copy.Remove(toAdvance[idx]);
}
while (copy.Count >= partsPerMatch)
{
List<TourneyParticipant> thisMatch = new List<TourneyParticipant>();
for (int i = 0; i < partsPerMatch; ++i)
{
int idx = 0;
switch (groupType)
{
case GroupingType.HighVsLow:
idx = i * (copy.Count - 1) / (partsPerMatch - 1);
break;
case GroupingType.Nearest:
idx = 0;
break;
case GroupingType.Random:
idx = Utility.Random(copy.Count);
break;
}
thisMatch.Add(copy[idx]);
copy.RemoveAt(idx);
}
level.Matches.Add(new TourneyMatch(thisMatch));
}
if (copy.Count > 1)
level.Matches.Add(new TourneyMatch(copy));
break;
}
}
Levels.Add(level);
}
}
public class PyramidLevel
{
public List<TourneyMatch> Matches{ get; set; } = new List<TourneyMatch>();
public TourneyParticipant FreeAdvance{ get; set; }
}
}

View file

@ -0,0 +1,93 @@
using System;
using Server.Factions;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class TournamentRegistrar : Banker
{
[Constructible]
public TournamentRegistrar()
{
Timer.DelayCall(TimeSpan.FromSeconds(30.0), TimeSpan.FromSeconds(30.0), Announce_Callback);
}
public TournamentRegistrar(Serial serial) : base(serial)
{
}
[CommandProperty(AccessLevel.GameMaster)]
public TournamentController Tournament{ get; set; }
private void Announce_Callback()
{
Tournament tourney = Tournament?.Tournament;
if (tourney?.Stage == TournamentStage.Signup)
PublicOverheadMessage(MessageType.Regular, 0x35, false,
"Come one, come all! Do you aspire to be a fighter of great renown? Join this tournament and show the world your abilities.");
}
public override void OnMovement(Mobile m, Point3D oldLocation)
{
base.OnMovement(m, oldLocation);
Tournament tourney = Tournament?.Tournament;
if (InRange(m, 4) && !InRange(oldLocation, 4) && tourney != null && tourney.Stage == TournamentStage.Signup &&
m.CanBeginAction(this))
{
Ladder ladder = Ladder.Instance;
LadderEntry entry = ladder?.Find(m);
if (entry != null && Ladder.GetLevel(entry.Experience) < tourney.LevelRequirement)
return;
if (tourney.IsFactionRestricted && Faction.Find(m) == null) return;
if (tourney.HasParticipant(m))
return;
PrivateOverheadMessage(MessageType.Regular, 0x35, false,
$"Hello m'{(m.Female ? "Lady" : "Lord")}. Dost thou wish to enter this tournament? You need only to write your name in this book.",
m.NetState);
m.BeginAction(this);
Timer.DelayCall(TimeSpan.FromSeconds(10.0), ReleaseLock_Callback, m);
}
}
public void ReleaseLock_Callback(Mobile m)
{
m.EndAction(this);
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
writer.Write(Tournament);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 0:
{
Tournament = reader.ReadItem() as TournamentController;
break;
}
}
Timer.DelayCall(TimeSpan.FromSeconds(30.0), TimeSpan.FromSeconds(30.0), Announce_Callback);
}
}
}

View file

@ -0,0 +1,149 @@
using System.Collections.Generic;
using Server.Factions;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class TournamentSignupItem : Item
{
[Constructible]
public TournamentSignupItem() : base(4029)
{
Movable = false;
}
public TournamentSignupItem(Serial serial) : base(serial)
{
}
[CommandProperty(AccessLevel.GameMaster)]
public TournamentController Tournament{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public Mobile Registrar{ get; set; }
public override string DefaultName => "tournament signup book";
public override void OnDoubleClick(Mobile from)
{
if (!from.InRange(GetWorldLocation(), 2))
{
from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019045); // I can't reach that
}
else
{
Tournament tourney = Tournament?.Tournament;
if (tourney == null)
return;
if (Registrar != null)
Registrar.Direction = Registrar.GetDirectionTo(this);
switch (tourney.Stage)
{
case TournamentStage.Fighting:
{
if (Registrar != null)
{
if (tourney.HasParticipant(from))
Registrar.PrivateOverheadMessage(MessageType.Regular,
0x35, false, "Excuse me? You are already signed up.", from.NetState);
else
Registrar.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "The tournament has already begun. You are too late to signup now.",
from.NetState);
}
break;
}
case TournamentStage.Inactive:
{
Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x35, false, "The tournament is closed.", from.NetState);
break;
}
case TournamentStage.Signup:
{
Ladder ladder = Ladder.Instance;
LadderEntry entry = ladder?.Find(from);
if (entry != null && Ladder.GetLevel(entry.Experience) < tourney.LevelRequirement)
{
Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x35, false, "You have not yet proven yourself a worthy dueler.", from.NetState);
break;
}
if (tourney.IsFactionRestricted && Faction.Find(from) == null)
{
Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x35, false, "Only those who have declared their faction allegiance may participate.",
from.NetState);
break;
}
if (from.HasGump<AcceptTeamGump>())
{
Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "You must first respond to the offer I've given you.", from.NetState);
}
else if (from.HasGump<AcceptDuelGump>())
{
Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "You must first cancel your duel offer.", from.NetState);
}
else if (from is PlayerMobile mobile && mobile.DuelContext != null)
{
Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "You are already participating in a duel.", mobile.NetState);
}
else if (!tourney.HasParticipant(from))
{
from.CloseGump<ConfirmSignupGump>();
from.SendGump(new ConfirmSignupGump(from, Registrar, tourney, new List<Mobile> { from }));
}
else
{
Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x35, false, "You have already entered this tournament.", from.NetState);
}
break;
}
}
}
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
writer.Write(Tournament);
writer.Write(Registrar);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 0:
{
Tournament = reader.ReadItem() as TournamentController;
Registrar = reader.ReadMobile();
break;
}
}
}
}
}

View file

@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Server.Engines.ConPVP
{
public class TourneyParticipant : IComparable<TourneyParticipant>
{
public TourneyParticipant(Mobile owner)
{
Log = new List<string>();
Players = new List<Mobile> { owner };
}
public TourneyParticipant(List<Mobile> players)
{
Log = new List<string>();
Players = players;
}
public List<Mobile> Players{ get; set; }
public List<string> Log{ get; set; }
public int FreeAdvances{ get; set; }
public int TotalLadderXP
{
get
{
Ladder ladder = Ladder.Instance;
if (ladder == null)
return 0;
int total = 0;
for (int i = 0; i < Players.Count; ++i)
{
Mobile mob = Players[i];
LadderEntry entry = ladder.Find(mob);
if (entry != null)
total += entry.Experience;
}
return total;
}
}
public string NameList
{
get
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < Players.Count; ++i)
{
if (Players[i] == null)
continue;
Mobile mob = Players[i];
if (sb.Length > 0)
{
if (Players.Count == 2)
sb.Append(" and ");
else if (i + 1 < Players.Count)
sb.Append(", ");
else
sb.Append(", and ");
}
sb.Append(mob.Name);
}
if (sb.Length == 0)
return "Empty";
return sb.ToString();
}
}
public int CompareTo(TourneyParticipant p)
{
return p.TotalLadderXP - TotalLadderXP;
}
public void AddLog(string text)
{
Log.Add(text);
}
public void AddLog(string format, params object[] args)
{
AddLog(string.Format(format, args));
}
public void WonMatch(TourneyMatch match)
{
AddLog("Match won.");
}
public void LostMatch(TourneyMatch match)
{
AddLog("Match lost.");
}
}
}

View file

@ -0,0 +1,119 @@
using System;
namespace Server.Items
{
public enum TrophyRank
{
Bronze,
Silver,
Gold
}
[Flippable(5020, 4647)]
public class Trophy : Item
{
private TrophyRank m_Rank;
[Constructible]
public Trophy(string title, TrophyRank rank) : base(5020)
{
Title = title;
m_Rank = rank;
Date = DateTime.UtcNow;
LootType = LootType.Blessed;
UpdateStyle();
}
public Trophy(Serial serial) : base(serial)
{
}
[CommandProperty(AccessLevel.GameMaster)]
public string Title{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public TrophyRank Rank
{
get => m_Rank;
set
{
m_Rank = value;
UpdateStyle();
}
}
[CommandProperty(AccessLevel.GameMaster)]
public Mobile Owner{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public DateTime Date{ get; private set; }
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(1); // version
writer.Write(Title);
writer.Write((int)m_Rank);
writer.Write(Owner);
writer.Write(Date);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
Title = reader.ReadString();
m_Rank = (TrophyRank)reader.ReadInt();
Owner = reader.ReadMobile();
Date = reader.ReadDateTime();
if (version == 0)
LootType = LootType.Blessed;
}
public override void OnAdded(IEntity parent)
{
base.OnAdded(parent);
if (Owner == null)
Owner = RootParent as Mobile;
}
public override void OnSingleClick(Mobile from)
{
base.OnSingleClick(from);
if (Owner != null)
LabelTo(from, "{0} -- {1}", Title, Owner.RawName);
else if (Title != null)
LabelTo(from, Title);
if (Date != DateTime.MinValue)
LabelTo(from, Date.ToString("d"));
}
public void UpdateStyle()
{
Name = $"{m_Rank.ToString().ToLower()} trophy";
switch (m_Rank)
{
case TrophyRank.Gold:
Hue = 2213;
break;
case TrophyRank.Silver:
Hue = 0;
break;
case TrophyRank.Bronze:
Hue = 2206;
break;
}
}
}
}

View file

@ -0,0 +1,55 @@
using System.Collections.Generic;
namespace Server.Engines.Craft
{
public enum CraftMarkOption
{
MarkItem,
DoNotMark,
PromptForMark
}
public class CraftContext
{
public CraftContext()
{
Items = new List<CraftItem>();
LastResourceIndex = -1;
LastResourceIndex2 = -1;
LastGroupIndex = -1;
}
public List<CraftItem> Items{ get; }
public int LastResourceIndex{ get; set; }
public int LastResourceIndex2{ get; set; }
public int LastGroupIndex{ get; set; }
public bool DoNotColor{ get; set; }
public CraftMarkOption MarkOption{ get; set; }
public CraftItem LastMade
{
get
{
if (Items.Count > 0)
return Items[0];
return null;
}
}
public void OnMade(CraftItem item)
{
Items.Remove(item);
if (Items.Count == 10)
Items.RemoveAt(9);
Items.Insert(0, item);
}
}
}

View file

@ -0,0 +1,23 @@
namespace Server.Engines.Craft
{
public class CraftGroup
{
public CraftGroup(TextDefinition groupName)
{
NameNumber = groupName;
NameString = groupName;
CraftItems = new CraftItemCol();
}
public CraftItemCol CraftItems{ get; }
public string NameString{ get; }
public int NameNumber{ get; }
public void AddCraftItem(CraftItem craftItem)
{
CraftItems.Add(craftItem);
}
}
}

View file

@ -0,0 +1,45 @@
using System.Collections;
namespace Server.Engines.Craft
{
public class CraftGroupCol : CollectionBase
{
public int Add(CraftGroup craftGroup)
{
return List.Add(craftGroup);
}
public void Remove(int index)
{
if (index > Count - 1 || index < 0)
{
}
else
{
List.RemoveAt(index);
}
}
public CraftGroup GetAt(int index)
{
return (CraftGroup)List[index];
}
public int SearchFor(TextDefinition groupName)
{
for (int i = 0; i < List.Count; i++)
{
CraftGroup craftGroup = (CraftGroup)List[i];
int nameNumber = craftGroup.NameNumber;
string nameString = craftGroup.NameString;
if (nameNumber != 0 && nameNumber == groupName.Number ||
nameString != null && nameString == groupName.String)
return i;
}
return -1;
}
}
}

View file

@ -0,0 +1,608 @@
using System;
using System.Collections.Generic;
using Server.Gumps;
using Server.Items;
using Server.Network;
namespace Server.Engines.Craft
{
public class CraftGump : Gump
{
private const int LabelHue = 0x480;
private const int LabelColor = 0x7FFF;
private const int FontColor = 0xFFFFFF;
private CraftSystem m_CraftSystem;
private Mobile m_From;
private CraftPage m_Page;
private BaseTool m_Tool;
public CraftGump(Mobile from, CraftSystem craftSystem, BaseTool tool, object notice, CraftPage page = CraftPage.None) : base(40, 40)
{
m_From = from;
m_CraftSystem = craftSystem;
m_Tool = tool;
m_Page = page;
CraftContext context = craftSystem.GetContext(from);
from.CloseGump<CraftGump>();
from.CloseGump<CraftGumpItem>();
AddPage(0);
AddBackground(0, 0, 530, 437, 5054);
AddImageTiled(10, 10, 510, 22, 2624);
AddImageTiled(10, 292, 150, 45, 2624);
AddImageTiled(165, 292, 355, 45, 2624);
AddImageTiled(10, 342, 510, 85, 2624);
AddImageTiled(10, 37, 200, 250, 2624);
AddImageTiled(215, 37, 305, 250, 2624);
AddAlphaRegion(10, 10, 510, 417);
if (craftSystem.GumpTitleNumber > 0)
AddHtmlLocalized(10, 12, 510, 20, craftSystem.GumpTitleNumber, LabelColor);
else
AddHtml(10, 12, 510, 20, craftSystem.GumpTitleString);
AddHtmlLocalized(10, 37, 200, 22, 1044010, LabelColor); // <CENTER>CATEGORIES</CENTER>
AddHtmlLocalized(215, 37, 305, 22, 1044011, LabelColor); // <CENTER>SELECTIONS</CENTER>
AddHtmlLocalized(10, 302, 150, 25, 1044012, LabelColor); // <CENTER>NOTICES</CENTER>
AddButton(15, 402, 4017, 4019, 0);
AddHtmlLocalized(50, 405, 150, 18, 1011441, LabelColor); // EXIT
AddButton(270, 402, 4005, 4007, GetButtonID(6, 2));
AddHtmlLocalized(305, 405, 150, 18, 1044013, LabelColor); // MAKE LAST
// Mark option
if (craftSystem.MarkOption)
{
AddButton(270, 362, 4005, 4007, GetButtonID(6, 6));
AddHtmlLocalized(305, 365, 150, 18, 1044017 + (context == null ? 0 : (int)context.MarkOption), LabelColor); // MARK ITEM
}
// ****************************************
// Resmelt option
if (craftSystem.Resmelt)
{
AddButton(15, 342, 4005, 4007, GetButtonID(6, 1));
AddHtmlLocalized(50, 345, 150, 18, 1044259, LabelColor); // SMELT ITEM
}
// ****************************************
// Repair option
if (craftSystem.Repair)
{
AddButton(270, 342, 4005, 4007, GetButtonID(6, 5));
AddHtmlLocalized(305, 345, 150, 18, 1044260, LabelColor); // REPAIR ITEM
}
// ****************************************
// Enhance option
if (craftSystem.CanEnhance)
{
AddButton(270, 382, 4005, 4007, GetButtonID(6, 8));
AddHtmlLocalized(305, 385, 150, 18, 1061001, LabelColor); // ENHANCE ITEM
}
// ****************************************
if (notice is int noticeInt && noticeInt > 0)
AddHtmlLocalized(170, 295, 350, 40, noticeInt, LabelColor);
else if (notice is string)
AddHtml(170, 295, 350, 40, $"<BASEFONT COLOR=#{FontColor:X6}>{notice}</BASEFONT>");
// If the system has more than one resource
if (craftSystem.CraftSubRes.Init)
{
string nameString = craftSystem.CraftSubRes.NameString;
int nameNumber = craftSystem.CraftSubRes.NameNumber;
int resIndex = context?.LastResourceIndex ?? -1;
Type resourceType = craftSystem.CraftSubRes.ResType;
if (resIndex > -1)
{
CraftSubRes subResource = craftSystem.CraftSubRes.GetAt(resIndex);
nameString = subResource.NameString;
nameNumber = subResource.NameNumber;
resourceType = subResource.ItemType;
}
int resourceCount = 0;
if (from.Backpack != null)
{
Item[] items = from.Backpack.FindItemsByType(resourceType);
for (int i = 0; i < items.Length; ++i)
resourceCount += items[i].Amount;
}
AddButton(15, 362, 4005, 4007, GetButtonID(6, 0));
if (nameNumber > 0)
AddHtmlLocalized(50, 365, 250, 18, nameNumber, resourceCount.ToString(), LabelColor);
else
AddLabel(50, 362, LabelHue, $"{nameString} ({resourceCount} Available)");
}
// ****************************************
// For dragon scales
if (craftSystem.CraftSubRes2.Init)
{
string nameString = craftSystem.CraftSubRes2.NameString;
int nameNumber = craftSystem.CraftSubRes2.NameNumber;
int resIndex = context?.LastResourceIndex2 ?? -1;
Type resourceType = craftSystem.CraftSubRes2.ResType;
if (resIndex > -1)
{
CraftSubRes subResource = craftSystem.CraftSubRes2.GetAt(resIndex);
nameString = subResource.NameString;
nameNumber = subResource.NameNumber;
resourceType = subResource.ItemType;
}
int resourceCount = 0;
if (from.Backpack != null)
{
Item[] items = from.Backpack.FindItemsByType(resourceType);
for (int i = 0; i < items.Length; ++i)
resourceCount += items[i].Amount;
}
AddButton(15, 382, 4005, 4007, GetButtonID(6, 7));
if (nameNumber > 0)
AddHtmlLocalized(50, 385, 250, 18, nameNumber, resourceCount.ToString(), LabelColor);
else
AddLabel(50, 385, LabelHue, $"{nameString} ({resourceCount} Available)");
}
// ****************************************
CreateGroupList();
if (page == CraftPage.PickResource)
CreateResList(false, from);
else if (page == CraftPage.PickResource2)
CreateResList(true, from);
else if (context?.LastGroupIndex > -1)
CreateItemList(context.LastGroupIndex);
}
public void CreateResList(bool opt, Mobile from)
{
CraftSubResCol res = opt ? m_CraftSystem.CraftSubRes2 : m_CraftSystem.CraftSubRes;
for (int i = 0; i < res.Count; ++i)
{
int index = i % 10;
CraftSubRes subResource = res.GetAt(i);
if (index == 0)
{
if (i > 0)
AddButton(485, 260, 4005, 4007, 0, GumpButtonType.Page, i / 10 + 1);
AddPage(i / 10 + 1);
if (i > 0)
AddButton(455, 260, 4014, 4015, 0, GumpButtonType.Page, i / 10);
CraftContext context = m_CraftSystem.GetContext(m_From);
AddButton(220, 260, 4005, 4007, GetButtonID(6, 4));
AddHtmlLocalized(255, 263, 200, 18, context == null || !context.DoNotColor ? 1061591 : 1061590,
LabelColor);
}
int resourceCount = 0;
if (from.Backpack != null)
{
Item[] items = from.Backpack.FindItemsByType(subResource.ItemType);
for (int j = 0; j < items.Length; ++j)
resourceCount += items[j].Amount;
}
AddButton(220, 60 + index * 20, 4005, 4007, GetButtonID(5, i));
if (subResource.NameNumber > 0)
AddHtmlLocalized(255, 63 + index * 20, 250, 18, subResource.NameNumber, resourceCount.ToString(),
LabelColor);
else
AddLabel(255, 60 + index * 20, LabelHue, $"{subResource.NameString} ({resourceCount})");
}
}
public void CreateMakeLastList()
{
CraftContext context = m_CraftSystem.GetContext(m_From);
if (context == null)
return;
List<CraftItem> items = context.Items;
if (items.Count > 0)
for (int i = 0; i < items.Count; ++i)
{
int index = i % 10;
CraftItem craftItem = items[i];
if (index == 0)
{
if (i > 0)
{
AddButton(370, 260, 4005, 4007, 0, GumpButtonType.Page, i / 10 + 1);
AddHtmlLocalized(405, 263, 100, 18, 1044045, LabelColor); // NEXT PAGE
}
AddPage(i / 10 + 1);
if (i > 0)
{
AddButton(220, 260, 4014, 4015, 0, GumpButtonType.Page, i / 10);
AddHtmlLocalized(255, 263, 100, 18, 1044044, LabelColor); // PREV PAGE
}
}
AddButton(220, 60 + index * 20, 4005, 4007, GetButtonID(3, i));
if (craftItem.NameNumber > 0)
AddHtmlLocalized(255, 63 + index * 20, 220, 18, craftItem.NameNumber, LabelColor);
else
AddLabel(255, 60 + index * 20, LabelHue, craftItem.NameString);
AddButton(480, 60 + index * 20, 4011, 4012, GetButtonID(4, i));
}
else
AddHtmlLocalized(230, 62, 200, 22, 1044165, LabelColor); // You haven't made anything yet.
}
public void CreateItemList(int selectedGroup)
{
if (selectedGroup == 501) // 501 : Last 10
{
CreateMakeLastList();
return;
}
CraftGroupCol craftGroupCol = m_CraftSystem.CraftGroups;
CraftGroup craftGroup = craftGroupCol.GetAt(selectedGroup);
CraftItemCol craftItemCol = craftGroup.CraftItems;
for (int i = 0; i < craftItemCol.Count; ++i)
{
int index = i % 10;
CraftItem craftItem = craftItemCol.GetAt(i);
if (index == 0)
{
if (i > 0)
{
AddButton(370, 260, 4005, 4007, 0, GumpButtonType.Page, i / 10 + 1);
AddHtmlLocalized(405, 263, 100, 18, 1044045, LabelColor); // NEXT PAGE
}
AddPage(i / 10 + 1);
if (i > 0)
{
AddButton(220, 260, 4014, 4015, 0, GumpButtonType.Page, i / 10);
AddHtmlLocalized(255, 263, 100, 18, 1044044, LabelColor); // PREV PAGE
}
}
AddButton(220, 60 + index * 20, 4005, 4007, GetButtonID(1, i));
if (craftItem.NameNumber > 0)
AddHtmlLocalized(255, 63 + index * 20, 220, 18, craftItem.NameNumber, LabelColor);
else
AddLabel(255, 60 + index * 20, LabelHue, craftItem.NameString);
AddButton(480, 60 + index * 20, 4011, 4012, GetButtonID(2, i));
}
}
public int CreateGroupList()
{
CraftGroupCol craftGroupCol = m_CraftSystem.CraftGroups;
AddButton(15, 60, 4005, 4007, GetButtonID(6, 3));
AddHtmlLocalized(50, 63, 150, 18, 1044014, LabelColor); // LAST TEN
for (int i = 0; i < craftGroupCol.Count; i++)
{
CraftGroup craftGroup = craftGroupCol.GetAt(i);
AddButton(15, 80 + i * 20, 4005, 4007, GetButtonID(0, i));
if (craftGroup.NameNumber > 0)
AddHtmlLocalized(50, 83 + i * 20, 150, 18, craftGroup.NameNumber, LabelColor);
else
AddLabel(50, 80 + i * 20, LabelHue, craftGroup.NameString);
}
return craftGroupCol.Count;
}
public static int GetButtonID(int type, int index)
{
return 1 + type + index * 7;
}
public void CraftItem(CraftItem item)
{
int num = m_CraftSystem.CanCraft(m_From, m_Tool, item.ItemType);
if (num > 0)
{
m_From.SendGump(new CraftGump(m_From, m_CraftSystem, m_Tool, num));
}
else
{
Type type = null;
CraftContext context = m_CraftSystem.GetContext(m_From);
if (context != null)
{
CraftSubResCol res = item.UseSubRes2 ? m_CraftSystem.CraftSubRes2 : m_CraftSystem.CraftSubRes;
int resIndex = item.UseSubRes2 ? context.LastResourceIndex2 : context.LastResourceIndex;
if (resIndex >= 0 && resIndex < res.Count)
type = res.GetAt(resIndex).ItemType;
}
m_CraftSystem.CreateItem(m_From, item.ItemType, type, m_Tool, item);
}
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (info.ButtonID <= 0)
return; // Canceled
int buttonID = info.ButtonID - 1;
int type = buttonID % 7;
int index = buttonID / 7;
CraftSystem system = m_CraftSystem;
CraftGroupCol groups = system.CraftGroups;
CraftContext context = system.GetContext(m_From);
switch (type)
{
case 0: // Show group
{
if (context == null)
break;
if (index >= 0 && index < groups.Count)
{
context.LastGroupIndex = index;
m_From.SendGump(new CraftGump(m_From, system, m_Tool, null));
}
break;
}
case 1: // Create item
{
if (context == null)
break;
int groupIndex = context.LastGroupIndex;
if (groupIndex >= 0 && groupIndex < groups.Count)
{
CraftGroup group = groups.GetAt(groupIndex);
if (index >= 0 && index < group.CraftItems.Count)
CraftItem(group.CraftItems.GetAt(index));
}
break;
}
case 2: // Item details
{
if (context == null)
break;
int groupIndex = context.LastGroupIndex;
if (groupIndex >= 0 && groupIndex < groups.Count)
{
CraftGroup group = groups.GetAt(groupIndex);
if (index >= 0 && index < group.CraftItems.Count)
m_From.SendGump(new CraftGumpItem(m_From, system, group.CraftItems.GetAt(index), m_Tool));
}
break;
}
case 3: // Create item (last 10)
{
if (context == null)
break;
List<CraftItem> lastTen = context.Items;
if (index >= 0 && index < lastTen.Count)
CraftItem(lastTen[index]);
break;
}
case 4: // Item details (last 10)
{
if (context == null)
break;
List<CraftItem> lastTen = context.Items;
if (index >= 0 && index < lastTen.Count)
m_From.SendGump(new CraftGumpItem(m_From, system, lastTen[index], m_Tool));
break;
}
case 5: // Resource selected
{
if (m_Page == CraftPage.PickResource && index >= 0 && index < system.CraftSubRes.Count)
{
CraftSubRes res = system.CraftSubRes.GetAt(index);
if (m_From.Skills[system.MainSkill].Base < res.RequiredSkill)
{
m_From.SendGump(new CraftGump(m_From, system, m_Tool, res.Message));
}
else
{
if (context != null)
context.LastResourceIndex = index;
m_From.SendGump(new CraftGump(m_From, system, m_Tool, null));
}
}
else if (m_Page == CraftPage.PickResource2 && index >= 0 && index < system.CraftSubRes2.Count)
{
CraftSubRes res = system.CraftSubRes2.GetAt(index);
if (m_From.Skills[system.MainSkill].Base < res.RequiredSkill)
{
m_From.SendGump(new CraftGump(m_From, system, m_Tool, res.Message));
}
else
{
if (context != null)
context.LastResourceIndex2 = index;
m_From.SendGump(new CraftGump(m_From, system, m_Tool, null));
}
}
break;
}
case 6: // Misc. buttons
{
switch (index)
{
case 0: // Resource selection
{
if (system.CraftSubRes.Init)
m_From.SendGump(new CraftGump(m_From, system, m_Tool, null, CraftPage.PickResource));
break;
}
case 1: // Smelt item
{
if (system.Resmelt)
Resmelt.Do(m_From, system, m_Tool);
break;
}
case 2: // Make last
{
if (context == null)
break;
CraftItem item = context.LastMade;
if (item != null)
CraftItem(item);
else
m_From.SendGump(new CraftGump(m_From, m_CraftSystem, m_Tool, 1044165,
m_Page)); // You haven't made anything yet.
break;
}
case 3: // Last 10
{
if (context == null)
break;
context.LastGroupIndex = 501;
m_From.SendGump(new CraftGump(m_From, system, m_Tool, null));
break;
}
case 4: // Toggle use resource hue
{
if (context == null)
break;
context.DoNotColor = !context.DoNotColor;
m_From.SendGump(new CraftGump(m_From, m_CraftSystem, m_Tool, null, m_Page));
break;
}
case 5: // Repair item
{
if (system.Repair)
Repair.Do(m_From, system, m_Tool);
break;
}
case 6: // Toggle mark option
{
if (context == null || !system.MarkOption)
break;
switch (context.MarkOption)
{
case CraftMarkOption.MarkItem:
context.MarkOption = CraftMarkOption.DoNotMark;
break;
case CraftMarkOption.DoNotMark:
context.MarkOption = CraftMarkOption.PromptForMark;
break;
case CraftMarkOption.PromptForMark:
context.MarkOption = CraftMarkOption.MarkItem;
break;
}
m_From.SendGump(new CraftGump(m_From, m_CraftSystem, m_Tool, null, m_Page));
break;
}
case 7: // Resource selection 2
{
if (system.CraftSubRes2.Init)
m_From.SendGump(new CraftGump(m_From, system, m_Tool, null, CraftPage.PickResource2));
break;
}
case 8: // Enhance item
{
if (system.CanEnhance)
Enhance.BeginTarget(m_From, system, m_Tool);
break;
}
}
break;
}
}
}
public enum CraftPage
{
None,
PickResource,
PickResource2
}
}
}

View file

@ -0,0 +1,289 @@
using System;
using Server.Gumps;
using Server.Items;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.Craft
{
public class CraftGumpItem : Gump
{
private const int LabelHue = 0x480; // 0x384
private const int RedLabelHue = 0x20;
private const int LabelColor = 0x7FFF;
private const int RedLabelColor = 0x6400;
private const int GreyLabelColor = 0x3DEF;
private static Type typeofBlankScroll = typeof(BlankScroll);
private static Type typeofSpellScroll = typeof(SpellScroll);
private CraftItem m_CraftItem;
private CraftSystem m_CraftSystem;
private Mobile m_From;
private int m_OtherCount;
private bool m_ShowExceptionalChance;
private BaseTool m_Tool;
public CraftGumpItem(Mobile from, CraftSystem craftSystem, CraftItem craftItem, BaseTool tool) : base(40, 40)
{
m_From = from;
m_CraftSystem = craftSystem;
m_CraftItem = craftItem;
m_Tool = tool;
from.CloseGump<CraftGump>();
from.CloseGump<CraftGumpItem>();
AddPage(0);
AddBackground(0, 0, 530, 417, 5054);
AddImageTiled(10, 10, 510, 22, 2624);
AddImageTiled(10, 37, 150, 148, 2624);
AddImageTiled(165, 37, 355, 90, 2624);
AddImageTiled(10, 190, 155, 22, 2624);
AddImageTiled(10, 217, 150, 53, 2624);
AddImageTiled(165, 132, 355, 80, 2624);
AddImageTiled(10, 275, 155, 22, 2624);
AddImageTiled(10, 302, 150, 53, 2624);
AddImageTiled(165, 217, 355, 80, 2624);
AddImageTiled(10, 360, 155, 22, 2624);
AddImageTiled(165, 302, 355, 80, 2624);
AddImageTiled(10, 387, 510, 22, 2624);
AddAlphaRegion(10, 10, 510, 399);
AddHtmlLocalized(170, 40, 150, 20, 1044053, LabelColor); // ITEM
AddHtmlLocalized(10, 192, 150, 22, 1044054, LabelColor); // <CENTER>SKILLS</CENTER>
AddHtmlLocalized(10, 277, 150, 22, 1044055, LabelColor); // <CENTER>MATERIALS</CENTER>
AddHtmlLocalized(10, 362, 150, 22, 1044056, LabelColor); // <CENTER>OTHER</CENTER>
if (craftSystem.GumpTitleNumber > 0)
AddHtmlLocalized(10, 12, 510, 20, craftSystem.GumpTitleNumber, LabelColor);
else
AddHtml(10, 12, 510, 20, craftSystem.GumpTitleString);
AddButton(15, 387, 4014, 4016, 0);
AddHtmlLocalized(50, 390, 150, 18, 1044150, LabelColor); // BACK
bool needsRecipe = craftItem.Recipe != null && from is PlayerMobile mobile &&
!mobile.HasRecipe(craftItem.Recipe);
if (needsRecipe)
{
AddButton(270, 387, 4005, 4007, 0, GumpButtonType.Page);
AddHtmlLocalized(305, 390, 150, 18, 1044151, GreyLabelColor); // MAKE NOW
}
else
{
AddButton(270, 387, 4005, 4007, 1);
AddHtmlLocalized(305, 390, 150, 18, 1044151, LabelColor); // MAKE NOW
}
if (craftItem.NameNumber > 0)
AddHtmlLocalized(330, 40, 180, 18, craftItem.NameNumber, LabelColor);
else
AddLabel(330, 40, LabelHue, craftItem.NameString);
if (craftItem.UseAllRes)
AddHtmlLocalized(170, 302 + m_OtherCount++ * 20, 310, 18, 1048176, LabelColor); // Makes as many as possible at once
DrawItem();
DrawSkill();
DrawResource();
/*
if ( craftItem.RequiresSE )
AddHtmlLocalized( 170, 302 + (m_OtherCount++ * 20), 310, 18, 1063363, LabelColor, false, false ); //* Requires the "Samurai Empire" expansion
* */
if (craftItem.RequiredExpansion != Expansion.None)
{
bool supportsEx = from.NetState?.SupportsExpansion(craftItem.RequiredExpansion) == true;
TextDefinition.AddHtmlText(this, 170, 302 + m_OtherCount++ * 20, 310, 18,
RequiredExpansionMessage(craftItem.RequiredExpansion), false, false,
supportsEx ? LabelColor : RedLabelColor, supportsEx ? LabelHue : RedLabelHue);
}
if (needsRecipe)
AddHtmlLocalized(170, 302 + m_OtherCount++ * 20, 310, 18, 1073620, RedLabelColor); // You have not learned this recipe.
}
private TextDefinition RequiredExpansionMessage(Expansion expansion)
{
switch (expansion)
{
case Expansion.SE:
return 1063363; // * Requires the "Samurai Empire" expansion
case Expansion.ML:
return 1072651; // * Requires the "Mondain's Legacy" expansion
default:
return $"* Requires the \"{ExpansionInfo.GetInfo(expansion).Name}\" expansion";
}
}
public void DrawItem()
{
Type type = m_CraftItem.ItemType;
AddItem(20, 50, CraftItem.ItemIDOf(type), m_CraftItem.ItemHue);
if (m_CraftItem.IsMarkable(type))
{
AddHtmlLocalized(170, 302 + m_OtherCount++ * 20, 310, 18, 1044059, LabelColor); // This item may hold its maker's mark
m_ShowExceptionalChance = true;
}
}
public void DrawSkill()
{
for (int i = 0; i < m_CraftItem.Skills.Count; i++)
{
CraftSkill skill = m_CraftItem.Skills.GetAt(i);
double minSkill = skill.MinSkill;
if (minSkill < 0)
minSkill = 0;
AddHtmlLocalized(170, 132 + i * 20, 200, 18, AosSkillBonuses.GetLabel(skill.SkillToMake), LabelColor);
AddLabel(430, 132 + i * 20, LabelHue, $"{minSkill:F1}");
}
CraftSubResCol res = m_CraftItem.UseSubRes2 ? m_CraftSystem.CraftSubRes2 : m_CraftSystem.CraftSubRes;
int resIndex = -1;
CraftContext context = m_CraftSystem.GetContext(m_From);
if (context != null)
resIndex = m_CraftItem.UseSubRes2 ? context.LastResourceIndex2 : context.LastResourceIndex;
bool allRequiredSkills = true;
double chance = m_CraftItem.GetSuccessChance(m_From, resIndex > -1 ? res.GetAt(resIndex).ItemType : null,
m_CraftSystem, false, ref allRequiredSkills);
double excepChance = m_CraftItem.GetExceptionalChance(m_CraftSystem, chance, m_From);
if (chance < 0.0)
chance = 0.0;
else if (chance > 1.0)
chance = 1.0;
AddHtmlLocalized(170, 80, 250, 18, 1044057, LabelColor); // Success Chance:
AddLabel(430, 80, LabelHue, $"{chance * 100:F1}%");
if (m_ShowExceptionalChance)
{
if (excepChance < 0.0)
excepChance = 0.0;
else if (excepChance > 1.0)
excepChance = 1.0;
AddHtmlLocalized(170, 100, 250, 18, 1044058, 32767); // Exceptional Chance:
AddLabel(430, 100, LabelHue, $"{excepChance * 100:F1}%");
}
}
public void DrawResource()
{
bool retainedColor = false;
CraftContext context = m_CraftSystem.GetContext(m_From);
CraftSubResCol res = m_CraftItem.UseSubRes2 ? m_CraftSystem.CraftSubRes2 : m_CraftSystem.CraftSubRes;
int resIndex = -1;
if (context != null)
resIndex = m_CraftItem.UseSubRes2 ? context.LastResourceIndex2 : context.LastResourceIndex;
bool cropScroll = m_CraftItem.Resources.Count > 1
&& m_CraftItem.Resources.GetAt(m_CraftItem.Resources.Count - 1).ItemType == typeofBlankScroll
&& typeofSpellScroll.IsAssignableFrom(m_CraftItem.ItemType);
for (int i = 0; i < m_CraftItem.Resources.Count - (cropScroll ? 1 : 0) && i < 4; i++)
{
Type type;
string nameString;
int nameNumber;
CraftRes craftResource = m_CraftItem.Resources.GetAt(i);
type = craftResource.ItemType;
nameString = craftResource.NameString;
nameNumber = craftResource.NameNumber;
// Resource Mutation
if (type == res.ResType && resIndex > -1)
{
CraftSubRes subResource = res.GetAt(resIndex);
type = subResource.ItemType;
nameString = subResource.NameString;
nameNumber = subResource.GenericNameNumber;
if (nameNumber <= 0)
nameNumber = subResource.NameNumber;
}
// ******************
if (!retainedColor && m_CraftItem.RetainsColorFrom(m_CraftSystem, type))
{
retainedColor = true;
AddHtmlLocalized(170, 302 + m_OtherCount++ * 20, 310, 18, 1044152, LabelColor); // * The item retains the color of this material
AddLabel(500, 219 + i * 20, LabelHue, "*");
}
if (nameNumber > 0)
AddHtmlLocalized(170, 219 + i * 20, 310, 18, nameNumber, LabelColor);
else
AddLabel(170, 219 + i * 20, LabelHue, nameString);
AddLabel(430, 219 + i * 20, LabelHue, craftResource.Amount.ToString());
}
if (m_CraftItem.NameNumber == 1041267) // runebook
{
AddHtmlLocalized(170, 219 + m_CraftItem.Resources.Count * 20, 310, 18, 1044447, LabelColor);
AddLabel(430, 219 + m_CraftItem.Resources.Count * 20, LabelHue, "1");
}
if (cropScroll)
AddHtmlLocalized(170, 302 + m_OtherCount++ * 20, 360, 18, 1044379, LabelColor); // Inscribing scrolls also requires a blank scroll and mana.
}
public override void OnResponse(NetState sender, RelayInfo info)
{
// Back Button
if (info.ButtonID == 0)
{
CraftGump craftGump = new CraftGump(m_From, m_CraftSystem, m_Tool, null);
m_From.SendGump(craftGump);
}
else // Make Button
{
int num = m_CraftSystem.CanCraft(m_From, m_Tool, m_CraftItem.ItemType);
if (num > 0)
{
m_From.SendGump(new CraftGump(m_From, m_CraftSystem, m_Tool, num));
}
else
{
Type type = null;
CraftContext context = m_CraftSystem.GetContext(m_From);
if (context != null)
{
CraftSubResCol res = m_CraftItem.UseSubRes2 ? m_CraftSystem.CraftSubRes2 : m_CraftSystem.CraftSubRes;
int resIndex = m_CraftItem.UseSubRes2 ? context.LastResourceIndex2 : context.LastResourceIndex;
if (resIndex > -1)
type = res.GetAt(resIndex).ItemType;
}
m_CraftSystem.CreateItem(m_From, m_CraftItem.ItemType, type, m_Tool, m_CraftItem);
}
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,53 @@
using System;
using System.Collections;
namespace Server.Engines.Craft
{
public class CraftItemCol : CollectionBase
{
public int Add(CraftItem craftItem)
{
return List.Add(craftItem);
}
public void Remove(int index)
{
if (index > Count - 1 || index < 0)
{
}
else
{
List.RemoveAt(index);
}
}
public CraftItem GetAt(int index)
{
return (CraftItem)List[index];
}
public CraftItem SearchForSubclass(Type type)
{
for (int i = 0; i < List.Count; i++)
{
CraftItem craftItem = (CraftItem)List[i];
if (craftItem.ItemType == type || type.IsSubclassOf(craftItem.ItemType))
return craftItem;
}
return null;
}
public CraftItem SearchFor(Type type)
{
for (int i = 0; i < List.Count; i++)
{
CraftItem craftItem = (CraftItem)List[i];
if (craftItem.ItemType == type) return craftItem;
}
return null;
}
}
}

View file

@ -0,0 +1,15 @@
using System;
namespace Server.Engines.Craft
{
[AttributeUsage(AttributeTargets.Class)]
public class CraftItemIDAttribute : Attribute
{
public CraftItemIDAttribute(int itemID)
{
ItemID = itemID;
}
public int ItemID{ get; }
}
}

View file

@ -0,0 +1,41 @@
using System;
namespace Server.Engines.Craft
{
public class CraftRes
{
public CraftRes(Type type, TextDefinition name, int amount, TextDefinition message = null)
{
ItemType = type;
Amount = amount;
NameNumber = name;
MessageNumber = message;
NameString = name;
MessageString = message;
}
public Type ItemType{ get; }
public string MessageString{ get; }
public int MessageNumber{ get; }
public string NameString{ get; }
public int NameNumber{ get; }
public int Amount{ get; }
public void SendMessage(Mobile from)
{
if (MessageNumber > 0)
from.SendLocalizedMessage(MessageNumber);
else if (!string.IsNullOrEmpty(MessageString))
from.SendMessage(MessageString);
else
from.SendLocalizedMessage(502925); // You don't have the resources required to make that item.
}
}
}

View file

@ -0,0 +1,28 @@
using System.Collections;
namespace Server.Engines.Craft
{
public class CraftResCol : CollectionBase
{
public void Add(CraftRes craftRes)
{
List.Add(craftRes);
}
public void Remove(int index)
{
if (index > Count - 1 || index < 0)
{
}
else
{
List.RemoveAt(index);
}
}
public CraftRes GetAt(int index)
{
return (CraftRes)List[index];
}
}
}

View file

@ -0,0 +1,18 @@
namespace Server.Engines.Craft
{
public class CraftSkill
{
public CraftSkill(SkillName skillToMake, double minSkill, double maxSkill)
{
SkillToMake = skillToMake;
MinSkill = minSkill;
MaxSkill = maxSkill;
}
public SkillName SkillToMake{ get; }
public double MinSkill{ get; }
public double MaxSkill{ get; }
}
}

View file

@ -0,0 +1,28 @@
using System.Collections;
namespace Server.Engines.Craft
{
public class CraftSkillCol : CollectionBase
{
public void Add(CraftSkill craftSkill)
{
List.Add(craftSkill);
}
public void Remove(int index)
{
if (index > Count - 1 || index < 0)
{
}
else
{
List.RemoveAt(index);
}
}
public CraftSkill GetAt(int index)
{
return (CraftSkill)List[index];
}
}
}

View file

@ -0,0 +1,34 @@
using System;
namespace Server.Engines.Craft
{
public class CraftSubRes
{
public CraftSubRes(Type type, TextDefinition name, double reqSkill, object message) : this(type, name, reqSkill, 0,
message)
{
}
public CraftSubRes(Type type, TextDefinition name, double reqSkill, int genericNameNumber, object message)
{
ItemType = type;
NameNumber = name;
NameString = name;
RequiredSkill = reqSkill;
GenericNameNumber = genericNameNumber;
Message = message;
}
public Type ItemType{ get; }
public string NameString{ get; }
public int NameNumber{ get; }
public int GenericNameNumber{ get; }
public object Message{ get; }
public double RequiredSkill{ get; }
}
}

View file

@ -0,0 +1,53 @@
using System;
using System.Collections;
namespace Server.Engines.Craft
{
public class CraftSubResCol : CollectionBase
{
public CraftSubResCol()
{
Init = false;
}
public bool Init{ get; set; }
public Type ResType{ get; set; }
public string NameString{ get; set; }
public int NameNumber{ get; set; }
public void Add(CraftSubRes craftSubRes)
{
List.Add(craftSubRes);
}
public void Remove(int index)
{
if (index > Count - 1 || index < 0)
{
}
else
{
List.RemoveAt(index);
}
}
public CraftSubRes GetAt(int index)
{
return (CraftSubRes)List[index];
}
public CraftSubRes SearchFor(Type type)
{
for (int i = 0; i < List.Count; i++)
{
CraftSubRes craftSubRes = (CraftSubRes)List[i];
if (craftSubRes.ItemType == type) return craftSubRes;
}
return null;
}
}
}

View file

@ -0,0 +1,358 @@
using System;
using System.Collections.Generic;
using Server.Items;
namespace Server.Engines.Craft
{
public enum CraftECA
{
ChanceMinusSixty,
FiftyPercentChanceMinusTenPercent,
ChanceMinusSixtyToFourtyFive
}
public abstract class CraftSystem
{
private Dictionary<Mobile, CraftContext> m_ContextTable = new Dictionary<Mobile, CraftContext>();
private List<int> m_RareRecipes;
private List<int> m_Recipes;
public CraftSystem(int minCraftEffect, int maxCraftEffect, double delay)
{
MinCraftEffect = minCraftEffect;
MaxCraftEffect = maxCraftEffect;
Delay = delay;
CraftItems = new CraftItemCol();
CraftGroups = new CraftGroupCol();
CraftSubRes = new CraftSubResCol();
CraftSubRes2 = new CraftSubResCol();
m_Recipes = new List<int>();
m_RareRecipes = new List<int>();
InitCraftList();
}
public int MinCraftEffect{ get; }
public int MaxCraftEffect{ get; }
public double Delay{ get; }
public CraftItemCol CraftItems{ get; }
public CraftGroupCol CraftGroups{ get; }
public CraftSubResCol CraftSubRes{ get; }
public CraftSubResCol CraftSubRes2{ get; }
public abstract SkillName MainSkill{ get; }
public virtual int GumpTitleNumber => 0;
public virtual string GumpTitleString => "";
public virtual CraftECA ECA => CraftECA.ChanceMinusSixty;
public bool Resmelt{ get; set; }
public bool Repair{ get; set; }
public bool MarkOption{ get; set; }
public bool CanEnhance{ get; set; }
public abstract double GetChanceAtMin(CraftItem item);
public virtual bool RetainsColorFrom(CraftItem item, Type type)
{
return false;
}
public CraftContext GetContext(Mobile m)
{
if (m == null)
return null;
if (m.Deleted)
{
m_ContextTable.Remove(m);
return null;
}
if (!m_ContextTable.TryGetValue(m, out CraftContext c))
m_ContextTable[m] = c = new CraftContext();
return c;
}
public void OnMade(Mobile m, CraftItem item)
{
GetContext(m)?.OnMade(item);
}
public virtual bool ConsumeOnFailure(Mobile from, Type resourceType, CraftItem craftItem)
{
return true;
}
public void CreateItem(Mobile from, Type type, Type typeRes, BaseTool tool, CraftItem realCraftItem)
{
// Verify if the type is in the list of the craftable item
if (CraftItems.SearchFor(type) != null)
realCraftItem.Craft(from, this, typeRes, tool);
}
public int RandomRecipe()
{
if (m_Recipes.Count == 0)
return -1;
return m_Recipes[Utility.Random(m_Recipes.Count)];
}
public int RandomRareRecipe()
{
if (m_RareRecipes.Count == 0)
return -1;
return m_RareRecipes[Utility.Random(m_RareRecipes.Count)];
}
public int AddCraft(Type typeItem, TextDefinition group, TextDefinition name, double minSkill, double maxSkill,
Type typeRes, TextDefinition nameRes, int amount)
{
return AddCraft(typeItem, group, name, MainSkill, minSkill, maxSkill, typeRes, nameRes, amount, "");
}
public int AddCraft(Type typeItem, TextDefinition group, TextDefinition name, double minSkill, double maxSkill,
Type typeRes, TextDefinition nameRes, int amount, TextDefinition message)
{
return AddCraft(typeItem, group, name, MainSkill, minSkill, maxSkill, typeRes, nameRes, amount, message);
}
public int AddCraft(Type typeItem, TextDefinition group, TextDefinition name, SkillName skillToMake, double minSkill,
double maxSkill, Type typeRes, TextDefinition nameRes, int amount)
{
return AddCraft(typeItem, group, name, skillToMake, minSkill, maxSkill, typeRes, nameRes, amount, "");
}
public int AddCraft(Type typeItem, TextDefinition group, TextDefinition name, SkillName skillToMake, double minSkill,
double maxSkill, Type typeRes, TextDefinition nameRes, int amount, TextDefinition message)
{
CraftItem craftItem = new CraftItem(typeItem, group, name);
craftItem.AddRes(typeRes, nameRes, amount, message);
craftItem.AddSkill(skillToMake, minSkill, maxSkill);
DoGroup(group, craftItem);
return CraftItems.Add(craftItem);
}
private void DoGroup(TextDefinition groupName, CraftItem craftItem)
{
int index = CraftGroups.SearchFor(groupName);
if (index == -1)
{
CraftGroup craftGroup = new CraftGroup(groupName);
craftGroup.AddCraftItem(craftItem);
CraftGroups.Add(craftGroup);
}
else
{
CraftGroups.GetAt(index).AddCraftItem(craftItem);
}
}
public void SetItemHue(int index, int hue)
{
CraftItem craftItem = CraftItems.GetAt(index);
craftItem.ItemHue = hue;
}
public void SetManaReq(int index, int mana)
{
CraftItem craftItem = CraftItems.GetAt(index);
craftItem.Mana = mana;
}
public void SetStamReq(int index, int stam)
{
CraftItem craftItem = CraftItems.GetAt(index);
craftItem.Stam = stam;
}
public void SetHitsReq(int index, int hits)
{
CraftItem craftItem = CraftItems.GetAt(index);
craftItem.Hits = hits;
}
public void SetUseAllRes(int index, bool useAll)
{
CraftItem craftItem = CraftItems.GetAt(index);
craftItem.UseAllRes = useAll;
}
public void SetNeedHeat(int index, bool needHeat)
{
CraftItem craftItem = CraftItems.GetAt(index);
craftItem.NeedHeat = needHeat;
}
public void SetNeedOven(int index, bool needOven)
{
CraftItem craftItem = CraftItems.GetAt(index);
craftItem.NeedOven = needOven;
}
public void SetBeverageType(int index, BeverageType requiredBeverage)
{
CraftItem craftItem = CraftItems.GetAt(index);
craftItem.RequiredBeverage = requiredBeverage;
}
public void SetNeedMill(int index, bool needMill)
{
CraftItem craftItem = CraftItems.GetAt(index);
craftItem.NeedMill = needMill;
}
public void SetNeededExpansion(int index, Expansion expansion)
{
CraftItem craftItem = CraftItems.GetAt(index);
craftItem.RequiredExpansion = expansion;
}
public void AddRes(int index, Type type, TextDefinition name, int amount)
{
AddRes(index, type, name, amount, "");
}
public void AddRes(int index, Type type, TextDefinition name, int amount, TextDefinition message)
{
CraftItem craftItem = CraftItems.GetAt(index);
craftItem.AddRes(type, name, amount, message);
}
public void AddSkill(int index, SkillName skillToMake, double minSkill, double maxSkill)
{
CraftItem craftItem = CraftItems.GetAt(index);
craftItem.AddSkill(skillToMake, minSkill, maxSkill);
}
public void SetUseSubRes2(int index, bool val)
{
CraftItem craftItem = CraftItems.GetAt(index);
craftItem.UseSubRes2 = val;
}
private void AddRecipeBase(int index, int id)
{
CraftItem craftItem = CraftItems.GetAt(index);
craftItem.AddRecipe(id, this);
}
public void AddRecipe(int index, int id)
{
AddRecipeBase(index, id);
m_Recipes.Add(id);
}
public void AddRareRecipe(int index, int id)
{
AddRecipeBase(index, id);
m_RareRecipes.Add(id);
}
public void AddQuestRecipe(int index, int id)
{
AddRecipeBase(index, id);
}
public void ForceNonExceptional(int index)
{
CraftItem craftItem = CraftItems.GetAt(index);
craftItem.ForceNonExceptional = true;
}
public void SetSubRes(Type type, string name)
{
CraftSubRes.ResType = type;
CraftSubRes.NameString = name;
CraftSubRes.Init = true;
}
public void SetSubRes(Type type, int name)
{
CraftSubRes.ResType = type;
CraftSubRes.NameNumber = name;
CraftSubRes.Init = true;
}
public void AddSubRes(Type type, int name, double reqSkill, object message)
{
CraftSubRes craftSubRes = new CraftSubRes(type, name, reqSkill, message);
CraftSubRes.Add(craftSubRes);
}
public void AddSubRes(Type type, int name, double reqSkill, int genericName, object message)
{
CraftSubRes craftSubRes = new CraftSubRes(type, name, reqSkill, genericName, message);
CraftSubRes.Add(craftSubRes);
}
public void AddSubRes(Type type, string name, double reqSkill, object message)
{
CraftSubRes craftSubRes = new CraftSubRes(type, name, reqSkill, message);
CraftSubRes.Add(craftSubRes);
}
public void SetSubRes2(Type type, string name)
{
CraftSubRes2.ResType = type;
CraftSubRes2.NameString = name;
CraftSubRes2.Init = true;
}
public void SetSubRes2(Type type, int name)
{
CraftSubRes2.ResType = type;
CraftSubRes2.NameNumber = name;
CraftSubRes2.Init = true;
}
public void AddSubRes2(Type type, int name, double reqSkill, object message)
{
CraftSubRes craftSubRes = new CraftSubRes(type, name, reqSkill, message);
CraftSubRes2.Add(craftSubRes);
}
public void AddSubRes2(Type type, int name, double reqSkill, int genericName, object message)
{
CraftSubRes craftSubRes = new CraftSubRes(type, name, reqSkill, genericName, message);
CraftSubRes2.Add(craftSubRes);
}
public void AddSubRes2(Type type, string name, double reqSkill, object message)
{
CraftSubRes craftSubRes = new CraftSubRes(type, name, reqSkill, message);
CraftSubRes2.Add(craftSubRes);
}
public abstract void InitCraftList();
public abstract void PlayCraftEffect(Mobile from);
public abstract int PlayEndingEffect(Mobile from, bool failed, bool lostMaterial, bool toolBroken, int quality,
bool makersMark, CraftItem item);
public abstract int CanCraft(Mobile from, BaseTool tool, Type itemType);
}
}

View file

@ -0,0 +1,34 @@
using System;
using Server.Items;
namespace Server.Engines.Craft
{
public abstract class CustomCraft
{
public CustomCraft(Mobile from, CraftItem craftItem, CraftSystem craftSystem, Type typeRes, BaseTool tool,
int quality)
{
From = from;
CraftItem = craftItem;
CraftSystem = craftSystem;
TypeRes = typeRes;
Tool = tool;
Quality = quality;
}
public Mobile From{ get; }
public CraftItem CraftItem{ get; }
public CraftSystem CraftSystem{ get; }
public Type TypeRes{ get; }
public BaseTool Tool{ get; }
public int Quality{ get; }
public abstract void EndCraftAction();
public abstract Item CompleteCraft(out int message);
}
}

View file

@ -0,0 +1,340 @@
using System;
using Server.Items;
using Server.Targeting;
namespace Server.Engines.Craft
{
public enum EnhanceResult
{
None,
NotInBackpack,
BadItem,
BadResource,
AlreadyEnhanced,
Success,
Failure,
Broken,
NoResources,
NoSkill
}
public class Enhance
{
public static EnhanceResult Invoke(Mobile from, CraftSystem craftSystem, BaseTool tool, Item item,
CraftResource resource, Type resType, ref object resMessage)
{
if (item == null)
return EnhanceResult.BadItem;
if (!item.IsChildOf(from.Backpack))
return EnhanceResult.NotInBackpack;
if (!(item is BaseArmor) && !(item is BaseWeapon))
return EnhanceResult.BadItem;
if (item is IArcaneEquip eq && eq.IsArcane)
return EnhanceResult.BadItem;
if (CraftResources.IsStandard(resource))
return EnhanceResult.BadResource;
int num = craftSystem.CanCraft(from, tool, item.GetType());
if (num > 0)
{
resMessage = num;
return EnhanceResult.None;
}
CraftItem craftItem = craftSystem.CraftItems.SearchFor(item.GetType());
if (craftItem == null || craftItem.Resources.Count == 0)
return EnhanceResult.BadItem;
bool allRequiredSkills = false;
if (craftItem.GetSuccessChance(from, resType, craftSystem, false, ref allRequiredSkills) <= 0.0)
return EnhanceResult.NoSkill;
CraftResourceInfo info = CraftResources.GetInfo(resource);
if (info == null || info.ResourceTypes.Length == 0)
return EnhanceResult.BadResource;
CraftAttributeInfo attributes = info.AttributeInfo;
if (attributes == null)
return EnhanceResult.BadResource;
int resHue = 0, maxAmount = 0;
if (!craftItem.ConsumeRes(from, resType, craftSystem, ref resHue, ref maxAmount, ConsumeType.None,
ref resMessage))
return EnhanceResult.NoResources;
if (craftSystem is DefBlacksmithy)
if (from.FindItemOnLayer(Layer.OneHanded) is AncientSmithyHammer hammer)
{
hammer.UsesRemaining--;
if (hammer.UsesRemaining < 1)
hammer.Delete();
}
int phys = 0, fire = 0, cold = 0, pois = 0, nrgy = 0;
int dura, luck, lreq, dinc = 0;
int baseChance;
bool physBonus = false;
bool fireBonus;
bool coldBonus;
bool nrgyBonus;
bool poisBonus;
bool duraBonus;
bool luckBonus;
bool lreqBonus;
bool dincBonus;
if (item is BaseWeapon weapon)
{
if (!CraftResources.IsStandard(weapon.Resource))
return EnhanceResult.AlreadyEnhanced;
baseChance = 20;
dura = weapon.MaxHitPoints;
luck = weapon.Attributes.Luck;
lreq = weapon.WeaponAttributes.LowerStatReq;
dinc = weapon.Attributes.WeaponDamage;
fireBonus = attributes.WeaponFireDamage > 0;
coldBonus = attributes.WeaponColdDamage > 0;
nrgyBonus = attributes.WeaponEnergyDamage > 0;
poisBonus = attributes.WeaponPoisonDamage > 0;
duraBonus = attributes.WeaponDurability > 0;
luckBonus = attributes.WeaponLuck > 0;
lreqBonus = attributes.WeaponLowerRequirements > 0;
dincBonus = dinc > 0;
}
else
{
BaseArmor armor = (BaseArmor)item;
if (!CraftResources.IsStandard(armor.Resource))
return EnhanceResult.AlreadyEnhanced;
baseChance = 20;
phys = armor.PhysicalResistance;
fire = armor.FireResistance;
cold = armor.ColdResistance;
pois = armor.PoisonResistance;
nrgy = armor.EnergyResistance;
dura = armor.MaxHitPoints;
luck = armor.Attributes.Luck;
lreq = armor.ArmorAttributes.LowerStatReq;
physBonus = attributes.ArmorPhysicalResist > 0;
fireBonus = attributes.ArmorFireResist > 0;
coldBonus = attributes.ArmorColdResist > 0;
nrgyBonus = attributes.ArmorEnergyResist > 0;
poisBonus = attributes.ArmorPoisonResist > 0;
duraBonus = attributes.ArmorDurability > 0;
luckBonus = attributes.ArmorLuck > 0;
lreqBonus = attributes.ArmorLowerRequirements > 0;
dincBonus = false;
}
int skill = from.Skills[craftSystem.MainSkill].Fixed / 10;
if (skill >= 100)
baseChance -= (skill - 90) / 10;
EnhanceResult res = EnhanceResult.Success;
if (physBonus)
CheckResult(ref res, baseChance + phys);
if (fireBonus)
CheckResult(ref res, baseChance + fire);
if (coldBonus)
CheckResult(ref res, baseChance + cold);
if (nrgyBonus)
CheckResult(ref res, baseChance + nrgy);
if (poisBonus)
CheckResult(ref res, baseChance + pois);
if (duraBonus)
CheckResult(ref res, baseChance + dura / 40);
if (luckBonus)
CheckResult(ref res, baseChance + 10 + luck / 2);
if (lreqBonus)
CheckResult(ref res, baseChance + lreq / 4);
if (dincBonus)
CheckResult(ref res, baseChance + dinc / 4);
switch (res)
{
case EnhanceResult.Broken:
{
if (!craftItem.ConsumeRes(from, resType, craftSystem, ref resHue, ref maxAmount, ConsumeType.Half,
ref resMessage))
return EnhanceResult.NoResources;
item.Delete();
break;
}
case EnhanceResult.Success:
{
if (!craftItem.ConsumeRes(from, resType, craftSystem, ref resHue, ref maxAmount, ConsumeType.All,
ref resMessage))
return EnhanceResult.NoResources;
if (item is BaseWeapon w)
{
w.Resource = resource;
int hue = w.GetElementalDamageHue();
if (hue > 0)
w.Hue = hue;
}
else
{
((BaseArmor)item).Resource = resource;
}
break;
}
case EnhanceResult.Failure:
{
if (!craftItem.ConsumeRes(from, resType, craftSystem, ref resHue, ref maxAmount, ConsumeType.Half,
ref resMessage))
return EnhanceResult.NoResources;
break;
}
}
return res;
}
public static void CheckResult(ref EnhanceResult res, int chance)
{
if (res != EnhanceResult.Success)
return; // we've already failed..
int random = Utility.Random(100);
if (10 > random)
res = EnhanceResult.Failure;
else if (chance > random)
res = EnhanceResult.Broken;
}
public static void BeginTarget(Mobile from, CraftSystem craftSystem, BaseTool tool)
{
CraftContext context = craftSystem.GetContext(from);
if (context == null)
return;
int lastRes = context.LastResourceIndex;
CraftSubResCol subRes = craftSystem.CraftSubRes;
if (lastRes >= 0 && lastRes < subRes.Count)
{
CraftSubRes res = subRes.GetAt(lastRes);
if (from.Skills[craftSystem.MainSkill].Value < res.RequiredSkill)
{
from.SendGump(new CraftGump(from, craftSystem, tool, res.Message));
}
else
{
CraftResource resource = CraftResources.GetFromType(res.ItemType);
if (resource != CraftResource.None)
{
from.Target = new InternalTarget(craftSystem, tool, res.ItemType, resource);
from.SendLocalizedMessage(
1061004); // Target an item to enhance with the properties of your selected material.
}
else
{
from.SendGump(new CraftGump(from, craftSystem, tool,
1061010)); // You must select a special material in order to enhance an item with its properties.
}
}
}
else
{
from.SendGump(new CraftGump(from, craftSystem, tool,
1061010)); // You must select a special material in order to enhance an item with its properties.
}
}
private class InternalTarget : Target
{
private CraftSystem m_CraftSystem;
private CraftResource m_Resource;
private Type m_ResourceType;
private BaseTool m_Tool;
public InternalTarget(CraftSystem craftSystem, BaseTool tool, Type resourceType, CraftResource resource) : base(
2, false, TargetFlags.None)
{
m_CraftSystem = craftSystem;
m_Tool = tool;
m_ResourceType = resourceType;
m_Resource = resource;
}
protected override void OnTarget(Mobile from, object targeted)
{
if (targeted is Item item)
{
object message = null;
EnhanceResult res = Enhance.Invoke(from, m_CraftSystem, m_Tool, item, m_Resource, m_ResourceType,
ref message);
switch (res)
{
case EnhanceResult.NotInBackpack:
message = 1061005;
break; // The item must be in your backpack to enhance it.
case EnhanceResult.AlreadyEnhanced:
message = 1061012;
break; // This item is already enhanced with the properties of a special material.
case EnhanceResult.BadItem:
message = 1061011;
break; // You cannot enhance this type of item with the properties of the selected special material.
case EnhanceResult.BadResource:
message = 1061010;
break; // You must select a special material in order to enhance an item with its properties.
case EnhanceResult.Broken:
message = 1061080;
break; // You attempt to enhance the item, but fail catastrophically. The item is lost.
case EnhanceResult.Failure:
message = 1061082;
break; // You attempt to enhance the item, but fail. Some material is lost in the process.
case EnhanceResult.Success:
message = 1061008;
break; // You enhance the item with the properties of the special material.
case EnhanceResult.NoSkill:
message = 1044153;
break; // You don't have the required skills to attempt this item.
}
from.SendGump(new CraftGump(from, m_CraftSystem, m_Tool, message));
}
}
}
}
}

View file

@ -0,0 +1,55 @@
using System;
using Server.Gumps;
using Server.Items;
using Server.Network;
namespace Server.Engines.Craft
{
public class QueryMakersMarkGump : Gump
{
private CraftItem m_CraftItem;
private CraftSystem m_CraftSystem;
private Mobile m_From;
private int m_Quality;
private BaseTool m_Tool;
private Type m_TypeRes;
public QueryMakersMarkGump(int quality, Mobile from, CraftItem craftItem, CraftSystem craftSystem, Type typeRes,
BaseTool tool) : base(100, 200)
{
from.CloseGump<QueryMakersMarkGump>();
m_Quality = quality;
m_From = from;
m_CraftItem = craftItem;
m_CraftSystem = craftSystem;
m_TypeRes = typeRes;
m_Tool = tool;
AddPage(0);
AddBackground(0, 0, 220, 170, 5054);
AddBackground(10, 10, 200, 150, 3000);
AddHtmlLocalized(20, 20, 180, 80, 1018317); // Do you wish to place your maker's mark on this item?
AddHtmlLocalized(55, 100, 140, 25, 1011011); // CONTINUE
AddButton(20, 100, 4005, 4007, 1);
AddHtmlLocalized(55, 125, 140, 25, 1011012); // CANCEL
AddButton(20, 125, 4005, 4007, 0);
}
public override void OnResponse(NetState sender, RelayInfo info)
{
bool makersMark = info.ButtonID == 1;
if (makersMark)
m_From.SendLocalizedMessage(501808); // You mark the item.
else
m_From.SendLocalizedMessage(501809); // Cancelled mark.
m_CraftItem.CompleteCraft(m_Quality, makersMark, m_From, m_CraftSystem, m_TypeRes, m_Tool, null);
}
}
}

Some files were not shown because too many files have changed in this diff Show more