Files

3670 lines
146 KiB
C#

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Threading;
using Library;
using Library.Network;
using Library.Network.ServerPackets;
using Library.SystemModels;
using Server.DBModels;
using Server.Envir;
using Zircon.Server.Models.Monsters;
using S = Library.Network.ServerPackets;
namespace Zircon.Server.Models
{
public class MonsterObject : MapObject
{
private struct SummonLevelSetting(uint tick, Color color)
{
public readonly uint Ticks = tick;
public readonly Color NameColor = color;
}
private static SummonLevelSetting[] arrSummonLevelSetting;
private static void DefaultGrowUp()
{
arrSummonLevelSetting = [
new SummonLevelSetting(40, Color.FromArgb(255, 180, 200, 200)),
new SummonLevelSetting(40, Color.FromArgb(255, 150, 200, 200)),
new SummonLevelSetting(50, Color.FromArgb(255, 120, 170, 200)),
new SummonLevelSetting(50, Color.FromArgb(255, 90, 140, 200)),
new SummonLevelSetting(60, Color.FromArgb(255, 60, 110, 200)), // 5级
new SummonLevelSetting(60, Color.FromArgb(255, 30, 80, 200)),
new SummonLevelSetting(60, Color.FromArgb(255, 0, 50, 200)),
new SummonLevelSetting(80, Color.FromArgb(255, 0, 10, 200)),
new SummonLevelSetting(100, Color.FromArgb(255, 0, 0, 220)),
new SummonLevelSetting(100, Color.FromArgb(255, 0, 0, 255)), // 10级
new SummonLevelSetting(120, Color.FromArgb(255, 120, 100, 180)),
new SummonLevelSetting(130, Color.FromArgb(255, 200, 170, 100)),
];
SEnvir.Log($"召唤怪成长数值使用默认值.");
}
static MonsterObject()
{
if (string.IsNullOrEmpty(Config.SummonMonsterGrowUpFile) || !File.Exists(Config.SummonMonsterGrowUpFile))
{
SEnvir.Log($"召唤怪成长数值文件为空或者无效.");
DefaultGrowUp();
return;
}
try
{
using (var stream = File.OpenRead(Config.SummonMonsterGrowUpFile))
{
var reader = new StreamReader(stream);
if (reader != null)
{
string? line;
List<SummonLevelSetting> tmp = new List<SummonLevelSetting>();
while ((line = reader.ReadLine()) != null)
{
string[] parts = line.Trim().Split(' ');
if (parts.Length != 2)
{
SEnvir.Log($"召唤怪成长数据格式不正确:{line}");
DefaultGrowUp();
return;
}
if (!uint.TryParse(parts[0], out uint exp))
{
SEnvir.Log($"召唤怪成长数据格式不正确:{line}");
DefaultGrowUp();
return;
}
string[] colors = parts[1].Split(",");
if (colors.Length != 3)
{
SEnvir.Log($"召唤怪成长数据格式不正确:{line}");
DefaultGrowUp();
return;
}
if (!int.TryParse(colors[0], out int r) || !int.TryParse(colors[1], out int g) || !int.TryParse(colors[2], out int b))
{
SEnvir.Log($"召唤怪成长数据格式不正确:{line}");
DefaultGrowUp();
return;
}
tmp.Add(new SummonLevelSetting(exp, Color.FromArgb(255, r, g, b)));
}
if (tmp.Count <= 0)
{
SEnvir.Log($"召唤怪成长数据文件为空:{Config.SummonMonsterGrowUpFile}");
DefaultGrowUp();
return;
}
arrSummonLevelSetting = tmp.ToArray();
return;
}
}
}
catch(Exception ex)
{
DefaultGrowUp();
SEnvir.Log($"加载召唤怪成长数据出现异常【{Config.SummonMonsterGrowUpFile}】,将使用默认数据");
SEnvir.Log(ex);
}
DefaultGrowUp();
}
public override ObjectType Race {get{return ObjectType.Monster;}}
public sealed override MirDirection Direction { get; set; }
public DateTime SearchTime { get; set; }
public DateTime RoamTime, EXPOwnerTime, DeadTime, RageTime;
public DateTime TameTime { get; set; }
public TimeSpan SearchDelay = TimeSpan.FromSeconds(3),
RoamDelay = TimeSpan.FromSeconds(2),
EXPOwnerDelay = TimeSpan.FromSeconds(20);
public MonsterInfo MonsterInfo;
private MapObject? Killer = null;
public SpawnInfo SpawnInfo;
public int DropSet;
private int AttackTicks { get; set; } = 0;
public int QierbianMin = 3000;
public int QierbianRandom = 0;
public MapObject Target
{
get { return _Target; }
set
{
if (_Target == value) return;
_Target = value;
if (_Target == null)
SearchTime = DateTime.MinValue;
}
}
private MapObject _Target;
public bool PlayerTagged { get; set; }
public bool Siege { get; set; } = false;
public Dictionary<MonsterInfo, int> SpawnList = new Dictionary<MonsterInfo, int>();
public bool Skeleton;
#region EXPOwner
public PlayerObject EXPOwner
{
get { return _EXPOwner; }
set
{
if (_EXPOwner == value) return;
PlayerObject oldValue = _EXPOwner;
_EXPOwner = value;
OnEXPOwnerChanged(oldValue, value);
}
}
private PlayerObject _EXPOwner;
public virtual void OnEXPOwnerChanged(PlayerObject oValue, PlayerObject nValue)
{
if (oValue != null)
oValue.TaggedMonsters.Remove(this);
if (nValue != null)
nValue.TaggedMonsters.Add(this);
}
#endregion
public Dictionary<AccountInfo, List<UserItem>> Drops { get; set; }
public virtual decimal Experience {get{return MonsterInfo.Experience;}}
public decimal ExtraExperienceRate = 0;
public bool Passive, NeedHarvest, AvoidFireWall;
public int HarvestCount;
public int DeathCloudDurationMin = 4000, DeathCloudDurationRandom = 0;
public int MoveDelay;
public int AttackDelay;
public List<MonsterObject> MinionList = new List<MonsterObject>();
public MonsterObject Master;
public int MaxMinions = 20;
public PlayerObject PetOwner;
public HashSet<UserMagic> Magics = new HashSet<UserMagic>();
public int SummonLevel { get; set; } = 0;
public int SummonMagicLevel { get; set; } = 0;
public int SummonBase { get; set; } = 0;
public int SummonCritical { get; set; } = 0;
public int SummonCriticalDamage { get; set; } = 0;
public int ViewRange
{
get { return PoisonList.Any(x => x.Type == PoisonType.Abyss) ? 2 : MonsterInfo.ViewRange; }
}
public PoisonType PoisonType;
public int PoisonRate = 10;
public int PoisonTicks = 5;
public int PoisonFrequency = 2;
public bool IgnoreShield { get; set; }
public bool EasterEventMob, HalloweenEventMob, ChristmasEventMob;
public int MapGoldRate { get; set; }
public int MapExperienceRate { get; set; }
public int MapDamageRate { get; set; }
public int MapHealthRate { get; set; }
public int MapDropRate { get; set; }
public Element AttackElement {get{return Stats.GetAffinityElement();}}
public override bool CanMove {get{return base.CanMove && (Poison & PoisonType.Silenced) != PoisonType.Silenced && MoveDelay > 0 && (PetOwner == null || PetOwner.PetMode == PetMode.Both || PetOwner.PetMode == PetMode.Move || PetOwner.PetMode == PetMode.PvP);}}
public override bool CanAttack { get { return base.CanAttack && (Poison & PoisonType.Silenced) != PoisonType.Silenced && AttackDelay > 0 && (PetOwner == null || PetOwner.PetMode == PetMode.Both || PetOwner.PetMode == PetMode.Attack || PetOwner.PetMode == PetMode.PvP); } }
public DateTime LifeLimit { get; set; } = DateTime.MinValue;
public DateTime PendingDespawnTime { get; set; } = DateTime.MaxValue;
public int PendingDespawnOwnerIndex { get; set; } = -1;
public static MonsterObject GetMonster(MonsterInfo monsterInfo)
{
switch (monsterInfo.AI)
{
case -1:
return new Guard { MonsterInfo = monsterInfo };
case 1:
return new MonsterObject { MonsterInfo = monsterInfo, Passive = true, NeedHarvest = true, HarvestCount = 2 };
case 2:
return new MonsterObject { MonsterInfo = monsterInfo, Passive = true, NeedHarvest = true, HarvestCount = 3 };
case 3:
return new MonsterObject { MonsterInfo = monsterInfo, NeedHarvest = true, HarvestCount = 3 };
case 4:
return new TreeMonster { MonsterInfo = monsterInfo };
case 5:
return new CarnivorousPlant { MonsterInfo = monsterInfo, NeedHarvest = true, HarvestCount = 2 };
case 6:
return new SpittingSpider { MonsterInfo = monsterInfo, NeedHarvest = true, HarvestCount = 2, PoisonType = PoisonType.Green };
case 7:
return new SkeletonAxeThrower { MonsterInfo = monsterInfo };
case 8:
return new MonsterObject { MonsterInfo = monsterInfo, NeedHarvest = true, HarvestCount = 2, PoisonType = PoisonType.Paralysis, PoisonTicks = 1, PoisonFrequency = 5, PoisonRate = 12 };
case 9:
return new GhostSorcerer { MonsterInfo = monsterInfo };
case 10:
return new GhostMage { MonsterInfo = monsterInfo };
case 11:
return new VoraciousGhost { MonsterInfo = monsterInfo };
case 12:
return new HealerAnt { MonsterInfo = monsterInfo };
case 13:
return new LordNiJae { MonsterInfo = monsterInfo };
case 14:
return new SpittingSpider { MonsterInfo = monsterInfo, PoisonType = PoisonType.Green };
case 15:
return new MonsterObject { MonsterInfo = monsterInfo };
case 16:
return new UmaKing { MonsterInfo = monsterInfo };
case 17:
return new ArachnidGrazer
{
MonsterInfo = monsterInfo,
SpawnList = { {SEnvir.MonsterInfoList.Binding.First(x => x.Flag == MonsterFlag.Larva), 1} }
};
case 18:
return new Larva { MonsterInfo = monsterInfo, PoisonType = PoisonType.Green };
case 19:
return new RedMoonTheFallen { MonsterInfo = monsterInfo };
case 20:
return new SkeletonAxeThrower { MonsterInfo = monsterInfo, FearRate = 2, FearDuration = 4 };
case 21:
return new ZumaGuardian { MonsterInfo = monsterInfo };
case 22:
return new ZumaKing
{
MonsterInfo = monsterInfo,
SpawnList =
{
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.ZumaArcherMonster;}), 50},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.ZumaFanaticMonster;}), 25},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.ZumaGuardianMonster;}), 25},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.ZumaKeeperMonster;}), 1},
}
};
case 23:
return new Monkey { MonsterInfo = monsterInfo, PoisonType = PoisonType.Green };
case 24:
return new Monkey { MonsterInfo = monsterInfo, PoisonType = PoisonType.Red };
case 25:
return new EvilElephant { MonsterInfo = monsterInfo };
case 26:
return new NumaMage { MonsterInfo = monsterInfo };
case 27:
return new GhostMage { MonsterInfo = monsterInfo };
case 28:
return new WindfurySorcerer { MonsterInfo = monsterInfo };
case 29:
return new SkeletonAxeThrower { MonsterInfo = monsterInfo };
case 30:
return new NetherworldGate { MonsterInfo = monsterInfo };
case 31:
return new SonicLizard { MonsterInfo = monsterInfo };
case 33:
return new GiantLizard { MonsterInfo = monsterInfo, AttackRange = 9, IgnoreShield = true };
case 34:
return new SkeletonAxeThrower { MonsterInfo = monsterInfo, AttackRange = 9 };
case 35:
return new MonsterObject { MonsterInfo = monsterInfo };
case 36:
return new NumaMage { MonsterInfo = monsterInfo };
case 37:
return new MonsterObject { MonsterInfo = monsterInfo };
case 38:
return new BanyaLeftGuard { MonsterInfo = monsterInfo };
case 39:
return new MonsterObject { MonsterInfo = monsterInfo };
case 40:
return new MonsterObject { MonsterInfo = monsterInfo };
case 41:
return new EmperorSaWoo { MonsterInfo = monsterInfo };
case 42:
return new SpittingSpider { MonsterInfo = monsterInfo };
case 43:
return new ArchLichTaedu
{
MonsterInfo = monsterInfo,
SpawnList =
{
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.BoneArcher;}), 90},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.BoneSoldier;}), 15},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.BoneBladesman;}), 15},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.BoneCaptain;}), 15},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.SkeletonEnforcer;}), 1},
}
};
case 44:
return new WedgeMothLarva
{
MonsterInfo = monsterInfo,
SpawnList = { {SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.LesserWedgeMoth;}), 1 }}
};
case 45:
return new RazorTusk { MonsterInfo = monsterInfo };
case 46:
return new SpittingSpider { MonsterInfo = monsterInfo, PoisonType = PoisonType.Red, PoisonTicks = 1, PoisonFrequency = 10, PoisonRate = 25 };
case 47:
return new SpittingSpider { MonsterInfo = monsterInfo, PoisonType = PoisonType.Green, PoisonTicks = 7, PoisonRate = 15 };
case 48:
return new SonicLizard { MonsterInfo = monsterInfo, IgnoreShield = true };
case 49:
return new GiantLizard { MonsterInfo = monsterInfo, AttackRange = 8, PoisonType = PoisonType.Paralysis, PoisonTicks = 1, PoisonFrequency = 5 };
case 50:
return new GiantLizard { MonsterInfo = monsterInfo, AttackRange = 8 };
case 52:
return new WhiteBone() { MonsterInfo = monsterInfo };
case 53:
return new Shinsu { MonsterInfo = monsterInfo };
case 54:
return new GiantLizard { MonsterInfo = monsterInfo, RangeCooldown = TimeSpan.FromSeconds(5) };
case 56:
return new CorrosivePoisonSpitter { MonsterInfo = monsterInfo, PoisonType = PoisonType.Green, PoisonTicks = 7, PoisonRate = 15, IgnoreShield = true };
case 57:
return new CorrosivePoisonSpitter { MonsterInfo = monsterInfo };
case 58:
return new Stomper { MonsterInfo = monsterInfo };
case 59:
return new CrimsonNecromancer() { MonsterInfo = monsterInfo };
case 60:
return new ChaosKnight() { MonsterInfo = monsterInfo };
case 61:
return new PachontheChaosbringer { MonsterInfo = monsterInfo };
case 62:
return new NumaHighMage { MonsterInfo = monsterInfo };
case 63:
return new NumaStoneThrower { MonsterInfo = monsterInfo };
case 64:
return new Monkey { MonsterInfo = monsterInfo };
case 65:
return new IcyGoddess { MonsterInfo = monsterInfo, FindRange = 3 };
case 66:
return new IcySpiritWarrior { MonsterInfo = monsterInfo, PoisonType = PoisonType.Paralysis, PoisonTicks = 1, PoisonFrequency = 5, PoisonRate = 25 };
case 67:
return new IcySpiritGeneral { MonsterInfo = monsterInfo, IgnoreShield = true,
};
case 68:
return new Warewolf { MonsterInfo = monsterInfo,
IgnoreShield = true,
};
case 69:
return new JinamStoneGate { MonsterInfo = monsterInfo };
case 70:
return new FrostLordHwa { MonsterInfo = monsterInfo };
case 71:
return new BanyoWarrior { MonsterInfo = monsterInfo };
case 72:
return new BanyoCaptain { MonsterInfo = monsterInfo };
case 74:
return new BanyoLordGuzak
{
MonsterInfo = monsterInfo,
SpawnList =
{
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.BanyoCaptain;}), 2},
}
};
case 75:
return new DepartedMonster
{
MonsterInfo = monsterInfo,
SpawnList = { {SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.MatureEarwig;}), 1} }
};
case 76:
return new DepartedMonster
{
MonsterInfo = monsterInfo,
SpawnList = { {SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.GoldenArmouredBeetle;}), 1 }}
};
case 77:
return new EnragedLordNiJae
{
MonsterInfo = monsterInfo,
SpawnList = { {SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.Millipede;}), 1} },
MaxMinions = 200,
};
case 78:
return new JinchonDevil { MonsterInfo = monsterInfo };
case 79:
return new GiantLizard { MonsterInfo = monsterInfo, AttackRange = 10, RangeCooldown = TimeSpan.FromSeconds(5) };
case 80:
return new SunFeralWarrior
{
MonsterInfo = monsterInfo,
SpawnList =
{
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.FerociousFlameDemon;}),5},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.FlameDemon;}),1},
}
};
case 81:
return new MoonFeralWarrior
{
MonsterInfo = monsterInfo
};
case 82:
return new OxFeralGeneral
{
MonsterInfo = monsterInfo,
IgnoreShield = true,
};
case 83:
return new FlameDemon
{
MonsterInfo = monsterInfo,
Min = -2,
Max = 2,
};
case 84:
return new WingedHorror
{
MonsterInfo = monsterInfo,
RangeChance = 1,
};
case 85:
return new EmperorSaWoo { MonsterInfo = monsterInfo, PoisonType = PoisonType.Paralysis, PoisonTicks = 1, PoisonFrequency = 5, PoisonRate = 8 };
case 86:
return new FlameDemon
{
MonsterInfo = monsterInfo,
Passive = true,
Min = 0,
Max = 8,
};
case 87:
return new OmaWarlord
{
MonsterInfo = monsterInfo,
PoisonType = PoisonType.Abyss,
PoisonTicks = 1,
PoisonFrequency = 7,
PoisonRate = 15
};
case 88:
return new GoruSpearman
{
MonsterInfo = monsterInfo,
};
case 89:
return new GoruArcher
{
MonsterInfo = monsterInfo,
PoisonType = PoisonType.Silenced,
PoisonTicks = 1,
PoisonFrequency = 5,
PoisonRate = 10
};
case 90:
return new OmaWarlord
{
MonsterInfo = monsterInfo,
PoisonType = PoisonType.Paralysis,
PoisonTicks = 1,
PoisonFrequency = 5,
PoisonRate = 25
};
case 91:
return new EnragedArchLichTaedu
{
MonsterInfo = monsterInfo,
MinSpawn = 5,
RandomSpawn = 5,
PoisonType = PoisonType.Red,
PoisonTicks = 1,
PoisonFrequency = 25,
PoisonRate = 5,
SpawnList =
{
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.GoruArcher;}), 10},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.GoruGeneral;}),5},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.GoruSpearman;}),5},
}
};
case 92:
return new GiantLizard { MonsterInfo = monsterInfo, AttackRange = 9 };
case 93:
return new EscortCommander { MonsterInfo = monsterInfo };
case 94:
return new FieryDancer { MonsterInfo = monsterInfo };
case 95:
return new FieryDancer
{
MonsterInfo = monsterInfo,
PoisonType = PoisonType.Paralysis,
PoisonTicks = 1,
PoisonFrequency = 5,
PoisonRate = 15,
};
case 96:
return new QueenOfDawn { MonsterInfo = monsterInfo };
case 97:
return new SonicLizard { MonsterInfo = monsterInfo, IgnoreShield = true, Range = 5 };
case 98:
return new YumgonWitch
{
MonsterInfo = monsterInfo,
AoEElement = Element.Lightning
};
case 99:
return new JinhwanSpirit
{
MonsterInfo = monsterInfo,
SpawnList =
{
{monsterInfo, 1},
}
};
case 100:
return new YumgonWitch
{
MonsterInfo = monsterInfo,
};
case 101:
return new DragonQueen
{
MonsterInfo = monsterInfo,
DragonLordInfo = SEnvir.MonsterInfoList.Binding.First(x => x.Flag == MonsterFlag.DragonLord),
SpawnList =
{
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.OYoungBeast;}), 2},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.YumgonWitch;}), 2},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.MaWarden;}), 2},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.MaWarlord;}), 2},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.JinhwanSpirit;}), 2},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.JinhwanGuardian;}), 2},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.OyoungGeneral;}), 2},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.YumgonGeneral;}), 2},
}
};
case 102:
return new DragonLord { MonsterInfo = monsterInfo,
SpawnList =
{
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.OYoungBeast;}), 10000},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.YumgonWitch;}), 10000},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.MaWarden;}), 10000},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.MaWarlord;}), 10000},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.JinhwanSpirit;}), 10000},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.JinhwanGuardian;}), 10000},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.OyoungGeneral;}), 10000},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.YumgonGeneral;}), 10000},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.DragonLord;}), 1},
}
};
case 103:
return new GiantLizard { MonsterInfo = monsterInfo, AttackRange = 5 };
case 104:
return new FerociousIceTiger { MonsterInfo = monsterInfo };
case 105:
return new GiantLizard { MonsterInfo = monsterInfo, AttackRange = 5, IgnoreShield = true, CanPvPRange = true };
case 106:
return new GiantLizard { MonsterInfo = monsterInfo, AttackRange = 7, CanPvPRange = true };
case 107:
return new SamaFireGuardian { MonsterInfo = monsterInfo };
case 108:
return new SamaIceGuardian { MonsterInfo = monsterInfo };
case 109:
return new SamaLightningGuardian { MonsterInfo = monsterInfo };
case 110:
return new SamaWindGuardian { MonsterInfo = monsterInfo };
case 111:
return new SamaPhoenix { MonsterInfo = monsterInfo };
case 112:
return new SamaBlack { MonsterInfo = monsterInfo };
case 113:
return new SamaBlue { MonsterInfo = monsterInfo };
case 114:
return new SamaWhite { MonsterInfo = monsterInfo };
case 115:
return new SamaProphet
{
MonsterInfo = monsterInfo,
SpawnList =
{
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.SamaSorcerer;}), 1},
}
};
case 116:
return new SamaScorcer()
{
MonsterInfo = monsterInfo,
};
case 117:
return new BanyoWarrior { MonsterInfo = monsterInfo, DoubleDamage = true };
case 118:
return new OmaMage { MonsterInfo = monsterInfo };
case 119:
return new MonsterObject
{
MonsterInfo = monsterInfo,
PoisonType = PoisonType.Silenced,
PoisonTicks = 1,
PoisonFrequency = 5,
PoisonRate = 10
};
case 120:
return new DoomClaw()
{
MonsterInfo = monsterInfo,
};
case 121:
return new PinkBat { MonsterInfo = monsterInfo };
case 122:
return new QuartzTurtleSub
{
MonsterInfo = monsterInfo,
SpawnList =
{
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.QuartzMiniTurtle;}), 2},
}
};
case 123:
return new Larva
{
MonsterInfo = monsterInfo,
Range = 3,
};
case 124:
return new QuartzTree
{
MonsterInfo = monsterInfo,
SubBossInfo = SEnvir.MonsterInfoList.Binding.First(x => x.Flag == MonsterFlag.QuartzTurtleSub),
SpawnList =
{
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.QuartzBlueBat;}), 20},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.QuartzPinkBat;}), 20},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.QuartzBlueCrystal;}), 20},
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.QuartzRedHood;}), 2},
}
};
case 125:
return new CarnivorousPlant { MonsterInfo = monsterInfo, HideRange = 1, FindRange = 1 };
case 126:
return new MonasteryBoss
{
MonsterInfo = monsterInfo,
SpawnList =
{
{SEnvir.MonsterInfoList.Binding.First(delegate(MonsterInfo x) {return x.Flag == MonsterFlag.Sacrafice;}), 1},
}
};
case 127:
return new JinchonDevil {MonsterInfo = monsterInfo, CastDelay = TimeSpan.FromSeconds(8), DeathCloudDurationMin = 2000, DeathCloudDurationRandom = 5000};
case 153:
FireBird monster140 = new FireBird();
monster140.MonsterInfo = monsterInfo;
return (MonsterObject)monster140;
case 160:
GardenSoldier monster146 = new GardenSoldier();
monster146.MonsterInfo = monsterInfo;
return (MonsterObject)monster146;
case 161:
GardenDefender monster147 = new GardenDefender();
monster147.MonsterInfo = monsterInfo;
return (MonsterObject)monster147;
case 162:
RedBlossom monster148 = new RedBlossom();
monster148.MonsterInfo = monsterInfo;
return (MonsterObject)monster148;
case 163:
BlueBlossom monster149 = new BlueBlossom();
monster149.MonsterInfo = monsterInfo;
return (MonsterObject)monster149;
case 164:
QitiandashengAI monster150 = new QitiandashengAI();
monster150.MonsterInfo = monsterInfo;
monster150.CastDelay = TimeSpan.FromSeconds(8.0);
monster150.QierbianMin = 3000;
monster150.QierbianRandom = 0;
return (MonsterObject)monster150;
default:
return new MonsterObject { MonsterInfo = monsterInfo };
}
}
public MonsterObject()
{
Stats = new Stats();
Direction = (MirDirection)SEnvir.Random.Next(8);
}
public void QierBian(Point location)
{
bool flag = true;
foreach (Cell cell in CurrentMap.GetCells(location, 0, 2))
{
ActionList.Add(new DelayedAction(SEnvir.Now.AddMilliseconds(500.0), ActionType.DelayMagic, new object[4]
{
MagicType.Qierbian,
cell,
flag,
location
}));
flag = false;
}
}
protected override void OnSpawned()
{
base.OnSpawned();
if (SpawnInfo != null && SpawnInfo.Info.EasterEventChance > 0 && SEnvir.Now < Config.EasterEventEnd)
EasterEventMob = SEnvir.Random.Next(SpawnInfo.Info.EasterEventChance) == 0;
int offset = 1000000;
MapHealthRate = SEnvir.Random.Next(CurrentMap.Info.MonsterHealth + offset, CurrentMap.Info.MaxMonsterHealth + offset);
MapDamageRate = SEnvir.Random.Next(CurrentMap.Info.MonsterDamage + offset, CurrentMap.Info.MaxMonsterDamage + offset);
if (MapHealthRate >= CurrentMap.Info.ExperienceRate && MapHealthRate <= CurrentMap.Info.MaxExperienceRate)
MapExperienceRate = MapHealthRate;
else
MapExperienceRate = SEnvir.Random.Next(CurrentMap.Info.ExperienceRate + offset, CurrentMap.Info.MaxExperienceRate + offset);
MapDropRate = SEnvir.Random.Next(CurrentMap.Info.DropRate + offset, CurrentMap.Info.MaxDropRate + offset);
MapGoldRate = SEnvir.Random.Next(CurrentMap.Info.GoldRate + offset, CurrentMap.Info.MaxGoldRate + offset);
MapHealthRate -= offset;
MapDamageRate -= offset;
MapExperienceRate -= offset;
MapDropRate -= offset;
MapGoldRate -= offset;
RefreshStats();
CurrentHP = Stats[Stat.Health];
DisplayHP = CurrentHP;
RegenTime = SEnvir.Now.AddMilliseconds(SEnvir.Random.Next((int)RegenDelay.TotalMilliseconds));
SearchTime = SEnvir.Now.AddMilliseconds(SEnvir.Random.Next((int)SearchDelay.TotalMilliseconds));
RoamTime = SEnvir.Now.AddMilliseconds(SEnvir.Random.Next((int)RoamDelay.TotalMilliseconds));
ActionTime = SEnvir.Now.AddSeconds(1);
Level = MonsterInfo.Level;
CoolEye = SEnvir.Random.Next(100) < MonsterInfo.CoolEye;
AddAllObjects();
Activate();
}
public override void RefreshStats()
{
SummonLevel = Math.Min(arrSummonLevelSetting.Length - (Config.技能最高等级 - SummonMagicLevel), SummonLevel);
base.RefreshStats();
Stats.Clear();
Stats.Add(MonsterInfo.Stats);
ApplyBonusStats();
MoveDelay = MonsterInfo.MoveDelay * (36 - SummonLevel) / 36;
int tmp = Dead ? 24 : 36;
AttackDelay = MonsterInfo.AttackDelay * (tmp - SummonLevel) / tmp;
if (SummonLevel > 0)
AttackDelay = Math.Max(400, AttackDelay);
int live_rate = MonsterInfo.Undead ? 5 : 10;
int attack_rate = MonsterInfo.Undead ? 6 : 10;
if (SummonLevel > 0)
{
Stats[Stat.Health] += Stats[Stat.Health] * SummonLevel / live_rate + SummonBase * SummonLevel * 2;
Stats[Stat.MinAC] += Stats[Stat.MinAC] * SummonLevel / live_rate;
Stats[Stat.MaxAC] += Stats[Stat.MaxAC] * SummonLevel / live_rate;
Stats[Stat.MinMR] += Stats[Stat.MinMR] * SummonLevel / live_rate;
Stats[Stat.MaxMR] += Stats[Stat.MaxMR] * SummonLevel / live_rate;
Stats[Stat.MinDC] += Stats[Stat.MinDC]*SummonLevel / attack_rate;
Stats[Stat.MaxDC] += Stats[Stat.MaxDC]*SummonLevel / attack_rate;
Stats[Stat.MinMC] += Stats[Stat.MinMC]*SummonLevel / attack_rate;
Stats[Stat.MaxMC] += Stats[Stat.MaxMC]*SummonLevel / attack_rate;
Stats[Stat.MinSC] += Stats[Stat.MinSC]*SummonLevel / attack_rate;
Stats[Stat.MaxSC] += Stats[Stat.MaxSC]*SummonLevel / attack_rate;
Stats[Stat.Accuracy] += Stats[Stat.Accuracy]*SummonLevel / attack_rate;
Stats[Stat.Agility] += Stats[Stat.Agility]*SummonLevel / attack_rate;
Stats[Stat.CriticalChance] += SummonCritical * SummonLevel / 10;
Stats[Stat.CriticalDamage] += SummonCriticalDamage * SummonLevel / 10;
Stats[Stat.CritReduction] += Stats[Stat.CritReduction] * SummonLevel / 10;
Stats[Stat.JMCriticalDamage] += Stats[Stat.JMCriticalDamage] * SummonLevel / 10;
Stats[Stat.DamageReduction] += Stats[Stat.DamageReduction] * SummonLevel / 10;
}
if (Stats[Stat.CriticalChance] < 1)
Stats[Stat.CriticalChance] = MonsterInfo.IsBoss && MonsterInfo.Level >= 250 ? SEnvir.BigBossCriticalChance : 1;
if (Buffs.Any(x => x.Type == BuffType.MagicWeakness))
{
Stats[Stat.MinMR] = 0;
Stats[Stat.MaxMR] = 0;
}
foreach (BuffInfo buff in Buffs)
{
if (buff.Stats == null) continue;
Stats.Add(buff.Stats);
}
if (PetOwner != null && PetOwner.Stats[Stat.PetDCPercent] > 0)
{
Stats[Stat.MinDC] += Stats[Stat.MinDC] * PetOwner.Stats[Stat.PetDCPercent] / 100;
Stats[Stat.MaxDC] += Stats[Stat.MaxDC] * PetOwner.Stats[Stat.PetDCPercent] / 100;
Stats[Stat.Health] += Stats[Stat.Health] * PetOwner.Stats[Stat.PetHPPercent] / 100;
foreach (UserMagic magic in Magics)
{
switch (magic.Info.Magic)
{
case MagicType.DemonicRecovery:
Stats[Stat.Health] += (magic.Level + 1) * 300;
break;
}
}
}
/*
Stats[Stat.FireResistance] = Math.Min(5, Stats[Stat.FireResistance]);
Stats[Stat.IceResistance] = Math.Min(5, Stats[Stat.IceResistance]);
Stats[Stat.LightningResistance] = Math.Min(5, Stats[Stat.LightningResistance]);
Stats[Stat.WindResistance] = Math.Min(5, Stats[Stat.WindResistance]);
Stats[Stat.HolyResistance] = Math.Min(5, Stats[Stat.HolyResistance]);
Stats[Stat.DarkResistance] = Math.Min(5, Stats[Stat.DarkResistance]);
Stats[Stat.PhantomResistance] = Math.Min(5, Stats[Stat.PhantomResistance]);
*/
Stats[Stat.Health] += (int)(Stats[Stat.Health] * (long)Stats[Stat.HealthPercent] / 100);
Stats[Stat.Mana] += (int)(Stats[Stat.Mana] * (long)Stats[Stat.ManaPercent] / 100);
Stats[Stat.MinDC] += (int)(Stats[Stat.MinDC] * (long)Stats[Stat.DCPercent] / 100);
Stats[Stat.MaxDC] += (int)(Stats[Stat.MaxDC] * (long)Stats[Stat.DCPercent] / 100);
Stats[Stat.MinMC] += (int)(Stats[Stat.MinMC] * (long)Stats[Stat.MCPercent] / 100);
Stats[Stat.MaxMC] += (int)(Stats[Stat.MaxMC] * Stats[Stat.MCPercent] / 100);
Stats[Stat.MinSC] += (int)(Stats[Stat.MinSC] * (long)Stats[Stat.SCPercent] / 100);
Stats[Stat.MaxSC] += (int)(Stats[Stat.MaxSC] * (long)Stats[Stat.SCPercent] / 100);
if (PetOwner == null && CurrentMap != null)
{
Stats[Stat.Health] += (int)(Stats[Stat.Health] * (long)MapHealthRate / 100);
Stats[Stat.MinDC] += (int)(Stats[Stat.MinDC] * (long)MapDamageRate / 100);
Stats[Stat.MaxDC] += (int)(Stats[Stat.MaxDC] * (long)MapDamageRate / 100);
}
Stats[Stat.Health] = Math.Max(1, Stats[Stat.Health]);
Stats[Stat.Mana] = Math.Max(1, Stats[Stat.Mana]);
Stats[Stat.MinAC] = Math.Max(0, Stats[Stat.MinAC]);
Stats[Stat.MaxAC] = Math.Max(0, Stats[Stat.MaxAC]);
Stats[Stat.MinMR] = Math.Max(0, Stats[Stat.MinMR]);
Stats[Stat.MaxMR] = Math.Max(0, Stats[Stat.MaxMR]);
Stats[Stat.MinDC] = Math.Max(0, Stats[Stat.MinDC]);
Stats[Stat.MaxDC] = Math.Max(0, Stats[Stat.MaxDC]);
Stats[Stat.MinMC] = Math.Max(0, Stats[Stat.MinMC]);
Stats[Stat.MaxMC] = Math.Max(0, Stats[Stat.MaxMC]);
Stats[Stat.MinSC] = Math.Max(0, Stats[Stat.MinSC]);
Stats[Stat.MaxSC] = Math.Max(0, Stats[Stat.MaxSC]);
Stats[Stat.MinDC] = Math.Min(Stats[Stat.MinDC], Stats[Stat.MaxDC]);
Stats[Stat.MinMC] = Math.Min(Stats[Stat.MinMC], Stats[Stat.MaxMC]);
Stats[Stat.MinSC] = Math.Min(Stats[Stat.MinSC], Stats[Stat.MaxSC]);
if (MonsterInfo.Undead && PetOwner != null && Stats[Stat.Rebirth] > 0)
{
for(int i = 0; i < Stats[Stat.Rebirth]; i++)
{
if (Stats[Stat.MinAC] > 0) Stats[Stat.MinAC] += Stats[Stat.MinAC] * 15 / 100;
if (Stats[Stat.MaxAC] > 0) Stats[Stat.MaxAC] += Stats[Stat.MaxAC] * 15 / 100;
if (Stats[Stat.MinMR] > 0) Stats[Stat.MinMR] += Stats[Stat.MinMR] * 15 / 100;
if (Stats[Stat.MaxMR] > 0) Stats[Stat.MaxMR] += Stats[Stat.MaxMR] * 15 / 100;
}
}
if (EasterEventMob)
Stats[Stat.Health] = 1;
if (ChristmasEventMob)
Stats[Stat.Health] = 10;
S.DataObjectMaxHealthMana p = new S.DataObjectMaxHealthMana { ObjectID = ObjectID, Stats = Stats};
foreach (PlayerObject player in DataSeenByPlayers)
player.Enqueue(p);
if (CurrentHP > Stats[Stat.Health]) SetHP(Stats[Stat.Health]);
if (CurrentMP > Stats[Stat.Mana]) SetMP(Stats[Stat.Mana]);
}
public virtual void ApplyBonusStats()
{
}
public override void CleanUp()
{
base.CleanUp();
_Target = null;
if (SpawnList != null)
SpawnList.Clear();
SpawnList = null;
_EXPOwner = null;
if (Drops != null)
Drops.Clear();
if (Magics != null)
Magics.Clear();
}
public override void Activate()
{
if (Activated) return;
if (NearByPlayers.Count == 0 && MonsterInfo.ViewRange <= Config.MaxViewRange && !MonsterInfo.IsBoss && PetOwner == null && PendingDespawnTime >= DateTime.MaxValue) return;
Activated = true;
SEnvir.ActiveObjects.Add(this);
}
public override void DeActivate()
{
if (!Activated) return;
if (NearByPlayers.Count > 0 || MonsterInfo.ViewRange > Config.MaxViewRange || Target != null || MonsterInfo.IsBoss || PetOwner != null || PendingDespawnTime < DateTime.MaxValue || ActionList.Count > 0 || CurrentHP < Stats[Stat.Health]) return;
Activated = false;
SEnvir.ActiveObjects.Remove(this);
}
public override void ProcessAction(DelayedAction action)
{
switch (action.Type)
{
case ActionType.DelayAttack:
Attack((MapObject) action.Data[0], (int) action.Data[1], (Element) action.Data[2]);
return;
case ActionType.DelayMagic:
switch ((MagicType) action.Data[0])
{
case MagicType.FireWall:
FireWallEnd((Cell)action.Data[1]);
break;
case MagicType.DragonRepulse:
DragonRepulseEnd((MapObject)action.Data[1]);
break;
case MagicType.Purification:
Purify((MapObject) action.Data[1]);
break;
case MagicType.MonsterDeathCloud:
DeathCloudEnd((Cell)action.Data[1], (bool)action.Data[2], (Point)action.Data[3]);
break;
case MagicType.Qierbian:
QierbianEnd((Cell)action.Data[1], (bool)action.Data[2], (Point)action.Data[3]);
break;
}
break;
}
int limit_level = arrSummonLevelSetting.Count() - (Config.技能最高等级 - SummonMagicLevel);
if (limit_level >= arrSummonLevelSetting.Count()) limit_level = arrSummonLevelSetting.Count();
if (PetOwner != null && SummonLevel < limit_level)
{
AttackTicks++;
if (AttackTicks >= arrSummonLevelSetting[SummonLevel].Ticks)
{
AttackTicks = 0;
SummonLevel++;
Color oldColour = NameColour;
RefreshStats();
if (oldColour != NameColour)
Broadcast(new S.ObjectNameColour { ObjectID = ObjectID, Colour = NameColour });
}
}
base.ProcessAction(action);
}
public override void Process()
{
base.Process();
if (Dead)
{
Target = null;
if (SEnvir.Now > DeadTime)
{
Despawn();
return;
}
}
if (LifeLimit > DateTime.MinValue && SEnvir.Now > LifeLimit)
{
Despawn();
return;
}
if (PendingDespawnTime < DateTime.MaxValue && SEnvir.Now > PendingDespawnTime)
{
Despawn();
return;
}
// Skip AI processing if pet is pending despawn (waiting for owner to relog)
if (PendingDespawnTime < DateTime.MaxValue)
return;
if (Target == null || Target.Node == null || Target.Dead || Target.CurrentMap != CurrentMap || !Functions.InRange(CurrentLocation, Target.CurrentLocation, Config.MaxViewRange) ||
((Poison & PoisonType.Abyss) == PoisonType.Abyss && !Functions.InRange(CurrentLocation, Target.CurrentLocation, ViewRange)) || !CanAttackTarget(Target))
Target = null;
if (Target != null && Target.Buffs.Any(x => x.Type == BuffType.Cloak) && !Functions.InRange(CurrentLocation, Target.CurrentLocation, 2) && Stats[Stat.IgnoreStealth] == 0)
Target = null;
if (Target != null && Target.Buffs.Any(x => x.Type == BuffType.Transparency))
Target = null;
ProcessAI();
}
public override void ProcessNameColour()
{
NameColour = Color.White;
if (SEnvir.Now < ShockTime)
NameColour = Color.Peru;
else if (SEnvir.Now < RageTime)
NameColour = Color.Red;
else if (SummonLevel > 0)
{
if (SummonLevel <= arrSummonLevelSetting.Length)
NameColour = arrSummonLevelSetting[SummonLevel - 1].NameColor;
else
NameColour = arrSummonLevelSetting[arrSummonLevelSetting.Length - 1].NameColor;
}
}
public virtual void ProcessAI()
{
if (Dead) return;
if (PetOwner != null && PetOwner.Node != null)
{
if (Target != null)
{
if (PetOwner.PetMode == PetMode.PvP && Target.Race != ObjectType.Player)
Target = null;
if (PetOwner.PetMode == PetMode.None || PetOwner.PetMode == PetMode.Move)
Target = null;
}
if (SEnvir.Now > TameTime)
UnTame();
else if (Visible && !PetOwner.VisibleObjects.Contains(this) && (PetOwner.PetMode == PetMode.Both || PetOwner.PetMode == PetMode.Move || PetOwner.PetMode == PetMode.PvP))
PetRecall(); //Could be killed.
}
ProcessRegen();
ProcessSearch();
ProcessRoam();
ProcessTarget();
}
public override void OnSafeDespawn()
{
base.OnSafeDespawn();
if (Master != null)
Master.MinionList.Remove(this);
Master = null;
if (PetOwner != null)
PetOwner.Pets.Remove(this);
PetOwner = null;
if (MinionList != null)
{
for (int i = MinionList.Count - 1; i >= 0; i--)
MinionList[i].Master = null;
MinionList.Clear();
}
if (SpawnInfo != null)
SpawnInfo.AliveCount--;
ProcessEvents();
SpawnInfo = null;
EXPOwner = null;
}
public void UnTame()
{
if (PetOwner !=null)
PetOwner.Pets.Remove(this);
PetOwner = null;
Target = null;
SearchTime = DateTime.MinValue;
Magics.Clear();
SummonLevel = 0;
SummonMagicLevel = 0;
RefreshStats();
SetHP(Math.Min(CurrentHP, Stats[Stat.Health]/10));
Broadcast(new S.ObjectPetOwnerChanged { ObjectID = ObjectID });
}
public void PetRecall()
{
Cell cell = PetOwner.CurrentMap.GetCell(Functions.Move(PetOwner.CurrentLocation, PetOwner.Direction, -1));
if (cell == null || cell.Movements != null)
cell = PetOwner.CurrentCell;
Teleport(PetOwner.CurrentMap, cell.Location);
}
public virtual void ProcessRegen()
{
if (SEnvir.Now < RegenTime) return;
RegenTime = SEnvir.Now + RegenDelay;
if (CurrentHP >= Stats[Stat.Health]) return;
int regen = Math.Max(1, Stats[Stat.Health] * 2 / 100); //2% every 10 seconds aprox
ChangeHP(regen);
}
public virtual void ProcessSearch()
{
if (PetOwner != null)
{
ProperSearch();
return;
}
if (Target != null)
{
if (!Visible)
{
if (SEnvir.Now < SearchTime) return;
}
else if (!CanMove && !CanAttack) return;
}
else if (SEnvir.Now < SearchTime || CurrentMap.Players.Count == 0) return;
SearchTime = SEnvir.Now + SearchDelay;
int bestDistance = int.MaxValue;
List<MapObject> closest = new List<MapObject>();
//Save resources
foreach (PlayerObject player in CurrentMap.Players)
{
int distance;
foreach (MonsterObject pet in player.Pets)
{
if (pet.CurrentMap != CurrentMap) continue;
distance = Functions.Distance(pet.CurrentLocation, CurrentLocation);
if (distance > ViewRange) continue;
if (distance > bestDistance || !ShouldAttackTarget(pet)) continue;
if (distance != bestDistance) closest.Clear();
closest.Add(pet);
bestDistance = distance;
}
distance = Functions.Distance(player.CurrentLocation, CurrentLocation);
if (distance > ViewRange) continue;
if (distance > bestDistance || !ShouldAttackTarget(player)) continue;
if (distance != bestDistance) closest.Clear();
closest.Add(player);
bestDistance = distance;
}
if (closest.Count == 0) return;
Target = closest[SEnvir.Random.Next(closest.Count)];
}
public void ProperSearch()
{
if (Target != null)
{
if (!CanMove && !CanAttack && Visible) return;
if (PetOwner != null && Functions.Distance(PetOwner.CurrentLocation, Target.CurrentLocation) > Config.宠物不追击距离玩家多少格以外的敌人)
{
Target = null;
return;
}
}
else
{
if (SEnvir.Now < SearchTime) return;
SearchTime = SEnvir.Now + SearchDelay;
if (CurrentMap.Players.Count == 0 && !HalloweenEventMob) return;
}
for (int d = 0; d <= ViewRange; d++)
{
List<MapObject> closest = new List<MapObject>();
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];
if (cell == null || cell.Objects == null) continue;
if (PetOwner != null && Functions.Distance(PetOwner.CurrentLocation, new Point(x, y)) > Config.MaxViewRange) continue;
foreach (MapObject ob in cell.Objects)
{
if (!ShouldAttackTarget(ob)) continue;
closest.Add(ob);
}
}
}
if (closest.Count == 0) continue;
Target = closest[SEnvir.Random.Next(closest.Count)];
return;
}
}
public virtual void ProcessRoam()
{
if (!CanMove) return;
if (PetOwner != null)
{
if (Target == null)
MoveTo(Functions.Move(PetOwner.CurrentLocation, PetOwner.Direction, -1));
return;
}
if (SEnvir.Now < RoamTime || SeenByPlayers.Count == 0) return;
RoamTime = SEnvir.Now + RoamDelay;
foreach (MapObject ob in CurrentCell.Objects)
{
if (ob == this || !ob.Blocking) continue;
MirDirection direction = (MirDirection)SEnvir.Random.Next(8);
int rotation = SEnvir.Random.Next(2) == 0 ? 1 : -1;
for (int d = 0; d < 8; d++)
{
if (Walk(direction)) return;
direction = Functions.ShiftDirection(direction, rotation);
}
return;
}
if (Target != null || SEnvir.Random.Next(10) > 0) return;
if (SEnvir.Random.Next(3) > 0)
Walk(Direction);
else
Turn((MirDirection) SEnvir.Random.Next(8));
}
public virtual void ProcessTarget()
{
if (Target == null) return;
if (!InAttackRange())
{
if (CurrentLocation == Target.CurrentLocation)
{
MirDirection direction = (MirDirection)SEnvir.Random.Next(8);
int rotation = SEnvir.Random.Next(2) == 0 ? 1 : -1;
for (int d = 0; d < 8; d++)
{
if (Walk(direction)) break;
direction = Functions.ShiftDirection(direction, rotation);
}
}
else
MoveTo(Target.CurrentLocation);
return;
}
if (!CanAttack) return;
Attack();
}
public void SpawnMinions(int fixedCount, int randomCount, MapObject target)
{
int count = Math.Min(MaxMinions - MinionList.Count, SEnvir.Random.Next(randomCount + 1) + fixedCount);
for (int i = 0; i < count; i++)
{
MonsterInfo info = SEnvir.GetMonsterInfo(SpawnList);
if (info == null) continue;
MonsterObject mob = GetMonster(info);
if (!SpawnMinion(mob)) return;
mob.Target = target;
mob.Master = this;
MinionList.Add(mob);
}
}
public virtual bool SpawnMinion(MonsterObject mob)
{
return mob.Spawn(CurrentMap.Info, CurrentMap.GetRandomLocation(CurrentLocation, 6));
}
public override int Pushed(MirDirection direction, int distance)
{
if (!MonsterInfo.CanPush) return 0;
return base.Pushed(direction, distance);
}
protected virtual bool InAttackRange()
{
if (Target.CurrentMap != CurrentMap) return false;
return Target.CurrentLocation != CurrentLocation && Functions.InRange(CurrentLocation, Target.CurrentLocation, 1);
}
public override bool CanAttackTarget(MapObject ob)
{
if (ob !=null)
if (ob == this || ob.Node == null || ob.Dead || !ob.Visible || ob is Guard || ob is CastleLord) return false;
switch (ob.Race)
{
case ObjectType.Item:
case ObjectType.NPC:
case ObjectType.Spell:
return false;
}
switch (ob.Race)
{
case ObjectType.Player:
PlayerObject player = (PlayerObject)ob;
if (player.GameMaster) return false;
if (PetOwner == null) return true;
if (PetOwner == player) return false;
if (InSafeZone || player.InSafeZone) return false;
switch (PetOwner.PetMode)
{
case PetMode.Move:
case PetMode.None:
return false;
}
switch (PetOwner.AttackMode)
{
case AttackMode.Peace:
return false;
case AttackMode.Group:
if (PetOwner.InGroup(player))
return false;
break;
case AttackMode.Guild:
if (PetOwner.InGuild(player))
return false;
break;
case AttackMode.WarRedBrown:
if (player.Stats[Stat.Brown] == 0 && player.Stats[Stat.PKPoint] < Config.RedPoint && !PetOwner.AtWar(player))
return false;
break;
}
//Are any Pets attacking target or any player's pets attacking pets
return true;
case ObjectType.Monster:
MonsterObject mob = (MonsterObject)ob;
if (PetOwner == null)
{
if (mob.PetOwner == null)
return SEnvir.Now < RageTime; //Wild vs Wild
return true; //Wild vs Pet
}
switch (PetOwner.PetMode)
{
case PetMode.Move:
case PetMode.None:
case PetMode.PvP:
return false;
}
//Pet vs Wild
if (mob.PetOwner == null) return true;
//Pet vs Pet
if (mob.InSafeZone || InSafeZone) return false;
if (PetOwner == mob.PetOwner) return false;
switch (PetOwner.AttackMode)
{
case AttackMode.Peace:
return false;
case AttackMode.Group:
if (PetOwner.InGroup(mob.PetOwner))
return false;
break;
case AttackMode.Guild:
if (PetOwner.InGuild(mob.PetOwner))
return false;
break;
case AttackMode.WarRedBrown:
if (mob.PetOwner.Stats[Stat.Brown] == 0 && mob.PetOwner.Stats[Stat.PKPoint] < Config.RedPoint && !PetOwner.AtWar(mob.PetOwner))
return false;
break;
}
return true;
default:
throw new NotImplementedException();
}
}
public override bool CanHelpTarget(MapObject ob)
{
if (ob == null || ob.Node == null || ob.Dead || !ob.Visible || ob is Guard || ob is CastleLord) return false;
if (ob == this) return true;
switch (ob.Race)
{
case ObjectType.Player:
if (PetOwner == null) return false;
PlayerObject player = (PlayerObject)ob;
switch (PetOwner.AttackMode)
{
case AttackMode.Peace:
return true;
case AttackMode.Group:
if (PetOwner.InGroup(player))
return true;
break;
case AttackMode.Guild:
if (PetOwner.InGuild(player))
return true;
break;
case AttackMode.WarRedBrown:
if (player.Stats[Stat.Brown] == 0 && player.Stats[Stat.PKPoint] < Config.RedPoint && !PetOwner.AtWar(player))
return true;
break;
}
return true;
case ObjectType.Monster:
MonsterObject mob = (MonsterObject)ob;
if (PetOwner == null) return mob.PetOwner == null;
if (mob.PetOwner == null) return false;
switch (PetOwner.AttackMode)
{
case AttackMode.Peace:
return true;
case AttackMode.Group:
if (PetOwner.InGroup(mob.PetOwner))
return true;
break;
case AttackMode.Guild:
if (PetOwner.InGuild(mob.PetOwner))
return true;
break;
case AttackMode.WarRedBrown:
if (mob.PetOwner.Stats[Stat.Brown] == 0 && mob.PetOwner.Stats[Stat.PKPoint] < Config.RedPoint && !PetOwner.AtWar(mob.PetOwner))
return true;
break;
}
return true;
default:
return false;
}
}
public virtual bool ShouldAttackTarget(MapObject ob)
{
if (ob == null || Passive || ob == this || ob.Node == null || ob.Dead || !ob.Visible || ob is Guard || ob is CastleLord) return false;
switch (ob.Race)
{
case ObjectType.Item:
case ObjectType.NPC:
case ObjectType.Spell:
return false;
}
if (ob.Buffs.Any(x => x.Type == BuffType.Invisibility) && !CoolEye) return false;
if (ob.Buffs.Any(x => x.Type == BuffType.Cloak) && Stats[Stat.IgnoreStealth] == 0)
{
if (!CoolEye) return false;
if (!Functions.InRange(ob.CurrentLocation, CurrentLocation, 2)) return false;
if (ob.Level >= Level) return false;
}
if (ob.Buffs.Any(x => x.Type == BuffType.Transparency) && ((Poison & PoisonType.Infection) != PoisonType.Infection || Level < 100)) return false;
switch (ob.Race)
{
case ObjectType.Player:
PlayerObject player = (PlayerObject)ob;
if (player.GameMaster) return false;
if (PetOwner == null) return true;
if (PetOwner == player) return false;
if (InSafeZone || player.InSafeZone) return false;
switch (PetOwner.PetMode)
{
case PetMode.Move:
case PetMode.None:
return false;
}
switch (PetOwner.AttackMode)
{
case AttackMode.Peace:
return false;
case AttackMode.Group:
if (PetOwner.InGroup(player))
return false;
break;
case AttackMode.Guild:
if (PetOwner.InGuild(player))
return false;
break;
case AttackMode.WarRedBrown:
if (player.Stats[Stat.Brown] == 0 && player.Stats[Stat.PKPoint] < Config.RedPoint && !PetOwner.AtWar(player))
return false;
break;
}
//Are any Pets attacking target or any player's pets attacking pets
if (PetOwner.Pets.Any(x =>
{
if (x.Target == null) return false;
switch (x.Target.Race)
{
case ObjectType.Player:
return x.Target == player;
case ObjectType.Monster:
return ((MonsterObject)x.Target).PetOwner == player;
default:
throw new ArgumentOutOfRangeException();
}
})) return true;
if (player.Pets.Any(x =>
{
if (x.Target == null) return false;
switch (x.Target.Race)
{
case ObjectType.Player:
return x.Target == PetOwner;
case ObjectType.Monster:
return ((MonsterObject)x.Target).PetOwner == PetOwner;
default:
throw new ArgumentOutOfRangeException();
}
})) return true;
return false;
case ObjectType.Monster:
MonsterObject mob = (MonsterObject) ob;
if (PetOwner == null)
{
if (mob.PetOwner == null)
return SEnvir.Now < RageTime; //Wild vs Wild
return true; //Wild vs Pet
}
switch (PetOwner.PetMode)
{
case PetMode.Move:
case PetMode.None:
case PetMode.PvP:
return false;
}
//Pet vs Wild
if (mob.PetOwner == null)
{
if (mob.EXPOwner == PetOwner || PetOwner.InGroup(mob.EXPOwner) || PetOwner.InGuild(mob.EXPOwner))
return true;
//Is mob's Targets = master or group/guild member
if (mob.EXPOwner != null) return false; //Someone else's mob
if (mob.Target == null) return false;
PlayerObject mobTarget;
if (mob.Target.Race == ObjectType.Monster)
mobTarget = ((MonsterObject) mob.Target).PetOwner;
else
mobTarget = (PlayerObject) mob.Target;
if (mobTarget == null || mobTarget.Node == null) return false;
if (mobTarget == PetOwner || PetOwner.InGroup(mobTarget) || PetOwner.InGuild(mobTarget))
return true;
return false;
}
//Pet vs Pet
if (mob.InSafeZone || InSafeZone) return false;
if (PetOwner == mob.PetOwner) return false;
switch (PetOwner.AttackMode)
{
case AttackMode.Peace:
return false;
case AttackMode.Group:
if (PetOwner.InGroup(mob.PetOwner))
return false;
break;
case AttackMode.Guild:
if (PetOwner.InGuild(mob.PetOwner))
return false;
break;
case AttackMode.WarRedBrown:
if (mob.PetOwner.Stats[Stat.Brown] == 0 && mob.PetOwner.Stats[Stat.PKPoint] < Config.RedPoint && !PetOwner.AtWar(mob.PetOwner))
return false;
break;
}
if (PetOwner.Pets.Any(x =>
{
if (x.Target == null) return false;
switch (x.Target.Race)
{
case ObjectType.Player:
return x.Target == mob.PetOwner;
case ObjectType.Monster:
return ((MonsterObject)x.Target).PetOwner == mob.PetOwner;
default:
throw new ArgumentOutOfRangeException();
}
})) return true;
if (mob.PetOwner.Pets.Any(x =>
{
if (x.Target == null) return false;
switch (x.Target.Race)
{
case ObjectType.Player:
return x.Target == PetOwner;
case ObjectType.Monster:
return ((MonsterObject)x.Target).PetOwner == PetOwner;
default:
throw new ArgumentOutOfRangeException();
}
})) return true;
return false;
default:
throw new NotImplementedException();
}
}
protected virtual void Attack()
{
Direction = Functions.DirectionFromPoint(CurrentLocation, Target.CurrentLocation);
Broadcast(new S.ObjectAttack { ObjectID = ObjectID, Direction = Direction, Location = CurrentLocation });
UpdateAttackTime();
ActionList.Add(new DelayedAction(
SEnvir.Now.AddMilliseconds(400),
ActionType.DelayAttack,
Target,
GetDC(),
AttackElement));
}
public virtual int Attack(MapObject ob, int power, Element element)
{
if (ob == null || ob.Node == null || ob.Dead) return 0;
int damage;
if (PoisonList.Any(x => x.Type == PoisonType.Abyss) && SEnvir.Random.Next(2) > 0)
{
ob.Dodged();
return 0;
}
if (element == Element.None)
{
int accuracy = Stats[Stat.Accuracy];
if (SEnvir.Random.Next(ob.Stats[Stat.Agility]) > accuracy)
{
ob.Dodged();
return 0;
}
damage = power - ob.GetAC();
}
else
{
damage = power - ob.GetMR();
}
int res = ob.Stats.GetResistanceValue(element);
if (res > 0)
damage -= damage * res / 10;
else if (res < 0)
damage -= damage * res / 5;
if (damage <= 0)
{
ob.Blocked();
return 0;
}
damage = ob.Attacked(this, damage, element, true, IgnoreShield);
if (damage <= 0) return damage;
LifeSteal += damage * Stats[Stat.LifeSteal] / 100M;
if (LifeSteal > 1)
{
int heal = (int) Math.Floor(LifeSteal);
LifeSteal -= heal;
ChangeHP(heal);
}
if (PetOwner != null)
{
foreach (UserMagic magic in Magics)
PetOwner.LevelMagic(magic);
int limit_level = arrSummonLevelSetting.Count() - (Config.技能最高等级 - SummonMagicLevel);
if (limit_level >= arrSummonLevelSetting.Count()) limit_level = arrSummonLevelSetting.Count();
if (SummonLevel < limit_level)
{
AttackTicks++;
if (AttackTicks >= arrSummonLevelSetting[SummonLevel].Ticks)
{
AttackTicks = 0;
SummonLevel++;
Color oldColour = NameColour;
RefreshStats();
if (oldColour != NameColour)
Broadcast(new S.ObjectNameColour { ObjectID = ObjectID, Colour = NameColour });
}
}
}
if (PoisonType == PoisonType.None || SEnvir.Random.Next(PoisonRate) > 0) return damage;
ob.ApplyPoison(new Poison
{
Owner = this,
Type = PoisonType,
Value = GetSC(),
TickFrequency = TimeSpan.FromSeconds(PoisonFrequency),
TickCount = PoisonTicks,
});
return damage;
}
#region Magics
public void AttackMagic(MagicType magic, Element element, bool travel, int damage = 0)
{
Direction = Functions.DirectionFromPoint(CurrentLocation, Target.CurrentLocation);
Broadcast(new S.ObjectMagic { ObjectID = ObjectID, Direction = Direction, CurrentLocation = CurrentLocation, Cast = true, Type = magic, Targets = new List<uint> { Target.ObjectID } });
UpdateAttackTime();
ActionList.Add(new DelayedAction(
SEnvir.Now.AddMilliseconds(500 + (travel ? Functions.Distance(CurrentLocation, Target.CurrentLocation) * 48 : 0)),
ActionType.DelayAttack,
Target,
damage == 0 ? GetDC() : damage,
element));
}
public void AttackAoE(int radius, MagicType magic, Element element, int damage = 0)
{
Direction = Functions.DirectionFromPoint(CurrentLocation, Target.CurrentLocation);
Broadcast(new S.ObjectMagic { ObjectID = ObjectID, Direction = Direction, CurrentLocation = CurrentLocation, Cast = true, Type = magic, Locations = new List<Point> { Target.CurrentLocation } });
UpdateAttackTime();
List<MapObject> targets = GetTargets(CurrentMap, Target.CurrentLocation, radius);
foreach (MapObject ob in targets)
{
ActionList.Add(new DelayedAction(
SEnvir.Now.AddMilliseconds(500),
ActionType.DelayAttack,
ob,
damage == 0 ? GetDC() : damage,
element));
}
}
public void SamaGuardianFire()
{
Direction = Functions.DirectionFromPoint(CurrentLocation, Target.CurrentLocation);
Broadcast(new S.ObjectMagic { ObjectID = ObjectID, Direction = Direction, CurrentLocation = CurrentLocation, Cast = true, Type = MagicType.SamaGuardianFire, Locations = new List<Point> { Target.CurrentLocation } });
UpdateAttackTime();
List<MapObject> targets = GetTargets(CurrentMap, Target.CurrentLocation, 5);
foreach (MapObject ob in targets)
{
ActionList.Add(new DelayedAction(
SEnvir.Now.AddMilliseconds(500),
ActionType.DelayAttack,
ob,
GetDC(),
Element.Fire));
}
}
public void LineAoE(int distance, int min, int max, MagicType magic, Element element)
{
Direction = Functions.DirectionFromPoint(CurrentLocation, Target.CurrentLocation);
List<uint> targetIDs = new List<uint>();
List<Point> locations = new List<Point>();
Broadcast(new S.ObjectMagic { ObjectID = ObjectID, Direction = Direction, CurrentLocation = CurrentLocation, Cast = true, Type = magic, Targets = targetIDs, Locations = locations });
UpdateAttackTime();
for (int d = min; d <= max; d++)
{
MirDirection direction = Functions.ShiftDirection(Direction, d);
if (magic == MagicType.LightningBeam || magic == MagicType.BlowEarth)
locations.Add(Functions.Move(CurrentLocation, direction, distance));
for (int i = 1; i <= distance; i++)
{
Point location = Functions.Move(CurrentLocation, direction, i);
Cell cell = CurrentMap.GetCell(location);
if (cell == null) continue;
if (magic != MagicType.LightningBeam && magic != MagicType.BlowEarth)
locations.Add(cell.Location);
if (cell.Objects != null)
{
foreach (MapObject ob in cell.Objects)
{
if (!CanAttackTarget(ob)) continue;
ActionList.Add(new DelayedAction(
SEnvir.Now.AddMilliseconds(500 + i * 75),
ActionType.DelayAttack,
ob,
GetDC(),
element));
}
}
switch (direction)
{
case MirDirection.Up:
case MirDirection.Right:
case MirDirection.Down:
case MirDirection.Left:
cell = CurrentMap.GetCell(Functions.Move(location, Functions.ShiftDirection(direction, -2)));
if (cell != null && cell.Objects != null)
{
foreach (MapObject ob in cell.Objects)
{
if (!CanAttackTarget(ob)) continue;
ActionList.Add(new DelayedAction(
SEnvir.Now.AddMilliseconds(500 + i * 75),
ActionType.DelayAttack,
ob,
GetDC() / 2,
element));
}
}
cell = CurrentMap.GetCell(Functions.Move(location, Functions.ShiftDirection(direction, 2)));
if (cell != null && cell.Objects != null)
{
foreach (MapObject ob in cell.Objects)
{
if (!CanAttackTarget(ob)) continue;
ActionList.Add(new DelayedAction(
SEnvir.Now.AddMilliseconds(500 + i * 75),
ActionType.DelayAttack,
ob,
GetDC() / 2,
element));
}
}
break;
case MirDirection.UpRight:
case MirDirection.DownRight:
case MirDirection.DownLeft:
case MirDirection.UpLeft:
cell = CurrentMap.GetCell(Functions.Move(location, Functions.ShiftDirection(direction, -1)));
if (cell != null && cell.Objects != null)
{
foreach (MapObject ob in cell.Objects)
{
if (!CanAttackTarget(ob)) continue;
ActionList.Add(new DelayedAction(
SEnvir.Now.AddMilliseconds(500 + i * 75),
ActionType.DelayAttack,
ob,
GetDC() / 2,
element));
}
}
cell = CurrentMap.GetCell(Functions.Move(location, Functions.ShiftDirection(direction, 1)));
if (cell != null && cell.Objects != null)
{
foreach (MapObject ob in cell.Objects)
{
if (!CanAttackTarget(ob)) continue;
ActionList.Add(new DelayedAction(
SEnvir.Now.AddMilliseconds(500 + i * 75),
ActionType.DelayAttack,
ob,
GetDC() / 2,
element));
}
}
break;
}
}
}
}
public void FireWall()
{
Direction = Functions.DirectionFromPoint(CurrentLocation, Target.CurrentLocation);
List<uint> targetIDs = new List<uint>();
List<Point> locations = new List<Point>();
Broadcast(new S.ObjectMagic { ObjectID = ObjectID, Direction = Direction, CurrentLocation = CurrentLocation, Cast = true, Type = MagicType.FireWall, Targets = targetIDs, Locations = locations });
UpdateAttackTime();
List<MapObject> targets = GetTargets(CurrentMap, CurrentLocation, 20);
if (targets.Count == 0) return;
Point location = targets[SEnvir.Random.Next(targets.Count)].CurrentLocation;
ActionList.Add(new DelayedAction(
SEnvir.Now.AddMilliseconds(500),
ActionType.DelayMagic,
MagicType.FireWall,
CurrentMap.GetCell(location)));
ActionList.Add(new DelayedAction(
SEnvir.Now.AddMilliseconds(500),
ActionType.DelayMagic,
MagicType.FireWall,
CurrentMap.GetCell(Functions.Move(location, MirDirection.Up))));
ActionList.Add(new DelayedAction(
SEnvir.Now.AddMilliseconds(500),
ActionType.DelayMagic,
MagicType.FireWall,
CurrentMap.GetCell(Functions.Move(location, MirDirection.Down))));
ActionList.Add(new DelayedAction(
SEnvir.Now.AddMilliseconds(500),
ActionType.DelayMagic,
MagicType.FireWall,
CurrentMap.GetCell(Functions.Move(location, MirDirection.Left))));
ActionList.Add(new DelayedAction(
SEnvir.Now.AddMilliseconds(500),
ActionType.DelayMagic,
MagicType.FireWall,
CurrentMap.GetCell(Functions.Move(location, MirDirection.Right))));
}
public void FireWallEnd(Cell cell)
{
if (cell == null) return;
if (cell.Objects != null)
{
for (int i = cell.Objects.Count - 1; i >= 0; i--)
{
if (cell.Objects[i].Race != ObjectType.Spell) continue;
SpellObject spell = (SpellObject)cell.Objects[i];
if (spell.Effect != SpellEffect.FireWall && spell.Effect != SpellEffect.MonsterFireWall && spell.Effect != SpellEffect.Tempest) continue;
spell.Despawn();
}
}
SpellObject ob = new SpellObject
{
DisplayLocation = cell.Location,
TickCount = 15,
TickFrequency = TimeSpan.FromSeconds(2),
Owner = this,
Effect = SpellEffect.MonsterFireWall
};
ob.Spawn(cell.Map.Info, cell.Location);
}
public void DeathCloud(Point location)
{
bool visible = true;
foreach (Cell cell in CurrentMap.GetCells(location, 0, 2))
{
ActionList.Add(new DelayedAction(
SEnvir.Now.AddMilliseconds(500),
ActionType.DelayMagic,
MagicType.MonsterDeathCloud,
cell,
visible,
location));
visible = false;
}
}
public void DeathCloudEnd(Cell cell, bool visible, Point displaylocation)
{
if (cell == null) return;
SpellObject ob = new SpellObject
{
DisplayLocation = displaylocation,
TickCount = 1,
TickTime = SEnvir.Now.AddMilliseconds(DeathCloudDurationMin + SEnvir.Random.Next(DeathCloudDurationRandom)),
Owner = this,
Effect = SpellEffect.MonsterDeathCloud,
Visible = visible,
};
ob.Spawn(cell.Map.Info, cell.Location);
}
public void MassLightningBall()
{
Direction = Functions.DirectionFromPoint(CurrentLocation, Target.CurrentLocation);
List<uint> targetIDs = new List<uint>();
List<Point> locations = new List<Point>();
Broadcast(new S.ObjectMagic { ObjectID = ObjectID, Direction = Direction, CurrentLocation = CurrentLocation, Cast = true, Type = MagicType.LightningBall, Targets = targetIDs, Locations = locations });
UpdateAttackTime();
for (int i = -20; i < 20; i += 5)
locations.Add(new Point(CurrentLocation.X - 20, CurrentLocation.Y - i));
for (int i = -20; i < 20; i += 5)
locations.Add(new Point(CurrentLocation.X + 20, CurrentLocation.Y - i));
for (int i = -20; i < 20; i += 5)
locations.Add(new Point(CurrentLocation.X + i, CurrentLocation.Y - 20));
for (int i = -20; i < 20; i += 5)
locations.Add(new Point(CurrentLocation.X + i, CurrentLocation.Y + 20));
List<MapObject> targets = GetTargets(CurrentMap, CurrentLocation, Config.MaxViewRange);
foreach (MapObject ob in targets)
{
targetIDs.Add(ob.ObjectID);
ActionList.Add(new DelayedAction(
SEnvir.Now.AddMilliseconds(500 + Functions.Distance(ob.CurrentLocation, CurrentLocation)*48),
ActionType.DelayAttack,
ob,
GetDC(),
Element.Lightning));
}
}
public void MassThunderBolt()
{
Direction = Functions.DirectionFromPoint(CurrentLocation, Target.CurrentLocation);
List<uint> targetIDs = new List<uint>();
List<Point> locations = new List<Point>();
Broadcast(new S.ObjectMagic { ObjectID = ObjectID, Direction = Direction, CurrentLocation = CurrentLocation, Cast = true, Type = MagicType.ThunderBolt, Targets = targetIDs, Locations = locations });
UpdateAttackTime();
List<Cell> cells = CurrentMap.GetCells(CurrentLocation, 0, Config.MaxViewRange);
foreach (Cell cell in cells)
{
if (cell.Objects == null)
{
if (SEnvir.Random.Next(50) == 0)
locations.Add(cell.Location);
continue;
}
foreach (MapObject ob in cell.Objects)
{
if (SEnvir.Random.Next(2) > 0) continue;
if (!CanAttackTarget(ob)) continue;
targetIDs.Add(ob.ObjectID);
ActionList.Add(new DelayedAction(
SEnvir.Now.AddMilliseconds(500),
ActionType.DelayAttack,
ob,
GetDC(),
Element.Lightning));
}
}
}
/* public void ThunderBolt(int damage)
{
Direction = Functions.DirectionFromPoint(CurrentLocation, Target.CurrentLocation);
Broadcast(new S.ObjectMagic { ObjectID = ObjectID, Direction = Direction, CurrentLocation = CurrentLocation, Cast = true, Type = MagicType.ThunderBolt, Targets = new List<uint> { Target.ObjectID } });
UpdateAttackTime();
ActionList.Add(new DelayedAction(
SEnvir.Now.AddMilliseconds(500),
ActionType.DelayAttack,
Target,
GetDC(),
Element.Lightning));
}*/
public void MonsterThunderStorm(int damage)
{
Direction = Functions.DirectionFromPoint(CurrentLocation, Target.CurrentLocation);
Broadcast(new S.ObjectMagic { ObjectID = ObjectID, Direction = Direction, CurrentLocation = CurrentLocation, Cast = true, Type = MagicType.MonsterThunderStorm, Locations = new List<Point> { CurrentLocation } });
UpdateAttackTime();
foreach (MapObject target in GetTargets(CurrentMap, CurrentLocation, 2))
{
ActionList.Add(new DelayedAction(
SEnvir.Now.AddMilliseconds(500),
ActionType.DelayAttack,
target,
damage,
Element.Lightning));
}
}
public void Purification()
{
Direction = Functions.DirectionFromPoint(CurrentLocation, Target.CurrentLocation);
Broadcast(new S.ObjectMagic { ObjectID = ObjectID, Direction = Direction, CurrentLocation = CurrentLocation, Cast = true, Type = MagicType.Purification, Targets = new List<uint> { Target.ObjectID } });
UpdateAttackTime();
ActionList.Add(new DelayedAction(
SEnvir.Now.AddMilliseconds(500),
ActionType.DelayMagic,
MagicType.Purification,
Target));
}
public void MassPurification()
{
Direction = Functions.DirectionFromPoint(CurrentLocation, Target.CurrentLocation);
List<uint> targets = new List<uint>();
Broadcast(new S.ObjectMagic { ObjectID = ObjectID, Direction = Direction, CurrentLocation = CurrentLocation, Cast = true, Type = MagicType.Purification, Targets = targets });
UpdateAttackTime();
List<MapObject> obs = GetAllObjects(CurrentLocation, Globals.MagicRange);
foreach (MapObject ob in obs)
{
if (!CanHelpTarget(ob) && !CanAttackTarget(ob)) continue;
targets.Add(ob.ObjectID);
ActionList.Add(new DelayedAction(
SEnvir.Now.AddMilliseconds(500),
ActionType.DelayMagic,
MagicType.Purification,
ob));
}
}
public void MassCyclone(MagicType type = MagicType.Cyclone, int chance = 30)
{
Direction = Functions.DirectionFromPoint(CurrentLocation, Target.CurrentLocation);
List<uint> targetIDs = new List<uint>();
List<Point> locations = new List<Point>();
Broadcast(new S.ObjectMagic
{
ObjectID = ObjectID,
Direction = Direction,
CurrentLocation = CurrentLocation,
Cast = true,
Type = type,
Targets = targetIDs,
Locations = locations
});
UpdateAttackTime();
List<Cell> cells = CurrentMap.GetCells(CurrentLocation, 0, Config.MaxViewRange);
foreach (Cell cell in cells)
{
if (cell.Objects == null)
{
if (SEnvir.Random.Next(chance) == 0)
locations.Add(cell.Location);
continue;
}
foreach (MapObject ob in cell.Objects)
{
if (SEnvir.Random.Next(4) == 0) continue;
if (!CanAttackTarget(ob)) continue;
targetIDs.Add(ob.ObjectID);
ActionList.Add(new DelayedAction(
SEnvir.Now.AddMilliseconds(500),
ActionType.DelayAttack,
ob,
GetDC(),
Element.Wind));
}
}
}
/*
public void MonsterIceStorm()
{
Direction = Functions.DirectionFromPoint(CurrentLocation, Target.CurrentLocation);
List<uint> targetIDs = new List<uint>();
List<Point> locations = new List<Point> { Target.CurrentLocation };
Broadcast(new S.ObjectMagic { ObjectID = ObjectID, Direction = Direction, CurrentLocation = CurrentLocation, Cast = true, Type = MagicType.MonsterIceStorm, Targets = targetIDs, Locations = locations });
UpdateAttackTime();
List<MapObject> targets = GetTargets(CurrentMap, Target.CurrentLocation, 1);
foreach (MapObject target in targets)
{
ActionList.Add(new DelayedAction(
SEnvir.Now.AddMilliseconds(500),
ActionType.DelayAttack,
target,
GetDC(),
Element.Ice));
}
}*/
public void PoisonousCloud()
{
Direction = Functions.DirectionFromPoint(CurrentLocation, Target.CurrentLocation);
List<uint> targetIDs = new List<uint>();
List<Point> locations = new List<Point>();
Broadcast(new S.ObjectMagic { ObjectID = ObjectID, Direction = Direction, CurrentLocation = CurrentLocation, Cast = true, Type = MagicType.PoisonousCloud, Targets = targetIDs, Locations = locations });
UpdateAttackTime();
List<Cell> cells = CurrentMap.GetCells(CurrentLocation, 0, 2);
foreach (Cell cell in cells)
{
SpellObject ob = new SpellObject
{
Visible = cell == CurrentCell,
DisplayLocation = CurrentLocation,
TickCount = 1,
TickFrequency = TimeSpan.FromSeconds(20),
Owner = this,
Effect = SpellEffect.PoisonousCloud,
Power = 20
};
ob.Spawn(CurrentMap.Info, cell.Location);
}
}
public void DragonRepulse()
{
Direction = Functions.DirectionFromPoint(CurrentLocation, Target.CurrentLocation);
List<uint> targetIDs = new List<uint>();
List<Point> locations = new List<Point>();
Broadcast(new S.ObjectMagic { ObjectID = ObjectID, Direction = Direction, CurrentLocation = CurrentLocation, Cast = true, Type = MagicType.DragonRepulse, Targets = targetIDs, Locations = locations });
UpdateAttackTime();
BuffInfo buff = BuffAdd(BuffType.DragonRepulse, TimeSpan.FromSeconds(6), null, true, false, TimeSpan.FromSeconds(1));
buff.TickTime = TimeSpan.FromMilliseconds(500);
}
public void DragonRepulseEnd(MapObject ob)
{
if (Attack(ob, GetDC(), AttackElement) > 0)
{
MirDirection dir = Functions.DirectionFromPoint(CurrentLocation, ob.CurrentLocation);
if (ob.Pushed(dir, 1) == 0)
{
int rotation = SEnvir.Random.Next(2) == 0 ? 1 : -1;
for (int i = 1; i < 2; i++)
{
if (ob.Pushed(Functions.ShiftDirection(dir, i*rotation), 1) > 0) break;
if (ob.Pushed(Functions.ShiftDirection(dir, i*-rotation), 1) > 0) break;
}
}
}
}
#endregion
public void UpdateAttackTime()
{
AttackTime = SEnvir.Now.AddMilliseconds(AttackDelay);
ActionTime = SEnvir.Now.AddMilliseconds(Math.Min(MoveDelay, AttackDelay - 100));
Poison poison = PoisonList.FirstOrDefault(x => x.Type == PoisonType.Slow);
if (poison != null)
{
AttackTime += TimeSpan.FromMilliseconds(poison.Value * 100);
ActionTime += TimeSpan.FromMilliseconds(poison.Value * 100);
}
}
public void UpdateMoveTime()
{
MoveTime = SEnvir.Now.AddMilliseconds(MoveDelay);
ActionTime = SEnvir.Now.AddMilliseconds(Math.Min(MoveDelay - 100, AttackDelay));
Poison poison = PoisonList.FirstOrDefault(x => x.Type == PoisonType.Slow);
if (poison != null)
{
AttackTime += TimeSpan.FromMilliseconds(poison.Value * 100);
ActionTime += TimeSpan.FromMilliseconds(poison.Value * 100);
}
}
public override int Attacked(MapObject attacker, int power, Element element, bool canReflect = true, bool ignoreShield = false, bool canCrit = true, bool canStruck = true)
{
if (attacker == null || attacker.Node == null || power == 0 || Dead || attacker.CurrentMap != CurrentMap || !Functions.InRange(attacker.CurrentLocation, CurrentLocation, Config.MaxViewRange)) return 0;
PlayerObject player;
switch (attacker.Race)
{
case ObjectType.Player:
PlayerTagged = true;
player = (PlayerObject) attacker;
break;
case ObjectType.Monster:
player = ((MonsterObject) attacker).PetOwner;
break;
default:
throw new NotImplementedException();
}
ShockTime = DateTime.MinValue;
Killer = player;
if (EXPOwner == null && PetOwner == null)
EXPOwner = player;
if (EXPOwner == player && player != null)
EXPOwnerTime = SEnvir.Now + EXPOwnerDelay;
//Damage Counting statistics.
if (StruckTime != DateTime.MaxValue && SEnvir.Now > StruckTime.AddMilliseconds(300))
{
StruckTime = SEnvir.Now;
Broadcast(new S.ObjectStruck { ObjectID = ObjectID, Direction = Direction, Location = CurrentLocation, AttackerID = attacker.ObjectID, Element = element });
}
if ((Poison & PoisonType.Red) == PoisonType.Red)
power += (power * 2 / 10);
for (int i = 0; i < attacker.Stats[Stat.Rebirth]; i++)
power += (power / 2);
power -= power * Stats[Stat.DamageReduction] / 100;
power += power * attacker.Stats[Stat.DamageAdd] / 100;
BuffInfo buff = Buffs.FirstOrDefault(x => x.Type == BuffType.MagicShield);
if (buff != null)
buff.RemainingTime -= TimeSpan.FromMilliseconds(power * 10);
power -= power * Stats[Stat.MagicShield] / 100;
if (SEnvir.Random.Next(100) < attacker.Stats[Stat.CriticalChance] - Stats[Stat.CritReduction] && canCrit && power > 0)
{
power += power + power * (attacker.Stats[Stat.CriticalDamage] - Stats[Stat.JMCriticalDamage]) / 100;
Critical();
}
ChangeHP(-power);
if (Dead) return power;
if (CanAttackTarget(attacker)&& PetOwner == null || Target == null)
Target = attacker;
return power;
}
public override bool ApplyPoison(Poison p)
{
bool res = base.ApplyPoison(p);
if (res && CanAttackTarget(p.Owner) && Target == null)
Target = p.Owner;
if (p.Owner.Race == ObjectType.Player)
PlayerTagged = true;
return res;
}
public override void Die()
{
if (MonsterInfo.IsBoss && MonsterInfo.Level == 250 && EXPOwner != null && PetOwner == null)
{
string msg = $"[{SEnvir.Now.ToLocalTime():HH:mm:ss}] {CurrentMap.Info.Description}的【{MonsterInfo.MonsterName}】被绝世高手【{EXPOwner.Name}】击杀";
foreach (var con in SEnvir.Connections)
con.ReceiveChat(msg, MessageType.Announcement);
}
if (PetOwner != null && !string.IsNullOrEmpty(Name))
{
if (Killer != null)
PetOwner.Connection?.ReceiveChat($"你的宝宝[{Name}]被{Killer.Race switch { ObjectType.Player => "玩家", ObjectType.Monster => "怪物", _=>"" }}[{Killer.Name}]杀死了", MessageType.System);
else
PetOwner.Connection?.ReceiveChat($"你的宝宝[{Name}]被杀死了", MessageType.System);
}
Killer = null;
base.Die();
YieldReward();
if (Master != null)
Master.MinionList.Remove(this);
Master = null;
if (PetOwner != null)
PetOwner.Pets.Remove(this);
PetOwner = null;
for (int i = MinionList.Count - 1; i >= 0; i--)
MinionList[i].Master = null;
MinionList.Clear();
DeadTime = SEnvir.Now + Config.DeadDuration;
if (Drops != null)
DeadTime += Config.HarvestDuration;
if (SpawnInfo != null)
SpawnInfo.AliveCount--;
ProcessEvents();
SpawnInfo = null;
EXPOwner = null;
}
private void ProcessEvents()
{
if (SpawnInfo == null) return;
foreach (EventTarget target in MonsterInfo.Events)
{
if ((DropSet & target.DropSet) != target.DropSet) continue;
int start = target.Event.CurrentValue;
int end = Math.Min(target.Event.MaxValue, Math.Max(0, start + target.Value));
target.Event.CurrentValue = end;
foreach (EventAction action in target.Event.Actions)
{
if (start >= action.TriggerValue || end < action.TriggerValue) continue;
Map map;
switch (action.Type)
{
case EventActionType.GlobalMessage:
SEnvir.Broadcast(new S.Chat { Text = action.StringParameter1, Type = MessageType.System });
break;
case EventActionType.MapMessage:
map = SEnvir.GetMap(action.MapParameter1);
if (map == null) continue;
map.Broadcast(new S.Chat { Text = action.StringParameter1, Type = MessageType.System });
break;
case EventActionType.PlayerMessage:
if (EXPOwner == null) continue;
EXPOwner.Broadcast(new S.Chat { Text = action.StringParameter1, Type = MessageType.System });
break;
case EventActionType.MonsterSpawn:
SpawnInfo spawn = SEnvir.Spawns.FirstOrDefault(x => x.Info == action.RespawnParameter1);
if (spawn == null) continue;
spawn.DoSpawn(true);
break;
case EventActionType.MonsterPlayerSpawn:
MonsterObject mob = GetMonster(action.MonsterParameter1);
mob.Spawn(CurrentMap.Info, CurrentMap.GetRandomLocation(CurrentLocation, 10));
break;
case EventActionType.MovementSettings:
break;
case EventActionType.PlayerRecall:
map = SEnvir.GetMap(action.MapParameter1);
if (map == null) continue;
for (int i = map.Players.Count - 1; i >= 0; i--)
{
PlayerObject player = map.Players[i];
player.Teleport(action.RegionParameter1);
}
break;
case EventActionType.PlayerEscape:
map = SEnvir.GetMap(action.MapParameter1);
if (map == null) continue;
for (int i = map.Players.Count - 1; i >= 0; i--)
{
PlayerObject player = map.Players[i];
player.Teleport(player.Character.BindPoint.BindRegion);
}
break;
}
}
}
}
protected void YieldReward()
{
if (EXPOwner == null || PetOwner != null) return;
decimal eRate = 1M + ExtraExperienceRate;
decimal dRate = 1M;
int totalLevels = 0;
List<PlayerObject> ePlayers = new List<PlayerObject>();
List<PlayerObject> dPlayers = new List<PlayerObject>();
if (EXPOwner.GroupMembers != null)
{
int eWarrior = 0, eWizard = 0, eTaoist = 0, eAssassin = 0;
int dWarrior = 0, dWizard = 0, dTaoist = 0, dAssassin = 0;
foreach (PlayerObject ob in EXPOwner.GroupMembers)
{
if (ob.CurrentMap != CurrentMap || !Functions.InRange(ob.CurrentLocation, CurrentLocation, Config.MaxViewRange)) continue;
switch (ob.Class)
{
case MirClass.Warrior:
dWarrior++;
break;
case MirClass.Wizard:
dWizard++;
break;
case MirClass.Taoist:
dTaoist++;
break;
case MirClass.Assassin:
dAssassin++;
break;
}
dPlayers.Add(ob);
if (ob.Dead) continue;
switch (ob.Class)
{
case MirClass.Warrior:
eWarrior++;
break;
case MirClass.Wizard:
eWizard++;
break;
case MirClass.Taoist:
eTaoist++;
break;
case MirClass.Assassin:
eAssassin++;
break;
}
ePlayers.Add(ob);
totalLevels += ob.Level;
}
switch (Math.Min(dWarrior, Math.Min(dWizard, Math.Min(dTaoist, dAssassin))))
{
case 1:
dRate *= 1.1M;
break;
case 2:
dRate *= 1.2M;
break;
case 3:
dRate *= 1.3M;
break;
}
switch (Math.Min(eWarrior, Math.Min(eWizard, Math.Min(eTaoist, eAssassin))))
{
case 1:
eRate *= 1.1M;
break;
case 2:
eRate *= 1.25M;
break;
case 3:
eRate *= 1.5M;
break;
}
}
if (PetOwner == null && CurrentMap != null)
eRate *= 1 + MapExperienceRate / 100M;
decimal exp = Math.Min(Experience * eRate, 500000000);
if (ePlayers.Count == 0)
{
if (!EXPOwner.Dead && EXPOwner.CurrentMap == CurrentMap && Functions.InRange(EXPOwner.CurrentLocation, CurrentLocation, Config.MaxViewRange))
{
if (EXPOwner.Stats[Stat.Rebirth] > 0 && ExtraExperienceRate > 0)
exp /= ExtraExperienceRate;
EXPOwner.GainExperience(exp, PlayerTagged, Level);
OnYieldReward(EXPOwner);
}
}
else
{
if (ePlayers.Count > 1)
exp += exp * 0.06M * ePlayers.Count; //6% per nearby member.
foreach (PlayerObject player in ePlayers)
{
decimal expfinal = exp * player.Level / totalLevels;
if (player.Stats[Stat.Rebirth] > 0 && ExtraExperienceRate > 0)
expfinal /= ExtraExperienceRate;
player.GainExperience(expfinal, PlayerTagged, Level);
OnYieldReward(player);
}
}
if (dPlayers.Count == 0)
{
if (!EXPOwner.Dead && EXPOwner.CurrentMap == CurrentMap && Functions.InRange(EXPOwner.CurrentLocation, CurrentLocation, Config.MaxViewRange))
Drop(EXPOwner, 1, dRate);
}
else
{
foreach (PlayerObject player in dPlayers)
Drop(player, dPlayers.Count, dRate);
}
}
private bool CheckDrop(ItemInfo item)
{
switch(item.ItemType)
{
case ItemType.Armour:
case ItemType.Helmet:
case ItemType.Shoes:
case ItemType.Ring:
case ItemType.Bracelet:
case ItemType.Necklace:
if (Config.DropLowestEquipmentsExcludeWeapon > 0
&& item.RequiredType == RequiredType.Level
&& item.RequiredAmount < Config.DropLowestEquipmentsExcludeWeapon
&& item.Rarity == Rarity.Common) return false;
break;
case ItemType.Weapon:
if (Config.DropLowestWeapon > 0
&& item.RequiredType == RequiredType.Level
&& item.RequiredAmount < Config.DropLowestWeapon
&& item.Rarity == Rarity.Common) return false;
break;
}
return true;
}
public virtual void Drop(PlayerObject owner, int players, decimal rate)
{
rate *= 1M + owner.Stats[Stat.DropRate] / 100M;
rate *= 1M + (MonsterInfo.IsBoss && MonsterInfo.Level == 250 ? Config.Boss掉落倍率 : owner.Stats[Stat.BaseDropRate]) / 100M;
if (PetOwner == null && CurrentMap != null)
rate *= 1 + MapDropRate / 100M;
bool result = false;
List<UserItem> drops = null;
foreach (DropInfo drop in MonsterInfo.Drops)
{
if (drop?.Item == null
|| drop.Chance == 0
|| (DropSet & drop.DropSet) != drop.DropSet)
continue;
if (drop.Item.BlockMonsterDrop) continue;
if (drop.Item.ItemType == ItemType.Consumable
&& Config.不掉落低于本价格的普通药水 > 0
&& Config.不掉落低于本价格的普通药水 > drop.Item.Price
&& drop.Item.Shape == 0
&& drop.Item.Rarity == Rarity.Common)
continue;
if (drop.Item.Rarity == Rarity.Common
&& (drop.Item.ItemType == ItemType.Poison
|| drop.Item.ItemType == ItemType.Amulet
|| drop.Item.ItemType == ItemType.Torch)) continue;
if (drop.EasterEvent && !EasterEventMob) continue;
if (drop.Item.ItemType == ItemType.Nothing
&& !Config.DropNothingTypeCommonItem
&& drop.Item.Rarity == Rarity.Common
&& drop.Item.Effect == ItemEffect.None) continue;
long amount = Math.Max(1, drop.Amount / 2 + SEnvir.Random.Next(drop.Amount));
long chance;
if (drop.Item.Effect == ItemEffect.Gold)
{
if (owner.Character.Account.GoldBot && Level < owner.Level) continue;
chance = int.MaxValue / drop.Chance;
amount /= players;
amount += (int)(amount * owner.Stats[Stat.GoldRate] / 100M);
amount += (int)(amount * owner.Stats[Stat.BaseGoldRate] / 100M);
if (PetOwner == null && CurrentMap != null)
amount += (int)(amount * MapGoldRate / 100M);
if (amount == 0) continue;
}
else
{
chance = (long)(int.MaxValue / (drop.Chance * players) * rate);
}
UserDrop userDrop = owner.Character.Account.UserDrops.FirstOrDefault(x => x.Item == drop.Item);
if (userDrop == null)
{
userDrop = SEnvir.UserDropList.CreateNewObject();
userDrop.Item = drop.Item;
userDrop.Account = owner.Character.Account;
}
decimal progress = chance/(decimal) int.MaxValue;
progress *= amount;
if (!drop.PartOnly)
userDrop.Progress += progress;
if (drop.PartOnly ||
( (SEnvir.Random.Next() > chance || (drop.Item.Effect != ItemEffect.Gold && owner.Character.Account.ItemBot))
&& ((long) userDrop.Progress <= userDrop.DropCount || drop.Item.Effect == ItemEffect.Gold) ))
{
if (drop.Item.PartCount <= 1) continue;
if (SEnvir.Random.Next() > ((owner.Character.Account.ItemBot || drop.PartOnly)
? chance
: (chance * drop.Item.PartCount))) continue;
result = true;
UserItem item = SEnvir.CreateDropItem(SEnvir.ItemPartInfo);
item.AddStat(Stat.ItemIndex, drop.Item.Index, StatSource.Added);
item.StatsChanged();
item.IsTemporary = true;
if (NeedHarvest)
{
if (drops == null)
drops = new List<UserItem>();
if (drop.Item.Rarity != Rarity.Common)
{
owner.Connection.ReceiveChat(
string.Format(owner.Connection.Language.HarvestRare, MonsterInfo.MonsterName),
MessageType.System);
foreach (SConnection con in owner.Connection.Observers)
con.ReceiveChat(string.Format(con.Language.HarvestRare, MonsterInfo.MonsterName),
MessageType.System);
}
drops.Add(item);
continue;
}
Cell cell = GetDropLocation(Config.DropDistance, owner) ?? CurrentCell;
ItemObject ob = new ItemObject
{
Item = item,
//Account = owner.Character.Account,
MonsterDrop = true,
};
List<PlayerObject> pList = new();
ob.OwnerList.Add(owner.Character);
pList.Add(owner);
if (Config.MonsterDropGroupShare)
{
if (owner.GroupMembers != null)
foreach (var mem in owner.GroupMembers)
{
if (mem.Character == owner.Character) continue;
ob.OwnerList.Add(mem.Character);
pList.Add(mem);
}
}
if (Config.MonsterDropProtectionDuration > 0)
ob.OwnerExpire = SEnvir.Now.AddSeconds(Config.MonsterDropProtectionDuration);
ob.Spawn(CurrentMap.Info, cell.Location);
if (owner.Stats[Stat.CompanionCollection] > 0 && owner.Companion != null)
{
long goldAmount = 0;
foreach(var player in pList)
{
if (ob.Item.Info.Effect == ItemEffect.Gold && player.Character.Account.GuildMember != null && player.Character.Account.GuildMember.Guild.GuildTax > 0)
goldAmount = (long)Math.Ceiling(ob.Item.Count * player.Character.Account.GuildMember.Guild.GuildTax);
ItemCheck check = new ItemCheck(ob.Item, ob.Item.Count - goldAmount, ob.Item.Flags, ob.Item.ExpireTime);
if (player.Companion.CanGainItems(true, check)) ob.PickUpItem(player.Companion);
}
}
pList.Clear();
continue;
}
if (drop.Item.Effect != ItemEffect.Gold && Math.Floor(userDrop.Progress) > userDrop.DropCount + amount)
amount = (long)(userDrop.Progress - userDrop.DropCount);
userDrop.DropCount += amount;
result = true;
while (amount > 0)
{
UserItem item;
//if (drop.Item.Effect == ItemEffect.Gold && amount > 1000000000)
//{
// var goldbox = SEnvir.GetGoldBox();
// item = SEnvir.CreateDropItem(goldbox ?? drop.Item);
// item.Count = 1;
// amount -= (goldbox?.Price ?? item.Count);
//}
//else if (drop.Item.Effect == ItemEffect.Gold && amount > 10000000)
//{
// item = SEnvir.CreateDropItem(drop.Item);
// item.Count = Math.Min(drop.Item.StackSize, amount);
// amount -= item.Count;
//}
//else if (drop.Item.Effect == ItemEffect.Gold && amount > 1000000)
//{
// item = SEnvir.CreateDropItem(drop.Item);
// item.Count = Math.Min(drop.Item.StackSize, amount);
// amount -= item.Count;
//}
//else
{
item = SEnvir.CreateDropItem(drop.Item);
item.Count = Math.Min(drop.Item.StackSize, amount);
amount -= item.Count;
}
item.IsTemporary = true; //REMOVE ON Gain
if (item.AddedStats.Count <= 0 && !CheckDrop(item.Info))
{
item.Delete();
continue;
}
if (NeedHarvest)
{
if (drops == null)
drops = new List<UserItem>();
if (item.Info.Rarity != Rarity.Common)
{
owner.Connection.ReceiveChat(string.Format(owner.Connection.Language.HarvestRare, MonsterInfo.MonsterName), MessageType.System);
foreach (SConnection con in owner.Connection.Observers)
con.ReceiveChat(string.Format(con.Language.HarvestRare, MonsterInfo.MonsterName), MessageType.System);
}
drops.Add(item);
continue;
}
Cell cell = GetDropLocation(Config.DropDistance, owner) ?? CurrentCell;
List<PlayerObject> pList = new();
ItemObject ob = new ItemObject
{
Item = item,
//Account = owner.Character.Account,
MonsterDrop = true,
};
ob.OwnerList.Add(owner.Character);
pList.Add(owner);
if (Config.MonsterDropGroupShare)
{
if (owner.GroupMembers != null)
foreach (var mem in owner.GroupMembers)
{
if (mem.Character == owner.Character) continue;
ob.OwnerList.Add(mem.Character);
pList.Add(mem);
}
}
if (Config.MonsterDropProtectionDuration > 0)
ob.OwnerExpire = SEnvir.Now.AddSeconds(Config.MonsterDropProtectionDuration);
ob.Spawn(CurrentMap.Info, cell.Location);
if (owner.Stats[Stat.CompanionCollection] > 0 && owner.Companion != null)
{
long goldAmount = 0;
foreach(var player in pList)
{
if (ob.Item.Info.Effect == ItemEffect.Gold && player.Character.Account.GuildMember != null &&
player.Character.Account.GuildMember.Guild.GuildTax > 0)
goldAmount = (long)Math.Ceiling(ob.Item.Count * player.Character.Account.GuildMember.Guild.GuildTax);
ItemCheck check = new ItemCheck(ob.Item, ob.Item.Count - goldAmount, ob.Item.Flags,
ob.Item.ExpireTime);
if (player.Companion.CanGainItems(true, check)) ob.PickUpItem(player.Companion);
}
}
pList.Clear();
}
}
foreach (UserQuest quest in owner.Character.Quests)
{
//For Each Active Quest
if (quest.Completed) continue;
bool changed = false;
foreach (QuestTask task in quest.QuestInfo.Tasks)
{
bool valid = false;
int count = 0;
foreach (QuestTaskMonsterDetails details in task.MonsterDetails)
{
if (details.Monster != MonsterInfo) continue;
if (details.Map != null && CurrentMap != null && CurrentMap.Info != details.Map) continue;
if (SEnvir.Random.Next(details.Chance) > 0) continue;
if ((DropSet & details.DropSet) != details.DropSet) continue;
valid = true;
count = details.Amount;
break;
}
if (!valid) continue;
UserQuestTask userTask = quest.Tasks.FirstOrDefault(x => x.Task == task);
if (userTask == null)
{
userTask = SEnvir.UserQuestTaskList.CreateNewObject();
userTask.Task = task;
userTask.Quest = quest;
}
if (userTask.Completed) continue;
switch (task.Task)
{
case QuestTaskType.KillMonster:
userTask.Amount = Math.Min(task.Amount, userTask.Amount + count);
changed = true;
break;
case QuestTaskType.GainItem:
if (task.ItemParameter == null) continue;
UserItem item = SEnvir.CreateDropItem(task.ItemParameter);
item.Count = count;
item.UserTask = userTask;
item.Flags |= UserItemFlags.QuestItem;
item.IsTemporary = true; //REMOVE ON Gain
if (NeedHarvest)
{
if (drops == null)
drops = new List<UserItem>();
drops.Add(item);
continue;
}
Cell cell = GetDropLocation(Config.DropDistance, owner) ?? CurrentCell;
List<PlayerObject> pList = new();
ItemObject ob = new ItemObject
{
Item = item,
//Account = owner.Character.Account,
MonsterDrop = true,
};
ob.OwnerList.Add(owner.Character);
pList.Add(owner);
if (Config.MonsterDropGroupShare)
{
if (owner.GroupMembers != null)
foreach (var mem in owner.GroupMembers)
{
if (mem.Character == owner.Character) continue;
ob.OwnerList.Add(mem.Character);
pList.Add(mem);
}
}
if (Config.MonsterDropProtectionDuration > 0)
ob.OwnerExpire = SEnvir.Now.AddSeconds(Config.MonsterDropProtectionDuration);
ob.Spawn(CurrentMap.Info, cell.Location);
userTask.Objects.Add(ob);
if (owner.Stats[Stat.CompanionCollection] > 0 && owner.Companion != null)
{
long goldAmount = 0;
foreach(var player in pList)
{
if (ob.Item.Info.Effect == ItemEffect.Gold && player.Character.Account.GuildMember != null &&
player.Character.Account.GuildMember.Guild.GuildTax > 0)
goldAmount = (long)Math.Ceiling(ob.Item.Count * player.Character.Account.GuildMember.Guild.GuildTax);
ItemCheck check = new ItemCheck(ob.Item, ob.Item.Count - goldAmount, ob.Item.Flags,
ob.Item.ExpireTime);
if (player.Companion.CanGainItems(true, check)) ob.PickUpItem(player.Companion);
}
}
pList.Clear();
break;
}
}
if (changed)
owner.Enqueue(new S.QuestChanged { Quest = quest.ToClientInfo() });
}
if (result && owner.Companion != null)
owner.Companion.SearchTime = DateTime.MinValue;
if (!NeedHarvest) return;
if (Drops == null)
Drops = new Dictionary<AccountInfo, List<UserItem>>();
Drops[owner.Character.Account] = drops;
}
public void QierbianEnd(Cell cell, bool visible, Point displaylocation)
{
if (cell == null)
return;
SpellObject spellObject = new SpellObject();
spellObject.DisplayLocation = displaylocation;
spellObject.TickCount = 1;
spellObject.TickTime = SEnvir.Now.AddMilliseconds((double)(QierbianMin / 2 + SEnvir.Random.Next(QierbianRandom)));
spellObject.Owner = (MapObject)this;
spellObject.Effect = SpellEffect.QierBian;
spellObject.Visible = visible;
spellObject.Spawn(cell.Map.Info, cell.Location);
}
public virtual void LineAoE(
int distance,
int min,
int max,
MagicType magic,
Element element,
MirDirection dir)
{
Direction = Functions.DirectionFromPoint(CurrentLocation, Target.CurrentLocation);
List<uint> uintList = new List<uint>();
List<Point> pointList = new List<Point>();
Broadcast((Packet)new ObjectMagic()
{
ObjectID = ObjectID,
Direction = Direction,
CurrentLocation = CurrentLocation,
Cast = true,
Type = magic,
Targets = uintList,
Locations = pointList
});
UpdateAttackTime();
for (int i = min; i <= max; ++i)
{
MirDirection mirDirection = Functions.ShiftDirection(Direction, i);
if (magic == MagicType.LightningBeam || magic == MagicType.BlowEarth)
pointList.Add(Functions.Move(CurrentLocation, mirDirection, distance));
for (int distance1 = 1; distance1 <= distance; ++distance1)
{
Point location = Functions.Move(CurrentLocation, mirDirection, distance1);
Cell cell1 = CurrentMap.GetCell(location);
if (cell1 != null)
{
if (magic != MagicType.LightningBeam && magic != MagicType.BlowEarth)
pointList.Add(cell1.Location);
if (cell1.Objects != null)
{
foreach (MapObject ob in cell1.Objects)
{
if (CanAttackTarget(ob))
ActionList.Add(new DelayedAction(SEnvir.Now.AddMilliseconds((double)(500 + distance1 * 75)), ActionType.DelayAttack, new object[3]
{
ob,
GetDC(),
element
}));
}
}
switch (mirDirection)
{
case MirDirection.Up:
case MirDirection.Right:
case MirDirection.Down:
case MirDirection.Left:
Cell cell2 = CurrentMap.GetCell(Functions.Move(location, Functions.ShiftDirection(mirDirection, -2)));
if (cell2?.Objects != null)
{
foreach (MapObject ob in cell2.Objects)
{
if (CanAttackTarget(ob))
ActionList.Add(new DelayedAction(SEnvir.Now.AddMilliseconds((double)(500 + distance1 * 75)), ActionType.DelayAttack, new object[3]
{
ob,
(GetDC() / 2),
element
}));
}
}
Cell cell3 = CurrentMap.GetCell(Functions.Move(location, Functions.ShiftDirection(mirDirection, 2)));
if (cell3?.Objects != null)
{
using (List<MapObject>.Enumerator enumerator = cell3.Objects.GetEnumerator())
{
while (enumerator.MoveNext())
{
MapObject current = enumerator.Current;
if (CanAttackTarget(current))
ActionList.Add(new DelayedAction(SEnvir.Now.AddMilliseconds((double)(500 + distance1 * 75)), ActionType.DelayAttack, new object[3]
{
current,
(GetDC() / 2),
element
}));
}
break;
}
}
else
break;
case MirDirection.UpRight:
case MirDirection.DownRight:
case MirDirection.DownLeft:
case MirDirection.UpLeft:
Cell cell4 = CurrentMap.GetCell(Functions.Move(location, Functions.ShiftDirection(mirDirection, -1)));
if (cell4?.Objects != null)
{
foreach (MapObject ob in cell4.Objects)
{
if (CanAttackTarget(ob))
ActionList.Add(new DelayedAction(SEnvir.Now.AddMilliseconds((double)(500 + distance1 * 75)), ActionType.DelayAttack, new object[3]
{
ob,
(GetDC() / 2),
element
}));
}
}
Cell cell5 = CurrentMap.GetCell(Functions.Move(location, Functions.ShiftDirection(mirDirection, 1)));
if (cell5?.Objects != null)
{
using (List<MapObject>.Enumerator enumerator = cell5.Objects.GetEnumerator())
{
while (enumerator.MoveNext())
{
MapObject current = enumerator.Current;
if (CanAttackTarget(current))
ActionList.Add(new DelayedAction(SEnvir.Now.AddMilliseconds((double)(500 + distance1 * 75)), ActionType.DelayAttack, new object[3]
{
current,
(GetDC() / 2),
element
}));
}
break;
}
}
else
break;
}
}
}
}
}
public virtual void OnYieldReward(PlayerObject player)
{
}
public virtual void Turn(MirDirection direction)
{
if (!CanMove) return;
UpdateMoveTime();
Direction = direction;
Broadcast(new S.ObjectTurn { ObjectID = ObjectID, Direction = Direction, Location = CurrentLocation });
}
public virtual bool Walk(MirDirection direction)
{
if (!CanMove) return false;
Cell cell = CurrentMap.GetCell(Functions.Move(CurrentLocation, direction));
if (cell == null) return false;
if (cell.IsBlocking(this, false)) return false;
if (AvoidFireWall && cell.Objects != null)
{
foreach (MapObject ob in cell.Objects)
{
if (ob.Race != ObjectType.Spell) continue;
SpellObject spell = (SpellObject) ob;
switch (spell.Effect)
{
case SpellEffect.FireWall:
case SpellEffect.MonsterFireWall:
case SpellEffect.Tempest:
break;
default:
continue;
}
if (spell.Owner == null || !spell.Owner.CanAttackTarget(this)) continue;
return false;
}
}
BuffRemove(BuffType.Invisibility);
BuffRemove(BuffType.Transparency);
Direction = direction;
UpdateMoveTime();
PreventSpellCheck = true;
CurrentCell = cell; //.GetMovement(this);
PreventSpellCheck = false;
RemoveAllObjects();
AddAllObjects();
Broadcast(new S.ObjectMove { ObjectID = ObjectID, Direction = direction, Location = CurrentLocation, Distance = 1 });
CheckSpellObjects();
return true;
}
protected virtual void MoveTo(Point target)
{
if (CurrentLocation == target) return;
if (Functions.InRange(target, CurrentLocation, 1))
{
Cell cell = CurrentMap.GetCell(target);
if (cell == null || cell.IsBlocking(this, false)) return;
}
MirDirection direction = Functions.DirectionFromPoint(CurrentLocation, target);
int rotation = SEnvir.Random.Next(2) == 0 ? 1 : -1;
for (int d = 0; d < 8; d++)
{
if (Walk(direction)) return;
direction = Functions.ShiftDirection(direction, rotation);
}
}
public override BuffInfo BuffAdd(BuffType type, TimeSpan remainingTicks, Stats stats, bool visible, bool pause, TimeSpan tickRate)
{
BuffInfo info = base.BuffAdd(type, remainingTicks, stats, visible, pause, tickRate);
info.IsTemporary = true;
return info;
}
protected override void OnLocationChanged()
{
base.OnLocationChanged();
if (CurrentCell == null) return;
InSafeZone = CurrentCell.SafeZone != null;
}
public void HarvestChanged()
{
Skeleton = true;
if (Drops == null)
DeadTime -= Config.HarvestDuration;
foreach (PlayerObject player in SeenByPlayers)
if (Drops == null || !Drops.ContainsKey(player.Character.Account))
player.Enqueue(new S.ObjectHarvested { ObjectID = ObjectID, Direction = Direction, Location = CurrentLocation });
}
public override Packet GetInfoPacket(PlayerObject ob)
{
return new S.ObjectMonster
{
ObjectID = ObjectID,
MonsterIndex = MonsterInfo.Index,
Location = CurrentLocation,
NameColour = NameColour,
Direction = Direction,
Dead = Dead,
PetOwner = PetOwner != null ? PetOwner.Name : string.Empty,
Skeleton = NeedHarvest && Skeleton && (Drops == null || !Drops.ContainsKey(ob.Character.Account)),
Poison = Poison,
EasterEvent = EasterEventMob,
HalloweenEvent = HalloweenEventMob,
ChristmasEvent = ChristmasEventMob,
Buffs = Buffs.Where(x => x.Visible).Select(x => x.Type).ToList()
};
}
public override Packet GetDataPacket(PlayerObject ob)
{
return new S.DataObjectMonster
{
ObjectID = ObjectID,
MonsterIndex = MonsterInfo.Index,
MapIndex = CurrentMap.Info.Index,
CurrentLocation = CurrentLocation,
Health = DisplayHP,
Stats = Stats,
Dead = Dead,
PetOwner = PetOwner != null ? PetOwner.Name : string.Empty,
};
}
}
}