686 lines
21 KiB
C#
686 lines
21 KiB
C#
using Server;
|
|
using System;
|
|
using Server.Items;
|
|
using Server.Multis;
|
|
using System.Collections.Generic;
|
|
using Server.Engines.Quests;
|
|
using System.Linq;
|
|
|
|
namespace Server.Mobiles
|
|
{
|
|
public class BaseShipCaptain : BaseCreature
|
|
{
|
|
public static readonly TimeSpan DeleteAfterDeath = TimeSpan.FromMinutes(15);
|
|
public static readonly TimeSpan ResumeTime = TimeSpan.FromSeconds(Utility.RandomMinMax(30, 45));
|
|
public static readonly TimeSpan DecayRetry = TimeSpan.FromSeconds(30);
|
|
|
|
private BaseGalleon m_Galleon;
|
|
private bool m_OnCourse;
|
|
private DateTime m_NextCannonShot;
|
|
private DateTime m_NextMoveCheck;
|
|
private DateTime m_ActionTime;
|
|
private DateTime m_NextCrewCheck;
|
|
private SpawnZone m_Zone;
|
|
private bool m_Blockade;
|
|
private List<Mobile> m_Crew = new List<Mobile>();
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public BaseGalleon Galleon { get { return m_Galleon; } }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool OnCourse { get { return m_OnCourse; } }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public DateTime NextCannonShot { get { return m_NextCannonShot; } }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public DateTime NextMoveCheck { get { return m_NextMoveCheck; } }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public DateTime NextCrewCheck { get { return m_NextCrewCheck; } set { m_NextCrewCheck = value; } }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public SpawnZone Zone { get { return m_Zone; } set { m_Zone = value; } }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool Blockade { get { return m_Blockade; } set { m_Blockade = value; } }
|
|
|
|
public List<Mobile> Crew { get { return m_Crew; } }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public virtual bool Aggressive { get { return true; } }
|
|
|
|
public override bool PlayerRangeSensitive { get { return false; } }
|
|
|
|
public override double TreasureMapChance { get { return 0.05; } }
|
|
public override int TreasureMapLevel { get { return 7; } }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public virtual TimeSpan ShootFrequency
|
|
{
|
|
get
|
|
{
|
|
return TimeSpan.FromSeconds(Math.Min(20, 20.0 - ((double)m_Crew.Count * 2.5)));
|
|
}
|
|
}
|
|
|
|
[Constructable]
|
|
public BaseShipCaptain() : this(null) { }
|
|
|
|
public BaseShipCaptain(BaseGalleon galleon)
|
|
: this(galleon, AIType.AI_Melee, FightMode.Weakest, 10, 1, .2, .4)
|
|
{
|
|
}
|
|
|
|
public BaseShipCaptain(BaseGalleon galleon, AIType ai, FightMode fm, int per, int range, double passive, double active)
|
|
: base(ai, fm, per, range, passive, active)
|
|
{
|
|
m_Galleon = galleon;
|
|
m_OnCourse = true;
|
|
m_StopTime = DateTime.MinValue;
|
|
|
|
if (this.Female = Utility.RandomBool())
|
|
{
|
|
Body = 0x191;
|
|
Name = NameList.RandomName("female");
|
|
AddItem(new Skirt(Utility.RandomNeutralHue()));
|
|
}
|
|
else
|
|
{
|
|
Body = 0x190;
|
|
Name = NameList.RandomName("male");
|
|
AddItem(new ShortPants(Utility.RandomNeutralHue()));
|
|
}
|
|
|
|
SetStr(500, 750);
|
|
SetDex(125, 175);
|
|
SetInt(61, 75);
|
|
|
|
SetHits(4500, 5000);
|
|
|
|
SetDamage(23, 35);
|
|
|
|
SetSkill(SkillName.Fencing, 115.0, 120.0);
|
|
SetSkill(SkillName.Macing, 115.0, 120.0);
|
|
SetSkill(SkillName.MagicResist, 115.0, 120.0);
|
|
SetSkill(SkillName.Swords, 115.0, 120.0);
|
|
SetSkill(SkillName.Tactics, 115.0, 120.0);
|
|
SetSkill(SkillName.Wrestling, 115.0, 120.0);
|
|
SetSkill(SkillName.Anatomy, 115.0, 120.0);
|
|
|
|
SetDamageType(ResistanceType.Physical, 100);
|
|
|
|
SetResistance(ResistanceType.Physical, 45, 55);
|
|
SetResistance(ResistanceType.Fire, 45, 55);
|
|
SetResistance(ResistanceType.Cold, 45, 55);
|
|
SetResistance(ResistanceType.Poison, 45, 55);
|
|
SetResistance(ResistanceType.Energy, 45, 55);
|
|
|
|
if (galleon == null)
|
|
Timer.DelayCall(TimeSpan.FromSeconds(.5), new TimerCallback(SpawnShip));
|
|
}
|
|
|
|
public void SpawnShip()
|
|
{
|
|
BaseGalleon gal;
|
|
|
|
if (this is PirateCaptain)
|
|
gal = new OrcishGalleon(Direction.North);
|
|
else if (this.Map == Map.Tokuno)
|
|
gal = new TokunoGalleon(Direction.North);
|
|
else
|
|
gal = new GargishGalleon(Direction.North);
|
|
|
|
var p = Location;
|
|
Map map = Map;
|
|
|
|
// Move this sucka out of the way!
|
|
Internalize();
|
|
|
|
if (gal.CanFit(p, map, gal.ItemID))
|
|
{
|
|
gal.Owner = this;
|
|
gal.MoveToWorld(p, map);
|
|
m_Galleon = gal;
|
|
|
|
Server.Engines.Quests.BountyQuestSpawner.FillHold(m_Galleon);
|
|
MoveToWorld(new Point3D(p.X, p.Y - 1, gal.Z + gal.ZSurface), map);
|
|
|
|
int crewCount = Utility.RandomMinMax(3, 5);
|
|
|
|
for (int j = 0; j < crewCount; j++)
|
|
{
|
|
Mobile crew = new PirateCrew();
|
|
|
|
if (j == 0 && this is PirateCaptain)
|
|
crew.Title = "the orc captain";
|
|
|
|
AddToCrew(crew);
|
|
crew.MoveToWorld(new Point3D(gal.X + Utility.RandomList(-1, 1), gal.Y + Utility.RandomList(-1, 0, 1), gal.ZSurface), map);
|
|
}
|
|
|
|
gal.AutoAddCannons(this);
|
|
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
gal.Delete();
|
|
Delete();
|
|
}
|
|
}
|
|
|
|
public void OnShipDelete()
|
|
{
|
|
if (this.Alive && !this.Deleted)
|
|
Kill();
|
|
|
|
for (int i = 0; i < m_Crew.Count; i++)
|
|
{
|
|
Mobile mob = m_Crew[i];
|
|
|
|
if (mob != null && !mob.Deleted)
|
|
mob.Kill();
|
|
}
|
|
}
|
|
|
|
public override void Delete()
|
|
{
|
|
if(BountyQuestSpawner.Instance != null)
|
|
BountyQuestSpawner.Instance.HandleDeath(this);
|
|
|
|
if (m_Galleon != null && !m_Galleon.Deleted)
|
|
{
|
|
m_Galleon.TimeOfDecay = DateTime.UtcNow + TimeSpan.FromMinutes(30);
|
|
Timer.DelayCall(DeleteAfterDeath, new TimerStateCallback(TryDecayGalleon), m_Galleon);
|
|
}
|
|
|
|
base.Delete();
|
|
}
|
|
|
|
public override void OnCombatantChange()
|
|
{
|
|
if (Combatant == null)
|
|
CantWalk = true;
|
|
else
|
|
CantWalk = false;
|
|
|
|
base.OnCombatantChange();
|
|
}
|
|
|
|
public void TryDecayGalleon(object obj)
|
|
{
|
|
BaseGalleon gal = obj as BaseGalleon;
|
|
|
|
if (gal == null)
|
|
return;
|
|
|
|
if (gal.PlayerCount() > 0)
|
|
{
|
|
Timer.DelayCall(DecayRetry, new TimerStateCallback(TryDecayGalleon), gal);
|
|
return;
|
|
}
|
|
|
|
if (gal != null && !gal.Deleted)
|
|
gal.ForceDecay();
|
|
}
|
|
|
|
public void AddToCrew(Mobile mob)
|
|
{
|
|
if (!m_Crew.Contains(mob))
|
|
m_Crew.Add(mob);
|
|
}
|
|
|
|
private DateTime m_StopTime;
|
|
private bool m_WillResume;
|
|
|
|
public void ResumeCourseTimed(TimeSpan ts, bool check)
|
|
{
|
|
if (!m_WillResume)
|
|
{
|
|
Timer.DelayCall(ts, new TimerCallback(ResumeCourse));
|
|
m_WillResume = true;
|
|
}
|
|
}
|
|
|
|
public void ResumeCourse()
|
|
{
|
|
if (m_Galleon != null)
|
|
{
|
|
m_Galleon.StartCourse(false, false);
|
|
m_WillResume = false;
|
|
m_OnCourse = true;
|
|
}
|
|
}
|
|
|
|
public override void OnThink()
|
|
{
|
|
base.OnThink();
|
|
|
|
if (m_Galleon == null)
|
|
return;
|
|
|
|
if (m_Galleon.Deleted)
|
|
OnShipDelete();
|
|
|
|
// Ship is fucked without his captain!!!
|
|
if (!m_Galleon.Contains(this))
|
|
return;
|
|
|
|
if (m_NextCrewCheck < DateTime.UtcNow)
|
|
{
|
|
CheckCrew();
|
|
}
|
|
|
|
if (!IsInBattle())
|
|
{
|
|
if (!m_OnCourse)
|
|
ResumeCourse();
|
|
else if (m_OnCourse && !m_Galleon.IsMoving && m_ActionTime < DateTime.UtcNow)
|
|
{
|
|
ResumeCourseTimed(ResumeTime, true);
|
|
m_ActionTime = DateTime.UtcNow + ResumeTime;
|
|
}
|
|
return;
|
|
}
|
|
|
|
m_OnCourse = false;
|
|
|
|
Mobile focusMob = GetFocusMob();
|
|
|
|
if(m_TargetBoat == null || !InRange(m_TargetBoat.Location, 25))
|
|
m_TargetBoat = GetFocusBoat(focusMob);
|
|
|
|
if (focusMob == null && m_TargetBoat == null)
|
|
return;
|
|
|
|
if (m_NextMoveCheck < DateTime.UtcNow && !m_Galleon.Scuttled && !m_Blockade)
|
|
{
|
|
Point3D pnt = m_TargetBoat != null ? m_TargetBoat.Location : focusMob.Location;
|
|
|
|
int dist = (int)GetDistanceToSqrt(pnt);
|
|
|
|
if (!Aggressive && dist < 25)
|
|
MoveBoat(pnt);
|
|
else if (Aggressive && dist >= 10 && dist <= 35)
|
|
MoveBoat(pnt);
|
|
else
|
|
{
|
|
m_Galleon.StopMove(false);
|
|
ResumeCourseTimed(TimeSpan.FromMinutes(2), false); //Loiter
|
|
}
|
|
}
|
|
|
|
if (m_TargetBoat != null && !m_TargetBoat.Scuttled)
|
|
ShootCannons(focusMob, true);
|
|
else
|
|
ShootCannons(focusMob, false);
|
|
}
|
|
|
|
private BaseGalleon m_TargetBoat;
|
|
|
|
public Mobile GetFocusMob()
|
|
{
|
|
Mobile focus = Combatant as Mobile;
|
|
|
|
if (focus == null || focus.Deleted || !focus.Alive)
|
|
{
|
|
int closest = 25;
|
|
|
|
foreach (Mobile mob in m_Crew)
|
|
{
|
|
if (mob.Alive && mob.Combatant is Mobile)
|
|
{
|
|
if (focus == null || (int)focus.GetDistanceToSqrt(mob) < closest)
|
|
{
|
|
focus = mob.Combatant as Mobile;
|
|
closest = (int)focus.GetDistanceToSqrt(mob);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return focus;
|
|
}
|
|
|
|
public BaseGalleon GetFocusBoat(Mobile focusMob)
|
|
{
|
|
if (focusMob == null || focusMob.Deleted || focusMob.Map == null || focusMob.Map == Map.Internal)
|
|
return null;
|
|
|
|
BaseGalleon g = BaseGalleon.FindGalleonAt(focusMob, focusMob.Map);
|
|
|
|
return g != m_Galleon ? g : null;
|
|
}
|
|
|
|
public void MoveBoat(Point3D p)
|
|
{
|
|
if (m_Galleon == null || m_Galleon.Contains(p))
|
|
return;
|
|
|
|
int x = p.X;
|
|
int y = p.Y;
|
|
int speed;
|
|
int flee = Aggressive ? 1 : -1;
|
|
|
|
//Direction d = Utility.GetDirectionTo(p.X, p.Y);
|
|
Direction dir = m_Galleon.GetMovementFor(x, y, out speed);
|
|
Direction f = m_Galleon.Facing;
|
|
|
|
if (!Aggressive)
|
|
dir = (Direction)(((int)dir + -4) & 0x7);
|
|
|
|
if (dir == Direction.West || dir == Direction.Left || dir == Direction.South)
|
|
{
|
|
m_Galleon.Turn(-2 * flee, true);
|
|
m_NextMoveCheck = DateTime.UtcNow + TimeSpan.FromSeconds(m_Galleon.TurnDelay);
|
|
return;
|
|
}
|
|
else if (dir == Direction.East || dir == Direction.Down)
|
|
{
|
|
m_Galleon.Turn(2 * flee, true);
|
|
m_NextMoveCheck = DateTime.UtcNow + TimeSpan.FromSeconds(m_Galleon.TurnDelay);
|
|
return;
|
|
}
|
|
|
|
m_Galleon.StartMove(dir, true);
|
|
}
|
|
|
|
private Dictionary<IShipCannon, DateTime> m_ShootTable = new Dictionary<IShipCannon, DateTime>();
|
|
|
|
public void ShootCannons(Mobile focus, bool shootAtBoat)
|
|
{
|
|
List<Item> cannons = new List<Item>(m_Galleon.Cannons.Where(i => !i.Deleted));
|
|
|
|
foreach (var cannon in cannons.OfType<IShipCannon>())
|
|
{
|
|
if (cannon != null)
|
|
{
|
|
if (m_ShootTable.ContainsKey(cannon) && m_ShootTable[cannon] > DateTime.UtcNow)
|
|
continue;
|
|
|
|
Direction facing = cannon.GetFacing();
|
|
|
|
if (shootAtBoat && HasTarget(focus, cannon, true))
|
|
{
|
|
cannon.AmmoType = AmmunitionType.Cannonball;
|
|
//cannon.LoadedAmmo = cannon.LoadTypes[0];
|
|
}
|
|
else if (!shootAtBoat && HasTarget(focus, cannon, false))
|
|
{
|
|
cannon.AmmoType = AmmunitionType.Grapeshot;
|
|
//cannon.LoadedAmmo = cannon.LoadTypes[1];
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
|
|
cannon.Shoot(this);
|
|
m_ShootTable[cannon] = DateTime.UtcNow + ShootFrequency + TimeSpan.FromSeconds(Utility.RandomMinMax(0, 3));
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool HasTarget(Mobile focus, IShipCannon cannon, bool shootatboat)
|
|
{
|
|
if (cannon == null || cannon.Deleted || cannon.Map == null || cannon.Map == Map.Internal || m_Galleon == null || m_Galleon.Deleted)
|
|
return false;
|
|
|
|
Direction d = cannon.GetFacing();
|
|
int xOffset = 0; int yOffset = 0;
|
|
int cannonrange = cannon.Range;
|
|
int currentRange = 0;
|
|
Point3D pnt = cannon.Location;
|
|
|
|
switch (d)
|
|
{
|
|
case Direction.North:
|
|
xOffset = 0; yOffset = -1; break;
|
|
case Direction.South:
|
|
xOffset = 0; yOffset = 1; break;
|
|
case Direction.West:
|
|
xOffset = -1; yOffset = 0; break;
|
|
case Direction.East:
|
|
xOffset = 1; yOffset = 0; break;
|
|
}
|
|
|
|
int xo = xOffset;
|
|
int yo = yOffset;
|
|
|
|
while (currentRange++ <= cannonrange)
|
|
{
|
|
xOffset = xo;
|
|
yOffset = yo;
|
|
|
|
for (int i = -1; i <= 1; i++)
|
|
{
|
|
Point3D newPoint;
|
|
|
|
if (xOffset == 0)
|
|
newPoint = new Point3D(pnt.X + (xOffset + i), pnt.Y + (yOffset * currentRange), pnt.Z);
|
|
else
|
|
newPoint = new Point3D(pnt.X + (xOffset * currentRange), pnt.Y + (yOffset + i), pnt.Z);
|
|
|
|
if (shootatboat)
|
|
{
|
|
BaseGalleon g = BaseGalleon.FindGalleonAt(newPoint, this.Map);
|
|
|
|
if (g != null && g == m_TargetBoat && g != Galleon)
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (focus == null)
|
|
return false;
|
|
|
|
if (newPoint.X == focus.X && newPoint.Y == focus.Y)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private bool IsInBattle()
|
|
{
|
|
if (Combatant != null)
|
|
return true;
|
|
|
|
foreach (Mobile mob in m_Crew)
|
|
{
|
|
if (mob.Alive && mob.Combatant != null)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public string AddHoldItem
|
|
{
|
|
get { return null; }
|
|
set
|
|
{
|
|
string str = value;
|
|
|
|
Type type = ScriptCompiler.FindTypeByName(str);
|
|
|
|
if (type != null && (type == typeof(Item) || type.IsSubclassOf(typeof(Item))))
|
|
{
|
|
Item item = Loot.Construct(type);
|
|
|
|
if (item != null)
|
|
HoldItem = item;
|
|
}
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Item HoldItem
|
|
{
|
|
get { return null; }
|
|
set
|
|
{
|
|
Item item = value;
|
|
|
|
if (item != null)
|
|
AddItemToHold(item);
|
|
}
|
|
}
|
|
|
|
public void AddItemToHold(Item item)
|
|
{
|
|
if (item == null)
|
|
return;
|
|
|
|
if (m_Galleon != null && m_Galleon.GalleonHold != null)
|
|
m_Galleon.GalleonHold.DropItem(item);
|
|
else
|
|
item.Delete();
|
|
}
|
|
|
|
public void CheckCrew()
|
|
{
|
|
if (m_Galleon == null || m_Crew == null || Map == null || Map == Map.Internal)
|
|
return;
|
|
|
|
List<Mobile> crew = new List<Mobile>(m_Crew.Where(m => m != null && m.Alive && m.Map != null && m.Map != Map.Internal));
|
|
|
|
if (m_Galleon.GalleonPilot != null)
|
|
crew.Add(m_Galleon.GalleonPilot);
|
|
|
|
crew.Add(this);
|
|
|
|
foreach (var crewman in crew)
|
|
{
|
|
if(!m_Galleon.Contains(crewman))
|
|
{
|
|
crewman.MoveToWorld(new Point3D(m_Galleon.X + Utility.RandomList(-1, 1), m_Galleon.Y + Utility.RandomList(-1, 0, 1), m_Galleon.ZSurface), this.Map);
|
|
}
|
|
}
|
|
|
|
ColUtility.Free(crew);
|
|
m_NextCrewCheck = DateTime.UtcNow + TimeSpan.FromMinutes(30);
|
|
}
|
|
|
|
public void CheckBlock(StaticTile tile, Point3D p)
|
|
{
|
|
BaseBoat check = BaseBoat.FindBoatAt(p, Map);
|
|
|
|
if (check != null)
|
|
CheckBlock(check, p);
|
|
}
|
|
|
|
public void CheckBlock(IEntity e, Point3D loc)
|
|
{
|
|
if (m_Galleon == null || !m_OnCourse || IsInBattle())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (loc == Point3D.Zero)
|
|
{
|
|
loc = e.Location;
|
|
}
|
|
|
|
BaseBoat check = BaseBoat.FindBoatAt(new Point3D(e), Map);
|
|
|
|
if ((check != null && check != m_Galleon) || e is BaseCreature)
|
|
{
|
|
Direction d = Utility.GetDirection(m_Galleon, e);
|
|
|
|
int blockX = e.X;
|
|
int blockY = e.Y;
|
|
|
|
int x = m_Galleon.X;
|
|
int y = m_Galleon.Y;
|
|
Direction toMove = Direction.North;
|
|
|
|
switch (m_Galleon.Facing)
|
|
{
|
|
case Direction.North:
|
|
toMove = blockX > x ? Direction.West : Direction.East;
|
|
break;
|
|
case Direction.East:
|
|
toMove = blockY > y ? Direction.North : Direction.South;
|
|
break;
|
|
case Direction.South:
|
|
toMove = blockX < x ? Direction.East : Direction.West;
|
|
break;
|
|
case Direction.West:
|
|
toMove = blockY < y ? Direction.South : Direction.North;
|
|
break;
|
|
}
|
|
|
|
Movement.Movement.Offset(toMove, ref x, ref y);
|
|
|
|
int speed;
|
|
toMove = m_Galleon.GetMovementFor(x, y, out speed);
|
|
|
|
Timer.DelayCall(TimeSpan.FromSeconds(1), () =>
|
|
{
|
|
m_Galleon.StartMove(toMove, false);
|
|
});
|
|
|
|
ResumeCourseTimed(TimeSpan.FromSeconds(11), true);
|
|
}
|
|
}
|
|
|
|
public BaseShipCaptain(Serial serial)
|
|
: base(serial)
|
|
{
|
|
}
|
|
|
|
public override void Serialize(GenericWriter writer)
|
|
{
|
|
base.Serialize(writer);
|
|
writer.Write((int)0);
|
|
|
|
writer.Write(m_Blockade);
|
|
|
|
writer.Write((int)m_Zone);
|
|
writer.Write(m_StopTime);
|
|
writer.Write(m_Galleon);
|
|
writer.Write(m_OnCourse);
|
|
|
|
writer.Write(m_Crew.Count);
|
|
foreach (Mobile mob in m_Crew)
|
|
writer.Write(mob);
|
|
}
|
|
|
|
public override void Deserialize(GenericReader reader)
|
|
{
|
|
base.Deserialize(reader);
|
|
int version = reader.ReadInt();
|
|
|
|
m_Blockade = reader.ReadBool();
|
|
m_Zone = (SpawnZone)reader.ReadInt();
|
|
m_StopTime = reader.ReadDateTime();
|
|
m_Galleon = reader.ReadItem() as BaseGalleon;
|
|
m_OnCourse = reader.ReadBool();
|
|
|
|
int count = reader.ReadInt();
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
Mobile mob = reader.ReadMobile() as Mobile;
|
|
if (mob != null && !mob.Deleted && mob.Alive)
|
|
m_Crew.Add(mob);
|
|
}
|
|
|
|
if (!m_Blockade)
|
|
ResumeCourseTimed(TimeSpan.FromSeconds(15), true);
|
|
|
|
if (m_Galleon != null)
|
|
{
|
|
Timer.DelayCall(TimeSpan.FromSeconds(30), CheckCrew);
|
|
}
|
|
|
|
m_NextMoveCheck = DateTime.UtcNow;
|
|
m_NextCannonShot = DateTime.UtcNow;
|
|
}
|
|
}
|
|
}
|