mirror of
https://github.com/raphaelcheung/zircon-legend-server.git
synced 2026-10-21 00:45:52 +08:00
1705 lines
56 KiB
C#
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;
|
|
}
|
|
|
|
}
|