Files
Unstable Kitsune b918192e4e Overwrite
Complete Overwrite of the Folder with the free shard. ServUO 57.3 has been added.
2023-11-28 23:20:26 -05:00

834 lines
28 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Server;
using Server.Mobiles;
using Server.ContextMenus;
using Server.Engines.Points;
using Server.Engines;
using Server.Items;
using Server.Multis;
using Server.Regions;
using Server.Engines.SeasonalEvents;
namespace Server.Engines.CityLoyalty
{
public enum TradeTitle
{
Trader = 1151739,
Exporter = 1151741,
Broker = 1151743,
Tycoon = 1151745,
Smuggler = 1151747,
Magnate = 1155481
}
public class CityTradeSystem : PointsSystem
{
public static readonly int TurnInGold = Config.Get("CityTrading.TurnInGold", 10000);
public static readonly int CrateDuration = Config.Get("CityTrading.CrateDuration", 24);
public static readonly int AmbushWaitDuration = Config.Get("CityTrading.AmbushWaitDuration", 5);
public static readonly int AmbusherDelete = Config.Get("CityTrading.AmbusherDelete", 10);
public override TextDefinition Name { get { return new TextDefinition("City Trading"); } }
public override PointsType Loyalty { get { return PointsType.CityTrading; } }
public override bool AutoAdd { get { return false; } }
public override double MaxPoints { get { return double.MaxValue; } }
public override bool ShowOnLoyaltyGump { get { return false; } }
public static bool KrampusEncounterActive { get { return KrampusEncounter.Enabled && KrampusEncounter.Encounter != null; } }
public static Dictionary<Mobile, TradeOrderCrate> ActiveTrades { get; private set; }
public static Dictionary<BaseCreature, DateTime> Ambushers { get; private set; }
public CityTradeSystem()
{
ActiveTrades = new Dictionary<Mobile, TradeOrderCrate>();
_NameBuffer = new Dictionary<Type, string>();
}
public override PointsEntry GetSystemEntry(PlayerMobile pm)
{
return new CityTradeEntry(pm);
}
public int GetMaxTrades(Mobile m)
{
CityTradeEntry entry = GetPlayerEntry<CityTradeEntry>(m as PlayerMobile);
if(entry == null)
return 1;
return Math.Min(8, Math.Max(1, (entry.Completed / 25) + 1));
}
public static bool HasTrade(Mobile from)
{
return ActiveTrades.ContainsKey(from);
}
public bool HasTurnIn(Mobile from, TradeMinister minister)
{
if(from == null || minister == null || !ActiveTrades.ContainsKey(from) || ActiveTrades[from] == null)
return false;
TradeOrderCrate crate = ActiveTrades[from];
return crate.Entry != null && crate.Entry.Origin != minister.City;
}
public bool TryOfferTrade(Mobile from, TradeMinister minister)
{
if(from == null || from.Backpack == null)
return true;
if (ActiveTrades.ContainsKey(from))
{
minister.SayTo(from, 1151722); // It appears you are already delivering a trade order. Deliver your current order before requesting another.
}
else if (KrampusEncounterActive && (KrampusEncounter.Encounter.Krampus != null || KrampusEncounter.Encounter.KrampusSpawning))
{
var p = KrampusEncounter.Encounter.SpawnLocation;
var map = KrampusEncounter.Encounter.SpawnMap;
minister.SayTo(
from,
1158790,
String.Format("{0}\t{1}",
WorldLocationInfo.GetLocationString(p, map),
Sextant.GetCoords(p, map)), 1150);
// Take notice! The vile Krampus has been spotted near ~2_where~ at ~1_coords~! New Trade Orders are suspended until Krampus has been defeated!
}
else
{
City origin = minister.City;
City destination;
do
{
destination = CityLoyaltySystem.GetRandomCity();
}
while (destination == origin);
int distance = GetDistance(minister, destination);
int trades = Utility.RandomMinMax(1, GetMaxTrades(from));
TradeEntry entry = new TradeEntry(destination, origin, distance);
TradeOrderCrate crate = new TradeOrderCrate(from, entry);
GetPlayerEntry<CityTradeEntry>(from as PlayerMobile, true);
for (int i = 0; i < trades; i++)
{
int worth = 1;
string name = null;
Type t = GetRandomTrade(origin, destination, ref worth, ref name);
if (t != null)
{
int amount = Utility.RandomList(5, 10, 15, 20);
entry.Details.Add(new TradeEntry.TradeDetails(t, worth, amount, name));
}
else
{
minister.SayTo(from, "There are no trades available at this time.");
return false;
}
}
if (from.Backpack == null || !from.Backpack.TryDropItem(from, crate, false))
{
crate.Delete();
from.SendLocalizedMessage(114456); // Your backpack cannot hold the Trade Order. Free up space and speak to the Trade Minister again.
}
ActiveTrades[from] = crate;
return true;
}
return false;
}
public bool TryTurnIn(Mobile from, TradeOrderCrate order, Mobile turninMobile)
{
if (order == null || from == null || turninMobile == null || order.Entry == null)
return false;
TradeEntry entry = order.Entry;
TradeMinister minister = turninMobile as TradeMinister;
if(from.AccessLevel == AccessLevel.Player && minister != null && minister.City != entry.Destination)
turninMobile.SayTo(from, 1151738, String.Format("#{0}", CityLoyaltySystem.GetCityLocalization(entry.Destination))); // Begging thy pardon, but those goods are destined for the City of ~1_city~
else if(!order.Fulfilled)
turninMobile.SayTo(from, 1151732); // This trade order has not been fulfilled. Fill the trade order with all necessary items and try again.
else
{
CityLoyaltySystem.OnTradeComplete(from, order.Entry);
CityTradeEntry pentry = GetPlayerEntry<CityTradeEntry>(from as PlayerMobile);
if (pentry != null)
{
pentry.Points++;
pentry.DistanceTraveled += entry.Distance;
pentry.Completed++;
CheckTitle(pentry);
}
order.Delete();
return true;
}
return false;
}
public bool TryTurnInToSlim(Mobile from, TradeOrderCrate order, SlimTheFence slim)
{
if (order == null || from == null || slim == null || order.Entry == null)
return false;
TradeEntry entry = order.Entry;
if (!order.Fulfilled)
slim.SayTo(from, 1151732); // This trade order has not been fulfilled. Fill the trade order with all necessary items and try again.
else
{
CityLoyaltySystem.OnSlimTradeComplete(from, order.Entry);
CityTradeEntry pentry = GetPlayerEntry<CityTradeEntry>(from as PlayerMobile, true);
if (pentry != null)
{
pentry.Points++;
pentry.DistanceTraveled += entry.Distance;
pentry.CompletedSlim++;
CheckTitle(pentry);
}
slim.SayTo(from, 1151736); // Haha! These goods will fetch me quite a bit o' coin! Thanks fer yer help! Here's a little something I was able to get me hands on...
order.Delete();
return true;
}
return false;
}
public void CheckTitle(CityTradeEntry entry)
{
switch (entry.Completed)
{
case 1: entry.Player.AddRewardTitle((int)TradeTitle.Trader); break;
case 25: entry.Player.AddRewardTitle((int)TradeTitle.Exporter); break;
case 50: entry.Player.AddRewardTitle((int)TradeTitle.Broker); break;
case 100: entry.Player.AddRewardTitle((int)TradeTitle.Tycoon); break;
case 150: entry.Player.AddRewardTitle((int)TradeTitle.Magnate); break;
}
if(entry.CompletedSlim == 50)
entry.Player.AddRewardTitle((int)TradeTitle.Smuggler);
}
public override void OnPlayerAdded(PlayerMobile m)
{
m.Backpack.DropItem(new MysteriousNote());
m.PrivateOverheadMessage(Server.Network.MessageType.Regular, 1150, 1151734, m.NetState); // *A passerby slips a rolled bit of parchment into your hand...*
}
public static void CancelTradeOrder(Mobile from, TradeOrderCrate crate)
{
if (from == null)
from = crate.Owner;
if (from != null)
{
crate.Items.ForEach(i =>
{
from.Backpack.DropItem(i);
});
CityTradeEntry entry = CityLoyaltySystem.CityTrading.GetPlayerEntry<CityTradeEntry>(from as PlayerMobile, true);
if (entry != null)
entry.Canceled++;
}
crate.Delete();
}
private Dictionary<Type, string> _NameBuffer;
public string GetNameFor(Type t, string fallbackname)
{
if (_NameBuffer.ContainsKey(t))
return _NameBuffer[t];
Item item = Loot.Construct(t);
if (item != null)
{
string name;
if (item.Name != null)
{
name = item.Name;
}
else
{
name = item.LabelNumber.ToString();
}
_NameBuffer[t] = name;
item.Delete();
return name;
}
Console.WriteLine("WARNING: Using Fallback name for: {0}", t.Name);
return fallbackname;
}
public void RemoveCrate(Mobile from, TradeOrderCrate crate)
{
if(ActiveTrades.ContainsKey(from))
{
ActiveTrades.Remove(from);
}
}
public static void OnPublicMoongateUsed(Mobile from)
{
if (ActiveTrades.ContainsKey(from))
{
ActiveTrades[from].Entry.Distance = 0;
}
}
public static int GetDistance(TradeMinister origin, City destination)
{
TradeMinister destMinister = TradeMinister.Ministers.FirstOrDefault(m => m.City == destination);
if(destMinister != null)
{
return (int)origin.GetDistanceToSqrt(destMinister.Location);
}
return 0;
}
public static Type GetRandomTrade(City originCity, City dest, ref int worth, ref string name)
{
Region region = CityLoyaltySystem.GetCityInstance(originCity).Definition.Region;
List<BaseVendor> list = new List<BaseVendor>(region.GetEnumeratedMobiles().OfType<BaseVendor>().Where(bv => bv.GetBuyInfo() != null && bv.GetBuyInfo().Length > 0));
if (list.Count == 0)
return null;
do
{
BaseVendor vendor = list[Utility.Random(list.Count)];
IBuyItemInfo[] buyInfo = vendor.GetBuyInfo();
GenericBuyInfo info = buyInfo[Utility.Random(buyInfo.Length)] as GenericBuyInfo;
if (!(info is BeverageBuyInfo) && !(info is AnimalBuyInfo) && info != null && info.Type != null && info.Args == null && info.Price < 5000)
{
list.Clear();
list.TrimExcess();
worth = info.Price;
name = info.Name;
return info.Type;
}
else
list.Remove(vendor);
}
while (list.Count > 0);
list.Clear();
list.TrimExcess();
return null;
}
public static void OnTick()
{
List<TradeOrderCrate> crates = new List<TradeOrderCrate>(ActiveTrades.Values);
List<BaseCreature> toDelete = new List<BaseCreature>();
foreach (var c in crates)
{
if (c.Expired)
{
CancelTradeOrder(c.Owner, c);
}
else if (c.Entry != null)
{
CheckAmbush(c);
}
}
if (Ambushers != null)
{
foreach (KeyValuePair<BaseCreature, DateTime> kvp in Ambushers)
{
if (kvp.Value < DateTime.UtcNow)
toDelete.Add(kvp.Key);
}
toDelete.ForEach(bc =>
{
if (!bc.Deleted)
bc.Delete();
Ambushers.Remove(bc);
});
}
toDelete.Clear();
toDelete.TrimExcess();
crates.Clear();
crates.TrimExcess();
}
public static void CheckAmbush(TradeOrderCrate crate)
{
if (crate == null || crate.Deleted || crate.Entry == null || crate.Expired || crate.Entry.LastAmbush + TimeSpan.FromMinutes(AmbushWaitDuration) > DateTime.UtcNow)
return;
if (crate.RootParentEntity is Mobile && !((Mobile)crate.RootParentEntity).Region.IsPartOf<GuardedRegion>())
{
Mobile m = crate.RootParentEntity as Mobile;
if (m.NetState != null && m.Map != null && m.Map != Map.Internal)
{
double chance = crate.Entry.LastAmbush == DateTime.MinValue ? 0.25 : .05;
if (chance > Utility.RandomDouble())
{
double dif = (double)(Math.Min(7200, m.SkillsTotal) + m.RawStr + m.RawInt + m.RawDex) / 10000;
m.RevealingAction();
SpawnCreatures(m, dif);
crate.Entry.LastAmbush = DateTime.UtcNow;
}
}
}
}
public static void SpawnCreatures(Mobile m, double difficulty)
{
BaseBoat boat = BaseBoat.FindBoatAt(m.Location, m.Map);
Type[] types = GetCreatureType(m, boat != null);
if (types == null)
{
return;
}
int amount = Utility.RandomMinMax(3, 5);
for (int i = 0; i < amount; i++)
{
BaseCreature bc = Activator.CreateInstance(types[Utility.Random(types.Length)]) as BaseCreature;
if (bc != null)
{
if (KrampusEncounterActive)
{
bc.Name = "An Icy Creature";
}
Rectangle2D zone;
if (boat != null)
{
if (boat.Facing == Direction.North || boat.Facing == Direction.South)
{
if (Utility.RandomBool())
{
zone = new Rectangle2D(boat.X - 7, m.Y - 4, 3, 3);
}
else
{
zone = new Rectangle2D(boat.X + 4, m.Y - 4, 3, 3);
}
}
else
{
if (Utility.RandomBool())
{
zone = new Rectangle2D(m.X + 4, boat.Y - 7, 3, 3);
}
else
{
zone = new Rectangle2D(m.X + 4, boat.Y + 4, 3, 3);
}
}
}
else
{
zone = new Rectangle2D(m.X - 3, m.Y - 3, 6, 6);
}
Point3D p = m.Location;
if (m.Map != null)
{
for (int j = 0; j < 25; j++)
{
Point3D check = m.Map.GetRandomSpawnPoint(zone);
if (CanFit(check.X, check.Y, check.Z, m.Map, bc))
{
p = check;
break;
}
}
}
foreach (Skill sk in bc.Skills.Where(s => s.Base > 0))
{
sk.Base += sk.Base * (difficulty);
}
bc.RawStr += (int)(bc.RawStr * difficulty);
bc.RawInt += (int)(bc.RawInt * difficulty);
bc.RawDex += (int)(bc.RawDex * difficulty);
if (bc.HitsMaxSeed == -1)
bc.HitsMaxSeed = bc.RawStr;
if (bc.StamMaxSeed == -1)
bc.StamMaxSeed = bc.RawDex;
if (bc.ManaMaxSeed == -1)
bc.ManaMaxSeed = bc.RawInt;
bc.HitsMaxSeed += (int)(bc.HitsMaxSeed * difficulty);
bc.StamMaxSeed += (int)(bc.StamMaxSeed * difficulty);
bc.ManaMaxSeed += (int)(bc.ManaMaxSeed * difficulty);
bc.Hits = bc.HitsMaxSeed;
bc.Stam = bc.RawDex;
bc.Mana = bc.RawInt;
bc.PhysicalResistanceSeed += (int)(bc.PhysicalResistanceSeed * (difficulty / 3));
bc.FireResistSeed += (int)(bc.FireResistSeed * (difficulty / 3));
bc.ColdResistSeed += (int)(bc.ColdResistSeed * (difficulty / 3));
bc.PoisonResistSeed += (int)(bc.PoisonResistSeed * (difficulty / 3));
bc.EnergyResistSeed += (int)(bc.EnergyResistSeed * (difficulty / 3));
bc.IsAmbusher = true;
if (Ambushers == null)
Ambushers = new Dictionary<BaseCreature, DateTime>();
Ambushers.Add(bc, DateTime.UtcNow + TimeSpan.FromMinutes(AmbusherDelete));
bc.MoveToWorld(p, m.Map);
Timer.DelayCall(() => bc.Combatant = m);
}
}
m.LocalOverheadMessage(Server.Network.MessageType.Regular, 1150, 1155479); // *Your keen senses alert you to an incoming ambush of attackers!*
m.SendLocalizedMessage(1049330, "", 0x22); // You have been ambushed! Fight for your honor!!!
}
public static Type[] GetCreatureType(Mobile m, bool wet)
{
if (KrampusEncounterActive)
{
return KrampusEncounter.Encounter.GetCreatureTypes(m, wet);
}
return wet ? _SeaTypes : _LandTypes;
}
public override void ProcessKill(Mobile victim, Mobile damager)
{
if (victim is BaseCreature && Ambushers != null && Ambushers.ContainsKey((BaseCreature)victim))
{
if (ActiveTrades.ContainsKey(damager))
{
TradeOrderCrate crate = ActiveTrades[damager];
if (crate.Entry != null)
crate.Entry.Kills++;
}
Ambushers.Remove((BaseCreature)victim);
}
}
private static Type[] _SeaTypes =
{
typeof(SeaSerpent), typeof(DeepSeaSerpent), typeof(Kraken), typeof(WaterElemental)
};
private static Type[] _LandTypes =
{
typeof(Troll), typeof(Ettin), typeof(GiantSpider), typeof(Brigand)
};
public static bool CanFit(int x, int y, int z, Map map, Mobile mob, int height = 16, bool checkMobiles = true, bool requireSurface = true)
{
if (map == null || map == Map.Internal)
return false;
if (x < 0 || y < 0 || x >= map.Width || y >= map.Height)
return false;
bool hasSurface = false;
bool canswim = mob.CanSwim;
bool cantwalk = mob.CantWalk;
LandTile lt = map.Tiles.GetLandTile(x, y);
int lowZ = 0, avgZ = 0, topZ = 0;
bool surface, impassable;
map.GetAverageZ(x, y, ref lowZ, ref avgZ, ref topZ);
TileFlag landFlags = TileData.LandTable[lt.ID & TileData.MaxLandValue].Flags;
impassable = (landFlags & TileFlag.Impassable) != 0;
bool wet = (landFlags & TileFlag.Wet) != 0;
if (cantwalk && !wet)
{
impassable = true;
}
if (canswim && wet)
{
impassable = false;
}
if (impassable && avgZ > z && (z + height) > lowZ)
return false;
else if (!impassable && z == avgZ && !lt.Ignored)
hasSurface = true;
StaticTile[] staticTiles = map.Tiles.GetStaticTiles(x, y, true);
for (int i = 0; i < staticTiles.Length; ++i)
{
ItemData id = TileData.ItemTable[staticTiles[i].ID & TileData.MaxItemValue];
surface = id.Surface;
impassable = id.Impassable;
wet = (id.Flags & TileFlag.Wet) != 0;
if (cantwalk && !wet)
{
impassable = true;
}
if (canswim && wet)
{
surface = true;
impassable = false;
}
if ((surface || impassable) && (staticTiles[i].Z + id.CalcHeight) > z && (z + height) > staticTiles[i].Z)
return false;
else if (surface && !impassable && z == (staticTiles[i].Z + id.CalcHeight))
hasSurface = true;
}
IPooledEnumerable eable = map.GetItemsInRange(new Point3D(x, y, z), 0);
foreach(Item item in eable)
{
if (item.ItemID < 0x4000)
{
ItemData id = item.ItemData;
surface = id.Surface;
impassable = id.Impassable;
wet = (id.Flags & TileFlag.Wet) != 0;
if (cantwalk && !wet)
{
impassable = true;
}
if (canswim && wet)
{
surface = true;
impassable = false;
}
if ((surface || impassable) && (item.Z + id.CalcHeight) > z && (z + height) > item.Z)
{
eable.Free();
return false;
}
else if (surface && !impassable && !item.Movable && z == (item.Z + id.CalcHeight))
{
hasSurface = true;
}
}
}
eable.Free();
if (checkMobiles)
{
eable = map.GetMobilesInRange(new Point3D(x, y, z), 0);
foreach(Mobile m in eable)
{
if (m.AccessLevel == AccessLevel.Player || !m.Hidden)
{
if ((m.Z + 16) > z && (z + height) > m.Z)
{
eable.Free();
return false;
}
}
}
eable.Free();
}
return !requireSurface || hasSurface;
}
[PropertyObject]
public class CityTradeEntry : PointsEntry
{
[CommandProperty(AccessLevel.GameMaster)]
public int Canceled { get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public int DistanceTraveled { get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public int Completed { get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public int CompletedSlim { get; set; }
public CityTradeEntry(PlayerMobile pm) : base(pm)
{
}
public override string ToString()
{
return "...";
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(1);
writer.Write(Canceled);
writer.Write(DistanceTraveled);
writer.Write(Completed);
writer.Write(Ambushers == null ? 0 : Ambushers.Count);
if (Ambushers != null)
{
foreach (KeyValuePair<BaseCreature, DateTime> kvp in Ambushers)
{
writer.Write(kvp.Key);
writer.Write(kvp.Value);
}
}
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
Canceled = reader.ReadInt();
DistanceTraveled = reader.ReadInt();
Completed = reader.ReadInt();
int count = reader.ReadInt();
for (int i = 0; i < count; i++)
{
BaseCreature bc = reader.ReadMobile() as BaseCreature;
DateTime dt = reader.ReadDateTime();
if (bc != null)
{
if (dt < DateTime.UtcNow)
bc.Delete();
else
{
if (Ambushers == null)
Ambushers = new Dictionary<BaseCreature, DateTime>();
bc.IsAmbusher = true;
Ambushers[bc] = dt;
}
}
}
if (version == 0)
{
Timer.DelayCall(() =>
{
if (Player.RemoveRewardTitle(2303807, true))
Player.AddRewardTitle(1151739);
});
}
}
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
writer.Write(ActiveTrades.Count);
foreach (KeyValuePair<Mobile, TradeOrderCrate> kvp in ActiveTrades)
{
writer.Write(kvp.Key);
writer.Write(kvp.Value);
}
writer.Write(_NameBuffer.Count);
foreach (KeyValuePair<Type, string> kvp in _NameBuffer)
{
writer.Write(kvp.Key.Name);
writer.Write(kvp.Value);
}
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
int count = reader.ReadInt();
for (int i = 0; i < count; i++)
{
Mobile m = reader.ReadMobile();
TradeOrderCrate crate = reader.ReadItem() as TradeOrderCrate;
if (m != null && crate != null)
ActiveTrades[m] = crate;
}
_NameBuffer = new Dictionary<Type, string>();
count = reader.ReadInt();
for (int i = 0; i < count; i++)
{
Type t = ScriptCompiler.FindTypeByName(reader.ReadString());
string name = reader.ReadString();
if (t != null)
_NameBuffer[t] = name;
}
}
}
}