Code:
public sealed partial class MagicData
{
private const int MAX_TARGET_NUM = 25;
private const int TC_PK_ARENA_ID = 1005;
private readonly TimeOutMS mDelay = new(MAGIC_DELAY);
private readonly TimeOutMS mIntone = new();
private readonly TimeOutMS mMagicDelay = new(MAGIC_DELAY);
private ushort mTypeMagic;
private Point mTargetPos;
private uint mIdTarget;
private bool mAutoAttack;
private int mAutoAttackNum;
public MagicState State { get; private set; } = MagicState.None;
public async Task<(bool Success, ushort X, ushort Y)> CheckConditionAsync(Magic magic, uint idTarget, ushort x,
ushort y)
{
int delay = mOwner.Map.IsTrainingMap()
? MAGIC_DELAY
: MAGIC_DELAY - magic.Level * MAGIC_DECDELAY_PER_LEVEL;
if (!mMagicDelay.IsTimeOut(delay) &&
magic.Sort != MagicSort.Collide)
return (false, x, y);
if (!magic.IsReady())
return (false, x, y);
if (mOwner.Map.IsLineSkillMap() && magic.Sort != MagicSort.Line)
return (false, x, y);
if (!((magic.AutoActive & 1) == 1
|| (magic.AutoActive & 4) == 4) && magic.Type != 6001)
if (!await Kernel.ChanceCalcAsync(magic.Percent))
return (false, x, y);
GameMap map = mOwner.Map;
var user = mOwner as Character;
Role role = null;
if (user != null && user.Map.QueryRegion(RegionTypes.PkProtected, user.MapX, user.MapY))
{
if (magic.Ground > 0)
{
if (magic.Crime > 0)
return (false, x, y);
}
else
{
role = map.QueryAroundRole(user, idTarget);
if (role is Character && magic.Crime > 0)
return (false, x, y);
}
}
if (map.IsLineSkillMap() && magic.Sort != MagicSort.Line)
return (false, x, y);
if (user?.CurrentEvent != null && !user.CurrentEvent.IsAttackEnable(user) &&
magic.Sort != MagicSort.Attachstatus)
return (false, x, y);
if (!map.IsTrainingMap() && user != null)
{
if (user.Mana < magic.UseMana)
return (false, x, y);
if (user.Energy < magic.UseStamina)
return (false, x, y);
if (magic.UseItem > 0 && user.CheckWeaponSubType(magic.UseItem, magic.UseItemNum))
return (false, x, y);
}
if (magic.UseXp == BattleSystem.MagicType.Normal)
{
IStatus pStatus = mOwner.QueryStatus(StatusSet.START_XP);
if (pStatus == null && magic.Status != StatusSet.VORTEX)
return (false, x, y);
}
if (magic.WeaponSubtype > 0 && user != null)
{
if (!user.CheckWeaponSubType(magic.WeaponSubtype))
return (false, x, y);
if (magic.Type == TWOFOLDBLADES_ID
&& user.UserPackage[Item.ItemPosition.RightHand].GetItemSubType() !=
user.UserPackage[Item.ItemPosition.LeftHand]?.GetItemSubType())
return (false, x, y);
}
if (user != null && user.TransformationMesh != 0)
return (false, x, y);
if (mOwner.IsWing && magic.Sort == MagicSort.Transform)
return (false, x, y);
if (map.IsWingDisable() && magic.Sort == MagicSort.Attachstatus && magic.Status == StatusSet.FLY)
return (false, x, y);
if (magic.Ground == 0 && magic.Sort != MagicSort.Groundsting
&& magic.Sort != MagicSort.Vortex
&& magic.Sort != MagicSort.Dashwhirl
&& magic.Sort != MagicSort.Dashdeadmark
&& magic.Sort != MagicSort.Mountwhirl)
{
role = map.QueryAroundRole(mOwner, idTarget);
if (role == null)
return (false, x, y);
if (!role.IsAlive
&& magic.Sort != MagicSort.Attachstatus
&& magic.Sort != MagicSort.Detachstatus)
return (false, x, y);
if (magic.Sort == MagicSort.Declife)
if (role.Life * 100 / role.MaxLife >= 15)
return (false, x, y);
x = role.MapX;
y = role.MapY;
}
if (HitByMagic(magic) != 0 && !HitByWeapon() && mOwner.GetDistance(x, y) > magic.Distance + 1)
return (false, x, y);
if (mOwner.GetDistance(x, y) > mOwner.GetAttackRange(0) + magic.Distance + 1)
return (false, x, y);
if (role is DynamicNpc dyna)
if (dyna.IsGoal() && mOwner.Level < dyna.Level)
return (false, x, y);
return (true, x, y);
}
public async Task<bool> ProcessMagicAttackAsync(ushort usMagicType, uint idTarget, ushort x, ushort y,
uint ucAutoActive = 0)
{
switch (State)
{
case MagicState.Intone:
await AbortMagicAsync(true);
break;
}
State = MagicState.None;
mTypeMagic = usMagicType;
if (!Magics.TryGetValue(usMagicType, out Magic magic)
&& (ucAutoActive == 0 || (magic?.AutoActive ?? 0 & ucAutoActive) != 0))
{
await Log.GmLogAsync(
"cheat", $"invalid magic type: {usMagicType}, user[{mOwner.Name}][{mOwner.Identity}]");
return false;
}
if (magic == null)
return false;
(bool Success, ushort X, ushort Y) result = await CheckConditionAsync(magic, idTarget, x, y);
if (!result.Success)
{
if (magic.Sort == MagicSort.Collide)
await ProcessCollideFailAsync(x, y, (int) idTarget);
await AbortMagicAsync(true);
return false;
}
mIdTarget = idTarget;
mTargetPos = new Point(x, y);
var user = mOwner as Character;
GameMap map = mOwner.Map;
if (user != null && !map.IsTrainingMap() && map.Identity != TC_PK_ARENA_ID)
{
if (magic.UseMana > 0)
await user.AddAttributesAsync(ClientUpdateType.Mana, magic.UseMana * -1);
if (magic.UseStamina > 0)
await user.AddAttributesAsync(ClientUpdateType.Stamina, magic.UseStamina * -1);
if (magic.UseItemNum > 0)
await user.SpendEquipItemAsync(magic.WeaponSubtype, magic.UseItemNum, true);
if (magic.UseItemNum > 0 && user.UserPackage[Item.ItemPosition.RightHand]?.IsBow() == true)
await user.SpendEquipItemAsync(50, magic.UseItemNum, true);
}
if (magic.UseXp == BattleSystem.MagicType.Normal && user != null)
{
if (magic.Status == StatusSet.VORTEX && mOwner.QueryStatus(StatusSet.VORTEX) != null)
{
}
else
{
IStatus pStatus = mOwner.QueryStatus(StatusSet.START_XP);
if (pStatus == null)
{
await AbortMagicAsync(true);
return false;
}
await user.DetachStatusAsync(StatusSet.START_XP);
await user.ClsXpValAsync();
}
}
if (!IsWeaponMagic(magic.Type))
await mOwner.BroadcastRoomMsgAsync(new MsgInteract
{
Action = MsgInteractType.MagicAttack,
TargetIdentity = idTarget,
SenderIdentity = mOwner.Identity,
PosX = mOwner.MapX,
PosY = mOwner.MapY
}, true);
mTypeMagic = magic.Type; // for auto attack!
if (magic.UseMana != 0)
{
if (!map.IsTrainingMap() && user != null)
await user.DecEquipmentDurabilityAsync(false, (int) HitByMagic(magic), (ushort) magic.UseItemNum);
if (await Kernel.ChanceCalcAsync(7) && user != null)
await user.SendGemEffectAsync();
}
mMagicDelay.Update();
await mOwner.ProcessOnAttackAsync();
if (magic.IntoneSpeed <= 0)
{
if (!await LaunchAsync(magic)) // pode ocorrer caso o monstro desapareça, morra antes da hora
{
ResetDelay();
}
else
{
if (mOwner.Map.IsTrainingMap() || IsAutoAttack())
{
SetAutoAttack(magic.Type);
mDelay.Startup(Math.Max(MAGIC_DELAY, magic.DelayMs));
State = MagicState.Delay;
return true;
}
State = MagicState.None;
}
}
else
{
State = MagicState.Intone;
mIntone.Startup((int) magic.IntoneSpeed);
}
return true;
}
#region Processing
private async Task<bool> LaunchAsync(Magic magic)
{
var result = false;
try
{
if (magic == null)
return false;
if (!mOwner.IsAlive)
return false;
magic.Use();
switch (magic.Sort)
{
case MagicSort.Attack:
result = await ProcessAttackAsync(magic);
break;
case MagicSort.Recruit:
result = await ProcessRecruitAsync(magic);
break;
case MagicSort.Fan:
result = await ProcessFanAsync(magic);
break;
case MagicSort.Bomb:
result = await ProcessBombAsync(magic);
break;
case MagicSort.Attachstatus:
result = await ProcessAttachAsync(magic);
break;
case MagicSort.Detachstatus:
result = await ProcessDetachAsync(magic);
break;
case MagicSort.Dispatchxp:
result = await ProcessDispatchXpAsync(magic);
break;
case MagicSort.Line:
result = await ProcessLineAsync(magic);
break;
case MagicSort.Atkstatus:
result = await ProcessAttackStatusAsync(magic);
break;
case MagicSort.Transform:
result = await ProcessTransformAsync(magic);
break;
case MagicSort.Addmana:
result = await ProcessAddManaAsync(magic);
break;
case MagicSort.Callpet:
result = await ProcessCallPetAsync(magic);
break;
case MagicSort.Groundsting:
result = await ProcessGroundStingAsync(magic);
break;
case MagicSort.Vortex:
result = await ProcessVortexAsync(magic);
break;
case MagicSort.Activateswitch:
result = await ProcessActivateSwitchAsync(magic);
break;
case MagicSort.Spook:
result = await ProcessSpookAsync(magic);
break;
case MagicSort.Warcry:
result = await ProcessWarCryAsync(magic);
break;
case MagicSort.Riding:
result = await ProcessRidingAsync(magic);
break;
default:
await Log.WriteLogAsync(LogLevel.Warning,
$"MagicProcessing::LaunchAsync {magic.Sort} not handled!!!");
result = true;
break;
}
}
catch (Exception ex)
{
await Log.WriteLogAsync(LogLevel.Error, "Error ocurred on MagicProcessing::LaunchAsync");
await Log.WriteLogAsync(LogLevel.Exception, ex.ToString());
}
mAutoAttackNum++;
return result;
}
#endregion
#region Magic Processing Manage
private void ResetDelay()
{
if (!Magics.TryGetValue(mTypeMagic, out Magic magic))
return;
State = MagicState.Delay;
mDelay.Update();
magic.SetDelay();
}
private void SetAutoAttack(ushort type)
{
mTypeMagic = type;
mAutoAttack = true;
}
private void BreakAutoAttack()
{
mTypeMagic = 0;
mAutoAttack = false;
}
public bool IsAutoAttack()
{
return mAutoAttack && mTypeMagic != 0;
}
#endregion
#region Abort Magic
public async Task<bool> AbortMagicAsync(bool bSynchro)
{
BreakAutoAttack();
if (State == MagicState.Intone) mIntone.Clear();
State = MagicState.None;
if (bSynchro && mOwner is Character)
await mOwner.SendAsync(new MsgAction
{
Identity = mOwner.Identity,
Action = MsgAction<Client>.ActionType.AbortMagic
});
return true;
}
#endregion
#region On Timer
public async Task OnTimerAsync()
{
if (!Magics.TryGetValue(mTypeMagic, out Magic magic))
{
State = MagicState.None;
return;
}
switch (State)
{
case MagicState.Intone: // intone
{
if (mIntone != null && !mIntone.IsTimeOut())
return;
if (mIntone != null && mIntone.IsTimeOut() && !await LaunchAsync(magic)) ResetDelay();
State = MagicState.None;
if (IsAutoAttack())
{
State = MagicState.Delay;
mDelay.Startup(Math.Max(MAGIC_DELAY, magic.DelayMs));
}
break;
}
case MagicState.Delay: // delay
{
if ((mOwner.Map.IsTrainingMap() || IsAutoAttack())
&& mDelay.IsActive()
&& magic.Sort != MagicSort.Atkstatus)
{
if (mDelay.IsTimeOut())
{
State = MagicState.None;
if (!await mOwner.ProcessMagicAttackAsync(magic.Type, mIdTarget, (ushort) mTargetPos.X,
(ushort) mTargetPos.Y))
State = MagicState.Delay;
}
return;
}
if (!mDelay.IsActive())
{
State = MagicState.None;
await AbortMagicAsync(true);
return;
}
if (mAutoAttack && mDelay.IsTimeOut())
{
if (mDelay.IsActive() && !mDelay.TimeOver())
return;
State = MagicState.None;
await mOwner.ProcessMagicAttackAsync(magic.Type, mIdTarget, (ushort) mTargetPos.X,
(ushort) mTargetPos.Y);
if (mIdTarget != 0 && mOwner.Map.QueryAroundRole(mOwner, mIdTarget)?.IsPlayer() == true)
await AbortMagicAsync(false);
}
if (mDelay.IsActive() && mDelay.TimeOver())
{
State = MagicState.None;
await AbortMagicAsync(false);
}
break;
}
}
}
#endregion
public enum MagicState
{
None = 0,
Intone = 1,
Delay = 2
}
}