Files

1705 lines
56 KiB
C#

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using Library;
using Library.Network;
using Library.SystemModels;
using Server.DBModels;
using Server.Envir;
using Zircon.Server.Models.Monsters;
using S = Library.Network.ServerPackets;
namespace Zircon.Server.Models
{
public abstract class MapObject
{
public uint ObjectID { get; private set; }
public LinkedListNode<MapObject> Node;
public abstract ObjectType Race { get; }
public virtual bool Blocking {get{return !Dead;}}
public virtual string Name { get; set; }
public virtual int Level { get; set; }
public Cell CurrentCell
{
get { return _CurrentCell; }
set
{
if (_CurrentCell == value) return;
var oldValue = _CurrentCell;
_CurrentCell = value;
LocationChanged(oldValue, value);
}
}
private Cell _CurrentCell;
public Map CurrentMap
{
get { return _CurrentMap; }
set
{
if (_CurrentMap == value) return;
var oldValue = _CurrentMap;
_CurrentMap = value;
MapChanged(oldValue, value);
}
}
private Map _CurrentMap;
public virtual Point CurrentLocation { get; set; }
public virtual MirDirection Direction { get; set; }
public bool DisplayCrit, DisplayMiss, DisplayResist, DisplayBlock;
public int DisplayHP;
public int DisplayMP;
public virtual int CurrentHP { get; set; }
public virtual int CurrentMP { get; set; }
public bool Spawned, Dead, CoolEye;
public bool InSafeZone;
public bool Activated { get; set; }
public DateTime FrostBiteImmunity;
public DateTime ActionTime, MoveTime, RegenTime, AttackTime, MagicTime, CellTime, StruckTime, BuffTime, ShockTime, DisplayHPMPTime, ItemReviveTime;
public List<DelayedAction> ActionList { get; set; }
public virtual bool CanMove
{
get{
return !Dead && SEnvir.Now >= ActionTime && SEnvir.Now >= MoveTime && SEnvir.Now > ShockTime && (Poison & PoisonType.Paralysis) != PoisonType.Paralysis && (Poison & PoisonType.WraithGrip) != PoisonType.WraithGrip && Buffs.All(x => x.Type != BuffType.DragonRepulse && x.Type != BuffType.FrostBite);
}
}
public virtual bool CanAttack
{
get
{
return !Dead && SEnvir.Now >= ActionTime && SEnvir.Now >= AttackTime && (Poison & PoisonType.Paralysis) != PoisonType.Paralysis && Buffs.All(x => x.Type != BuffType.DragonRepulse && x.Type != BuffType.FrostBite);
}
}
public virtual bool CanCast
{
get
{
return !Dead && SEnvir.Now >= ActionTime && SEnvir.Now >= MagicTime && (Poison & PoisonType.Paralysis) != PoisonType.Paralysis && (Poison & PoisonType.Silenced) != PoisonType.Silenced && Buffs.All(x => x.Type != BuffType.DragonRepulse && x.Type != BuffType.FrostBite);
}
}
public List<SpellObject> SpellList = new List<SpellObject>();
public List<BuffInfo> Buffs;
public Stats Stats;
public bool Visible, PreventSpellCheck;
public decimal LifeSteal { get; set; }
public TimeSpan RegenDelay { get; set; }
public Color NameColour { get; set; }
public List<PlayerObject> NearByPlayers;
public List<PlayerObject> SeenByPlayers;
public List<PlayerObject> DataSeenByPlayers;
public PoisonType Poison;
public List<Poison> PoisonList;
public List<PlayerObject> GroupMembers;
protected MapObject()
{
ObjectID = SEnvir.ObjectID;
NameColour = Color.White;
Visible = true;
RegenDelay = TimeSpan.FromSeconds(10);
BuffTime = SEnvir.Now;
NearByPlayers = new List<PlayerObject>();
SeenByPlayers = new List<PlayerObject>();
ActionList = new List<DelayedAction>();
Buffs = new List<BuffInfo>();
PoisonList = new List<Poison>();
DataSeenByPlayers = new List<PlayerObject>();
}
public void StartProcess()
{
DeActivate();
//Other things
for (int i = ActionList.Count - 1; i >= 0; i--)
{
if (SEnvir.Now < ActionList[i].Time) continue;
DelayedAction ac = ActionList[i];
ActionList.RemoveAt(i);
ProcessAction(ac);
}
ProcessBuff();
ProcessPoison();
Process();
ProcessHPMP();
Color oldColour = NameColour;
ProcessNameColour();
if (oldColour != NameColour)
Broadcast(new S.ObjectNameColour { ObjectID = ObjectID, Colour = NameColour });
}
public virtual void Process()
{
}
public virtual void ProcessHPMP()
{
if (SEnvir.Now < DisplayHPMPTime) return;
DisplayHPMPTime = SEnvir.Now.AddMilliseconds(200);
bool changed = false;
if (DisplayHP != CurrentHP || DisplayBlock || DisplayCrit || DisplayMiss)
{
int change = CurrentHP - DisplayHP;
Broadcast(new S.HealthChanged { ObjectID = ObjectID, Change = change, Critical = DisplayCrit, Miss = DisplayMiss, Block = DisplayBlock });
DisplayHP = CurrentHP;
DisplayMiss = false;
DisplayBlock = false;
DisplayCrit = false;
changed = true;
}
if (DisplayMP != CurrentMP)
{
int change = CurrentMP - DisplayMP;
Broadcast(new S.ManaChanged { ObjectID = ObjectID, Change = change });
DisplayMP = CurrentMP;
changed = true;
}
if (!changed) return;
S.DataObjectHealthMana p = new S.DataObjectHealthMana { ObjectID = ObjectID, Health = DisplayHP, Mana = DisplayMP, Dead = Dead };
foreach (PlayerObject player in DataSeenByPlayers)
player.Enqueue(p);
}
public virtual void ProcessPoison()
{
PoisonType current = PoisonType.None;
for (int i = PoisonList.Count - 1; i >= 0; i--)
{
Poison poison = PoisonList[i];
if (poison.Owner != null)
if (poison.Owner.Node == null || poison.Owner.Dead || poison.Owner.CurrentMap != CurrentMap || !Functions.InRange(poison.Owner.CurrentLocation, CurrentLocation, Config.MaxViewRange))
{
PoisonList.Remove(poison);
continue;
}
current |= poison.Type;
if (SEnvir.Now < poison.TickTime) continue;
if (poison.TickCount-- <= 0) PoisonList.RemoveAt(i);
poison.TickTime = SEnvir.Now + poison.TickFrequency;
bool infection = false;
int damage = 0;
MonsterObject mob;
switch (poison.Type)
{
case PoisonType.Green:
damage += poison.Value;
break;
case PoisonType.WraithGrip:
ChangeMP(-poison.Value);
if (poison.Extra != null)
poison.Owner.ChangeMP(poison.Value * (((UserMagic) poison.Extra).Level + 1));
break;
case PoisonType.HellFire:
damage += poison.Value;
break;
case PoisonType.Infection:
if (Race == ObjectType.Player)
damage += 1 + Stats[Stat.Health] / 100;
else
{
damage += poison.Value;
for (int x = 0; x < poison.Owner.Stats[Stat.Rebirth]; x++)
damage += (damage / 2);
}
infection = true;
if (Race == ObjectType.Monster && poison.Owner.Race == ObjectType.Player)
{
mob = (MonsterObject)this;
if (mob.EXPOwner == null)
mob.EXPOwner = (PlayerObject) poison.Owner;
}
if (poison.TickCount <= 0) break;
foreach (MapObject ob in poison.Owner.GetTargets(CurrentMap, CurrentLocation, 1))
{
//if (ob.Race != ObjectType.Monster) continue;
if (ob.Race == ObjectType.Monster && ((MonsterObject)ob).MonsterInfo.IsBoss) continue;
if (ob.PoisonList.Any(x => x.Type == PoisonType.Infection)) continue;
ob.ApplyPoison(new Poison
{
Value = poison.Value,
Owner = poison.Owner,
TickCount = poison.TickCount,
TickFrequency = poison.TickFrequency,
Type = poison.Type,
TickTime = poison.TickTime
});
}
break;
}
if (damage > 0)
{
if (Race == ObjectType.Monster && ((MonsterObject) this).MonsterInfo.IsBoss)
damage = 0;
else if (!infection)
damage = Math.Min(CurrentHP - 1, damage);
if (damage > 0)
{
#region Conquest Stats
UserConquestStats conquest;
switch (Race)
{
case ObjectType.Player:
conquest = SEnvir.GetConquestStats((PlayerObject)this);
if (conquest != null)
{
switch (poison.Owner.Race)
{
case ObjectType.Player:
conquest.PvPDamageTaken += damage;
conquest = SEnvir.GetConquestStats((PlayerObject)poison.Owner);
if (conquest != null)
conquest.PvPDamageDealt += damage;
break;
case ObjectType.Monster:
mob = (MonsterObject)poison.Owner;
if (mob is CastleLord)
conquest.BossDamageTaken += damage;
else if (mob.PetOwner != null)
{
conquest.PvPDamageTaken += damage;
conquest = SEnvir.GetConquestStats(mob.PetOwner);
if (conquest != null)
conquest.PvPDamageDealt += damage;
}
break;
}
}
break;
case ObjectType.Monster:
mob = (MonsterObject)this;
if (mob is CastleLord)
{
switch (poison.Owner.Race)
{
case ObjectType.Player:
conquest = SEnvir.GetConquestStats((PlayerObject)poison.Owner);
if (conquest != null)
conquest.BossDamageDealt += damage;
break;
case ObjectType.Monster:
mob = (MonsterObject)poison.Owner;
if (mob.PetOwner != null)
{
conquest = SEnvir.GetConquestStats(mob.PetOwner);
if (conquest != null)
conquest.BossDamageDealt += damage;
}
break;
}
}
break;
}
#endregion
ChangeHP(-damage);
}
if (Dead) break;
RegenTime = SEnvir.Now + RegenDelay;
ShockTime = DateTime.MinValue;
}
}
if (current == Poison) return;
Poison = current;
Broadcast(new S.ObjectPoison { ObjectID = ObjectID, Poison = Poison });
}
public virtual void ProcessNameColour()
{ }
public virtual void ProcessBuff()
{
TimeSpan ticks = SEnvir.Now - BuffTime;
BuffTime = SEnvir.Now;
List<BuffInfo> expiredBuffs = new List<BuffInfo>();
foreach (BuffInfo buff in Buffs)
{
int amount;
PlayerObject player;
UserMagic magic;
if (buff.Pause) continue;
switch (buff.Type)
{
case BuffType.Companion:
buff.TickTime -= ticks;
if (buff.TickTime > TimeSpan.Zero) continue;
buff.TickTime += buff.TickFrequency;
player = (PlayerObject)this;
if (!player.InSafeZone || player.Companion.UserCompanion.Level < 15)
player.Companion.UserCompanion.Hunger--;
if (player.Companion.LevelInfo.MaxExperience > 0)
{
int highest = player.Character.Account.Companions.Max(x => x.Level);
if (highest <= player.Companion.UserCompanion.Level)
highest = 1;
player.Companion.UserCompanion.Experience += highest + Stats[Stat.CompanionRate];
if (player.Companion.UserCompanion.Experience >= player.Companion.LevelInfo.MaxExperience)
{
player.Companion.UserCompanion.Experience = 0;
player.Companion.UserCompanion.Level++;
player.Companion.CheckSkills();
player.Companion.RefreshStats();
}
}
player.Companion.AutoFeed();
player.Enqueue(new S.CompanionUpdate
{
Level = player.Companion.UserCompanion.Level,
Experience = player.Companion.UserCompanion.Experience,
Hunger = player.Companion.UserCompanion.Hunger,
});
if (player.Companion.UserCompanion.Hunger <= 0)
expiredBuffs.Add(buff);
break;
case BuffType.Heal:
buff.TickTime -= ticks;
if (buff.TickTime > TimeSpan.Zero) continue;
buff.TickTime += buff.TickFrequency;
amount = Math.Min(buff.Stats[Stat.Healing], buff.Stats[Stat.HealingCap]);
ChangeHP(amount);
buff.Stats[Stat.Healing] -= amount;
if (CurrentHP < Stats[Stat.Health] && buff.Stats[Stat.Healing] > 0)
{
if (Race == ObjectType.Player)
((PlayerObject)this).Enqueue(new S.BuffChanged { Index = buff.Index, Stats = buff.Stats });
}
else
expiredBuffs.Add(buff);
break;
case BuffType.Cloak:
buff.TickTime -= ticks;
if (buff.TickTime > TimeSpan.Zero) continue;
buff.TickTime += buff.TickFrequency;
amount = buff.Stats[Stat.CloakDamage];
if (amount >= CurrentHP)
{
expiredBuffs.Add(buff);
break;
}
ChangeHP(-amount);
break;
case BuffType.DarkConversion:
buff.TickTime -= ticks;
if (buff.TickTime > TimeSpan.Zero) continue;
buff.TickTime += buff.TickFrequency;
amount = buff.Stats[Stat.DarkConversion];
if (amount > CurrentMP)
{
expiredBuffs.Add(buff);
break;
}
ChangeMP(-amount);
ChangeHP(amount*2);
if (Race != ObjectType.Player) break;
player = (PlayerObject)this;
if (!player.Magics.TryGetValue(MagicType.DarkConversion, out magic)) break;
player.LevelMagic(magic);
break;
case BuffType.PKPoint:
buff.TickTime -= ticks;
if (buff.TickTime > TimeSpan.Zero) continue;
buff.TickFrequency = Config.PKPointTickRate;
buff.TickTime += buff.TickFrequency;
buff.Stats[Stat.PKPoint]--;
RefreshStats();
if (buff.Stats[Stat.PKPoint] <= 0)
expiredBuffs.Add(buff);
else
{
if (Race == ObjectType.Player)
((PlayerObject) this).Enqueue(new S.BuffChanged { Index = buff.Index, Stats = buff.Stats });
}
break;
case BuffType.HuntGold:
buff.TickTime -= ticks;
if (buff.TickTime > TimeSpan.Zero) continue;
buff.TickTime += buff.TickFrequency;
player = this as PlayerObject;
if (player != null)
{
if (SEnvir.ConquestWars.Any(war => war.Map == CurrentMap))
{
player.Enqueue(new S.HuntGoldChanged { HuntGold = ++player.Character.Account.HuntGold });
continue;
}
}
if (buff.Stats[Stat.AvailableHuntGold] >= buff.Stats[Stat.AvailableHuntGoldCap]) continue;
buff.Stats[Stat.AvailableHuntGold]++;
if (Race == ObjectType.Player)
((PlayerObject)this).Enqueue(new S.BuffChanged { Index = buff.Index, Stats = buff.Stats });
break;
case BuffType.DragonRepulse:
buff.TickTime -= ticks;
if (buff.RemainingTime != TimeSpan.MaxValue)
{
buff.RemainingTime -= ticks;
if (buff.RemainingTime <= TimeSpan.Zero)
expiredBuffs.Add(buff);
}
if (buff.TickTime > TimeSpan.Zero) continue;
buff.TickTime += buff.TickFrequency;
List<Cell> cells = CurrentMap.GetCells(CurrentLocation, 0, 5);
switch (Race)
{
case ObjectType.Player:
player = (PlayerObject) this;
if (!player.Magics.TryGetValue(MagicType.DragonRepulse, out magic)) break;
player.LevelMagic(magic);
for (int c = cells.Count - 1; c >= 0; c--)
{
Cell cell = cells[c];
if (cell.Objects == null) continue;
if (Functions.Distance4Directions(CurrentLocation, cell.Location) > 5) continue;
for (int o = cell.Objects.Count - 1; o >= 0; o--)
{
MapObject ob = cell.Objects[o];
if (!CanAttackTarget(ob)) continue;
ActionList.Add(new DelayedAction(
SEnvir.Now.AddMilliseconds(SEnvir.Random.Next(200) + Functions.Distance(CurrentLocation, ob.CurrentLocation)*20),
ActionType.DelayMagic,
new List<UserMagic> { magic },
ob));
}
}
break;
case ObjectType.Monster:
for (int c = cells.Count - 1; c >= 0; c--)
{
Cell cell = cells[c];
if (cell.Objects == null) continue;
if (Functions.Distance4Directions(CurrentLocation, cell.Location) > 5) continue;
for (int o = cell.Objects.Count - 1; o >= 0; o--)
{
MapObject ob = cell.Objects[o];
if (!CanAttackTarget(ob)) continue;
ActionList.Add(new DelayedAction(
SEnvir.Now.AddMilliseconds(400),
ActionType.DelayMagic,
MagicType.DragonRepulse,
ob));
}
}
break;
}
break;
case BuffType.FrostBite:
buff.RemainingTime -= ticks;
if (buff.RemainingTime > TimeSpan.Zero) continue;
Broadcast(new S.ObjectEffect { ObjectID = ObjectID, Effect = Effect.FrostBiteEnd });
switch (Race)
{
case ObjectType.Player:
player = (PlayerObject)this;
if (!player.Magics.TryGetValue(MagicType.FrostBite, out magic)) break;
player.LevelMagic(magic);
foreach (MapObject ob in GetTargets(CurrentMap, CurrentLocation, 3))
{
if (!CanAttackTarget(ob)) continue;
if (ob.Race != ObjectType.Monster) continue;
MonsterObject mob = (MonsterObject)ob;
//if (mob.MonsterInfo.IsBoss) continue;
ActionList.Add(new DelayedAction(
SEnvir.Now.AddMilliseconds(SEnvir.Random.Next(500)),
ActionType.DelayedMagicDamage,
new List<UserMagic> { magic },
ob,
true,
buff.Stats,
0));
}
break;
}
FrostBiteImmunity = SEnvir.Now.AddSeconds(1);
expiredBuffs.Add(buff);
break;
default:
if (buff.RemainingTime == TimeSpan.MaxValue) continue;
buff.RemainingTime -= ticks;
if (buff.RemainingTime > TimeSpan.Zero) continue;
expiredBuffs.Add(buff);
break;
}
}
foreach (BuffInfo buff in expiredBuffs)
BuffRemove(buff);
}
public virtual void ProcessAction(DelayedAction action)
{
switch (action.Type)
{
case ActionType.BroadCastPacket:
Broadcast((Packet)action.Data[0]);
break;
}
}
public bool Spawn(MapRegion region)
{
if (region == null) return false;
Map map = SEnvir.GetMap(region.Map);
if (map == null) return false;
if (region.PointList.Count == 0) return false;
for (int i = 0; i < 20; i++)
if (Spawn(region.Map, region.PointList[SEnvir.Random.Next(region.PointList.Count)])) break;
return true;
}
public bool Spawn(MapInfo info, Point location)
{
if (Node != null)
throw new InvalidOperationException("Node is not null, Object already spawned");
if (info == null) return false;
Map map;
if (!SEnvir.Maps.TryGetValue(info, out map)) return false;
if (this is PlayerObject player1 && info.MinimumLevel > Level && !player1.GameMaster) return false;
if (this is PlayerObject player2 && info.MaximumLevel > 0 && info.MaximumLevel < Level && !player2.GameMaster) return false;
Cell cell = map.GetCell(location);
if (cell == null) return false;
CurrentCell = cell;
Spawned = true;
Node = SEnvir.Objects.AddLast(this);
OnSpawned();
return true;
}
protected void MapChanged(Map oMap, Map nMap)
{
if (oMap != null)
oMap.RemoveObject(this);
if (nMap != null)
nMap.AddObject(this);
OnMapChanged();
}
public void LocationChanged(Cell oCell, Cell nCell)
{
if (oCell != null)
oCell.RemoveObject(this);
if (nCell != null)
nCell.AddObject(this);
OnLocationChanged();
}
protected virtual void OnLocationChanged()
{
CellTime = SEnvir.Now.AddMilliseconds(300);
BuffRemove(BuffType.PoisonousCloud);
if (CurrentCell != null)
{
if (!PreventSpellCheck) CheckSpellObjects();
S.DataObjectLocation p = new S.DataObjectLocation { ObjectID = ObjectID, MapIndex = CurrentCell.Map.Info.Index, CurrentLocation = CurrentLocation };
foreach (PlayerObject player in DataSeenByPlayers)
player.Enqueue(p);
}
}
public virtual void CheckSpellObjects()
{
Cell cell = CurrentCell;
for (int i = CurrentCell.Objects.Count - 1; i >= 0; i--)
{
MapObject ob = CurrentCell.Objects[i];
if (Dead) break;
if (ob.Race != ObjectType.Spell) continue;
((SpellObject) ob).ProcessSpell(this);
if (cell != CurrentCell) break; //Tempest could repel this
}
}
protected virtual void OnMapChanged() { }
protected virtual void OnSpawned() { }
public void TeleportNearby(int minDistance, int maxDistance)
{
List<Cell> cells = CurrentMap.GetCells(CurrentLocation, minDistance, maxDistance);
if (cells.Count == 0) return;
Teleport(CurrentMap, cells[SEnvir.Random.Next(cells.Count)].Location);
}
public string Teleport(MapRegion region, bool leaveEffect = true)
{
Map map = SEnvir.GetMap(region.Map);
Point point = region.PointList.Count > 0 ? region.PointList[SEnvir.Random.Next(region.PointList.Count)] : map.GetRandomLocation();
return Teleport(map, point, leaveEffect);
}
public virtual string Teleport(Map map, Point location, bool leaveEffect = true, bool beForced = false)
{
if (this is PlayerObject player1 && map.Info.MinimumLevel > Level && !player1.GameMaster)
return "召唤的角色等级不能低于地图要求";
if (this is PlayerObject player2 && map.Info.MaximumLevel > 0 && map.Info.MaximumLevel < Level && !player2.GameMaster)
return "召唤的角色等级不能高于地图要求";
Cell? cell = map?.GetCell(location);
if (cell == null || cell.Movements != null) return "召唤地点错误";
if (leaveEffect)
Broadcast(new S.ObjectEffect { ObjectID = ObjectID, Effect = Effect.TeleportOut });
CurrentCell = cell.GetMovement(this);
RemoveAllObjects();
AddAllObjects();
Broadcast(new S.ObjectTurn { ObjectID = ObjectID, Direction = Direction, Location = CurrentLocation });
Broadcast(new S.ObjectEffect { ObjectID = ObjectID, Effect = Effect.TeleportIn });
return string.Empty;
}
public virtual void AddAllObjects()
{
foreach (PlayerObject ob in CurrentMap.Players)
{
if (CanBeSeenBy(ob))
ob.AddObject(this);
if (IsNearBy(ob))
ob.AddNearBy(this);
}
foreach (PlayerObject ob in SEnvir.Players)
{
if (CanDataBeSeenBy(ob))
ob.AddDataObject(this);
}
}
public virtual void RemoveAllObjects()
{
for (int i = SeenByPlayers.Count - 1; i >= 0; i--)
{
if (CanBeSeenBy(SeenByPlayers[i])) continue;
SeenByPlayers[i].RemoveObject(this);
}
for (int i = DataSeenByPlayers.Count - 1; i >= 0; i--)
{
if (CanDataBeSeenBy(DataSeenByPlayers[i])) continue;
DataSeenByPlayers[i].RemoveDataObject(this);
}
for (int i = NearByPlayers.Count - 1; i >= 0; i--)
{
if (IsNearBy(NearByPlayers[i])) continue;
NearByPlayers[i].RemoveNearBy(this);
}
}
public virtual void Activate()
{
if (Activated) return;
if (NearByPlayers.Count == 0) return;
Activated = true;
SEnvir.ActiveObjects.Add(this);
}
public virtual void DeActivate()
{
if (!Activated) return;
if (NearByPlayers.Count > 0 || ActionList.Count > 0) return;
Activated = false;
SEnvir.ActiveObjects.Remove(this);
}
public virtual bool CanDataBeSeenBy(PlayerObject ob)
{
if (ob == this) return true;
if (CurrentMap == null || ob.CurrentMap == null) return false;
switch (Race)
{
case ObjectType.Player:
PlayerObject player = (PlayerObject)this;
if (player.Observer) return false;
if (InGroup(ob)) return true;
if (player.InGuild(ob)) return true;
if (player.Character != null) if (player.Character.Partner != null)
if (player.Character.Partner.Player == ob) return true;
if (ob.CurrentMap == CurrentMap && ob.Stats[Stat.PlayerTracker] > 0) return true;
break;
case ObjectType.Monster:
MonsterObject mob = (MonsterObject) this;
if (ob.CurrentMap == CurrentMap && mob.MonsterInfo.IsBoss && ob.Stats[Stat.BossTracker] > 0) return true;
break;
}
return CanBeSeenBy(ob);
}
public virtual bool CanBeSeenBy(PlayerObject ob)
{
if (ob == this) return true;
if (CurrentMap != ob.CurrentMap)
return false;
if (!Functions.InRange(CurrentLocation, ob.CurrentLocation, Config.MaxViewRange))
return false;
if (Race == ObjectType.Player && ((PlayerObject) this).Observer) return false;
if (ob.GameMaster)
return true;
if (Buffs.Any(x => x.Type == BuffType.Cloak || x.Type == BuffType.Transparency))
{
if (InGroup(ob))
return true;
if (Race == ObjectType.Player)
{
PlayerObject player = (PlayerObject)this;
if (player.Observer) return false;
if (player.InGuild(ob)) return true;
if (player.Character != null) if (player.Character.Partner != null)
if (player.Character.Partner.Player == ob) return true;
}
if (ob.Level < Level || !Functions.InRange(CurrentLocation, ob.CurrentLocation, Globals.CloakRange))
return false;
}
return true;
}
public virtual bool IsNearBy(PlayerObject ob)
{
if (ob == this) return true;
return CurrentMap == ob.CurrentMap && Functions.InRange(CurrentLocation, ob.CurrentLocation, Config.MaxViewRange);
}
public void Despawn()
{
//if (Node == null)
// throw new InvalidOperationException("Node is null, Object already Despawned");
CurrentMap = null;
CurrentCell = null;
RemoveAllObjects();
//Clear Lists
Node?.List?.Remove(Node);
Node = null;
if (Activated)
{
Activated = false;
SEnvir.ActiveObjects?.Remove(this);
}
OnDespawned();
CleanUp();
}
public void SafeDespawn()
{
CurrentMap = null;
CurrentCell = null;
RemoveAllObjects();
if (Node != null)
{
Node.List.Remove(Node);
Node = null;
}
OnSafeDespawn();
CleanUp();
}
public virtual void CleanUp()
{
ActionList?.Clear();
SpellList?.Clear();
Buffs?.Clear();
NearByPlayers?.Clear();
SeenByPlayers?.Clear();
DataSeenByPlayers?.Clear();
PoisonList?.Clear();
GroupMembers?.Clear();
}
public virtual void OnDespawned()
{
for (int i = SpellList.Count - 1; i >= 0; i--)
SpellList[i].Despawn();
}
public virtual void OnSafeDespawn()
{
for (int i = SpellList.Count - 1; i >= 0; i--)
SpellList[i].Despawn();
}
public virtual void RefreshStats() { }
public virtual Cell GetDropLocation(int distance, PlayerObject player)
{
if (CurrentMap == null || CurrentCell == null) return null;
Cell bestCell = null;
int layers = 0;
for (int d = 0; d <= distance; d++)
{
for (int y = CurrentLocation.Y - d; y <= CurrentLocation.Y + d; y++)
{
if (y < 0) continue;
if (y >= CurrentMap.Height) break;
for (int x = CurrentLocation.X - d; x <= CurrentLocation.X + d; x += Math.Abs(y - CurrentLocation.Y) == d ? 1 : d * 2)
{
if (x < 0) continue;
if (x >= CurrentMap.Width) break;
Cell cell = CurrentMap.Cells[x, y]; //Direct Access we've checked the boudaries.
if (cell == null || cell.Movements != null) continue;
bool blocking = false;
int count = 0;
if (cell.Objects != null)
foreach (MapObject ob in cell.Objects)
{
if (ob.Blocking)
{
blocking = true;
break;
}
if (ob.Race != ObjectType.Item) continue;
if (player != null && !ob.CanBeSeenBy(player)) continue;
count++;
}
if (blocking) continue;
if (count == 0) return cell;
if (bestCell != null && count >= layers) continue;
bestCell = cell;
layers = count;
}
}
}
if (bestCell == null || layers >= Config.DropLayers) return null;
return bestCell;
}
public void SetHP(int amount)
{
if (Dead) return;
CurrentHP = Math.Min(amount, Stats[Stat.Health]);
if (CurrentHP <= 0 && !Dead)
{
if (Buffs.Any(x => x.Type == BuffType.CelestialLight))
{
CelestialLightActivate();
return;
}
if (Stats[Stat.ItemReviveTime] > 0 && SEnvir.Now >= ItemReviveTime)
{
ItemRevive();
return;
}
Die();
}
}
public void ChangeHP(int amount)
{
if (Dead) return;
if (amount < 0 && Stats[Stat.ProtectionRing] > 0)
{
if (CurrentMP >= Math.Abs(amount))
{
ChangeMP(amount);
return;
}
if (CurrentMP > 0)
{
amount += CurrentMP; //Amount is Negative so -100 + 15 Remaining mana = 85 Damage
SetMP(0);
}
}
if (CurrentHP + amount > Stats[Stat.Health])
amount = Stats[Stat.Health] - CurrentHP;
CurrentHP += amount;
if (CurrentHP <= 0 && !Dead)
{
if (Buffs.Any(x => x.Type == BuffType.CelestialLight))
{
CelestialLightActivate();
return;
}
if (Stats[Stat.ItemReviveTime] > 0 && SEnvir.Now >= ItemReviveTime)
{
ItemRevive();
return;
}
Die();
}
}
public void SetMP(int amount)
{
CurrentMP = Math.Min(amount, Stats[Stat.Mana]);
}
public void ChangeMP(int amount)
{
if (CurrentMP + amount > Stats[Stat.Mana])
amount = Stats[Stat.Mana] - CurrentMP;
CurrentMP += amount;
}
public virtual void CelestialLightActivate()
{
CurrentHP = Stats[Stat.Health] * Stats[Stat.CelestialLight] / 100;
BuffRemove(BuffType.CelestialLight);
}
public virtual void ItemRevive()
{
CurrentHP = Stats[Stat.Health];
CurrentMP = Stats[Stat.Mana];
ItemReviveTime = SEnvir.Now.AddSeconds(Stats[Stat.ItemReviveTime]);
}
public virtual int Purify(MapObject ob)
{
if (ob != null)
if (ob.Node == null || ob.Dead) return 0;
int result = 0;
List<BuffInfo> buffs = new List<BuffInfo>();
if (CanHelpTarget(ob))
{
result += ob.PoisonList.Count;
for (int i = ob.PoisonList.Count - 1; i >= 0; i--)
{
if (ob.PoisonList[i].Type == PoisonType.Infection) continue;
ob.PoisonList.RemoveAt(i);
}
foreach (BuffInfo buff in ob.Buffs)
{
switch (buff.Type)
{
case BuffType.MagicWeakness:
buffs.Add(buff);
result++;
break;
default:
continue;
}
}
}
if (CanAttackTarget(ob) && (ob.Level <= Level || SEnvir.Random.Next(100) < 42))
{
foreach (BuffInfo buff in ob.Buffs)
{
switch (buff.Type)
{
case BuffType.Heal:
case BuffType.Invisibility:
case BuffType.MagicResistance:
case BuffType.Resilience:
case BuffType.MagicShield:
case BuffType.ElementalSuperiority:
case BuffType.BloodLust:
case BuffType.Defiance:
case BuffType.Might:
case BuffType.ReflectDamage:
case BuffType.JudgementOfHeaven:
case BuffType.StrengthOfFaith:
case BuffType.CelestialLight:
case BuffType.Transparency:
case BuffType.Renounce:
case BuffType.Cloak:
case BuffType.GhostWalk:
case BuffType.LifeSteal:
case BuffType.DarkConversion:
case BuffType.Evasion:
case BuffType.RagingWind:
buffs.Add(buff);
result++;
break;
default:
continue;
}
}
}
for (int i = buffs.Count - 1; i >= 0; i--)
ob.BuffRemove(buffs[i]);
return result;
}
public virtual BuffInfo BuffAdd(BuffType type, TimeSpan remainingTicks, Stats stats, bool visible, bool pause, TimeSpan tickRate)
{
BuffRemove(type);
BuffInfo info;
Buffs.Add(info = SEnvir.BuffInfoList.CreateNewObject());
info.Type = type;
info.Visible = visible;
info.RemainingTime = remainingTicks;
info.TickFrequency = tickRate;
info.Pause = pause;
info.Stats = stats;
if (info.Stats != null && info.Stats.Count > 0)
RefreshStats();
switch (type)
{
case BuffType.PoisonousCloud:
case BuffType.Observable:
case BuffType.TheNewBeginning:
case BuffType.Server:
case BuffType.MapEffect:
case BuffType.Castle:
case BuffType.Guild:
case BuffType.Veteran:
info.IsTemporary = true;
break;
case BuffType.RagingWind:
case BuffType.MagicWeakness:
RefreshStats();
break;
case BuffType.Invisibility:
//Much faster than checking nearby cells?
foreach (MapObject mapOb in CurrentMap.Objects)
{
if (mapOb.Race != ObjectType.Monster) continue;
MonsterObject mob = (MonsterObject)mapOb;
if (mob.Target == this && !mob.CoolEye)
{
mob.Target = null;
mob.SearchTime = SEnvir.Now;
}
}
break;
case BuffType.Cloak:
case BuffType.Transparency:
//Much faster than checking nearby cells?
foreach (MapObject mapOb in CurrentMap.Objects)
{
if (mapOb.Race != ObjectType.Monster) continue;
MonsterObject mob = (MonsterObject)mapOb;
if (mob.Target == this)
{
mob.Target = null;
mob.SearchTime = SEnvir.Now;
}
}
RemoveAllObjects();
AddAllObjects();
if (((PlayerObject)this).Companion != null)
if (Race == ObjectType.Player)
{
((PlayerObject)this).Companion.RemoveAllObjects();
((PlayerObject)this).Companion.AddAllObjects();
}
break;
}
if (!info.Visible) return info;
Broadcast(new S.ObjectBuffAdd { ObjectID = ObjectID, Type = type });
return info;
}
public virtual void BuffRemove(BuffInfo info, bool needDelete = true)
{
if (info.Visible)
Broadcast(new S.ObjectBuffRemove { ObjectID = ObjectID, Type = info.Type });
Buffs.Remove(info);
if (info.Stats != null && info.Stats.Count > 0)
RefreshStats();
switch (info.Type)
{
case BuffType.Cloak:
case BuffType.Transparency:
BuffRemove(BuffType.GhostWalk);
RemoveAllObjects();
AddAllObjects();
if (((PlayerObject)this).Companion != null)
if (Race == ObjectType.Player)
{
((PlayerObject)this).Companion.RemoveAllObjects();
((PlayerObject)this).Companion.AddAllObjects();
}
break;
case BuffType.RagingWind:
case BuffType.MagicWeakness:
RefreshStats();
break;
}
switch (info.Type)
{
case BuffType.Cloak:
case BuffType.Transparency:
case BuffType.Invisibility:
//Much faster than checking nearby cells?
foreach (MapObject mapOb in CurrentMap.Objects)
{
if (mapOb.Race != ObjectType.Monster) continue;
MonsterObject mob = (MonsterObject) mapOb;
mob.SearchTime = DateTime.MinValue;
}
break;
}
if (needDelete)
info.Delete();
}
public void BuffRemove(BuffType type)
{
BuffInfo info = Buffs.FirstOrDefault(x => x.Type == type);
if (info != null)
BuffRemove(info);
}
public virtual int Attacked(MapObject attacker, int power, Element elemnet, bool canReflect = true, bool ignoreShield = false, bool canCrit = true, bool canStruck = true) { return 0; }
public List<MapObject> GetTargets(Map map, Point location, int radius)
{
List<MapObject> targets = new List<MapObject>();
for (int y = location.Y - radius; y <= location.Y + radius; y++)
{
if (y < 0) continue;
if (y >= map.Height) break;
for (int x = location.X - radius; x <= location.X + radius; x++)
{
if (x < 0) continue;
if (x >= map.Width) break;
Cell cell = map.Cells[x, y];
if (cell == null) continue;
if (cell.Objects == null) continue;
foreach (MapObject ob in cell.Objects)
{
if (!CanAttackTarget(ob)) continue;
targets.Add(ob);
}
}
}
return targets;
}
public List<MapObject> GetAllObjects(Point location, int radius)
{
List<MapObject> obs = new List<MapObject>();
for (int y = location.Y - radius; y <= location.Y + radius; y++)
{
if (y < 0) continue;
if (y >= CurrentMap.Height) break;
for (int x = location.X - radius; x <= location.X + radius; x++)
{
if (x < 0) continue;
if (x >= CurrentMap.Width) break;
Cell cell = CurrentMap.Cells[x, y];
if (cell == null || cell.Objects == null) continue;
obs.AddRange(cell.Objects);
}
}
return obs;
}
public virtual bool CanHelpTarget(MapObject ob)
{
return false;
}
public virtual bool CanAttackTarget(MapObject ob)
{
return false;
}
public virtual void Dodged()
{
DisplayMiss = true;
}
public virtual void Blocked()
{
DisplayBlock = true;
}
public virtual void Critical()
{
DisplayCrit = true;
}
public virtual bool ApplyPoison(Poison p)
{
if (Dead) return false;
if (SEnvir.Random.Next(100) < Stats[Stat.PoisonResistance]) return false;
foreach (Poison poison in PoisonList)
{
if (poison.Type != p.Type) continue;
if (poison.Value > p.Value) return false;
PoisonList.Remove(poison);
break;
}
//Check Pets target
PoisonList.Add(p);
return true;
}
public virtual void Die()
{
Dead = true;
BuffRemove(BuffType.Heal);
BuffRemove(BuffType.DragonRepulse);
PoisonList.Clear();
Broadcast(new S.ObjectDied { ObjectID = ObjectID, Direction = Direction, Location = CurrentLocation });
}
public abstract Packet GetInfoPacket(PlayerObject ob);
public abstract Packet GetDataPacket(PlayerObject ob);
public void Broadcast(Packet p)
{
foreach (PlayerObject player in SeenByPlayers)
player.Enqueue(p);
}
public virtual int Pushed(MirDirection direction, int distance)
{
int count = 0;
if (Dead) return count;
if (Buffs.Any(x => x.Type == BuffType.Endurance || x.Type == BuffType.DragonRepulse)) return count;
PreventSpellCheck = true;
for (int i = 0; i < distance; i++)
{
Cell cell = CurrentMap.GetCell(Functions.Move(CurrentLocation, direction));
if (cell == null || cell.IsBlocking(this, false)) break;
if (Race == ObjectType.Monster && cell.Movements != null) break;
Direction = Functions.ShiftDirection(direction, 4);
ActionTime += Globals.TurnTime;
CurrentCell = cell.GetMovement(this); //Repel same direction across map ?
RemoveAllObjects();
AddAllObjects();
Broadcast(new S.ObjectPushed { ObjectID = ObjectID, Direction = Direction, Location = CurrentLocation});
count++;
}
PreventSpellCheck = false;
/* if (count > 0 && checkSpells)
CheckSpellObjects();*/
return count;
}
public bool InGroup(MapObject ob)
{
//Ob can be Null
if (ob != null)
return ob.GroupMembers != null && ob.GroupMembers == GroupMembers;
else return false;
}
public int GetDC()
{
int min = Stats[Stat.MinDC];
int max = Stats[Stat.MaxDC];
int luck = Stats[Stat.Luck];
if (min < 0) min = 0;
if (min >= max) return max;
if (luck > 0)
{
if (luck >= 10) return max;
if (SEnvir.Random.Next(10) < luck) return max;
}
else if (luck < 0)
{
if (luck < -SEnvir.Random.Next(10)) return min;
}
return SEnvir.Random.Next(min, max + 1);
}
public int GetMC()
{
int min = Stats[Stat.MinMC];
int max = Stats[Stat.MaxMC];
int luck = Stats[Stat.Luck];
if (min < 0) min = 0;
if (min >= max) return max;
if (luck > 0)
{
if (luck >= 10) return max;
if (SEnvir.Random.Next(10) < luck) return max;
}
else if (luck < 0)
{
if (luck < -SEnvir.Random.Next(10)) return min;
}
return SEnvir.Random.Next(min, max + 1);
}
public int GetSC()
{
int min = Stats[Stat.MinSC];
int max = Stats[Stat.MaxSC];
int luck = Stats[Stat.Luck];
if (min < 0) min = 0;
if (min >= max) return max;
if (luck > 0)
{
if (luck >= 10) return max;
if (SEnvir.Random.Next(10) < luck) return max;
}
else if (luck < 0)
{
if (luck < -SEnvir.Random.Next(10)) return min;
}
return SEnvir.Random.Next(min, max + 1);
}
public int GetSP()
{
int min = Math.Min(Stats[Stat.MinMC], Stats[Stat.MinSC]);
int max = Math.Min(Stats[Stat.MaxMC], Stats[Stat.MaxSC]);
int luck = Stats[Stat.Luck];
if (min < 0) min = 0;
if (min >= max) return max;
if (luck > 0)
{
if (luck >= 10) return max;
if (SEnvir.Random.Next(10) < luck) return max;
}
else if (luck < 0)
{
if (luck < -SEnvir.Random.Next(10)) return min;
}
return SEnvir.Random.Next(min, max + 1);
}
public int GetAC()
{
int min = Stats[Stat.MinAC];
int max = Stats[Stat.MaxAC];
if (min < 0) min = 0;
if (min >= max) return max;
return SEnvir.Random.Next(min, max + 1);
}
public int GetMR()
{
int min = Stats[Stat.MinMR];
int max = Stats[Stat.MaxMR];
if (min < 0) min = 0;
if (min >= max) return max;
return SEnvir.Random.Next(min, max + 1);
}
}
public class Poison
{
public MapObject Owner;
public PoisonType Type;
public int Value;
public TimeSpan TickFrequency;
public int TickCount;
public DateTime TickTime;
public object Extra;
}
}