mirror of
https://bitbucket.org/Ioncannon/project-meteor-server.git
synced 2025-05-20 08:26:59 -04:00
Updated Map Server namespace. Moved all other data folders (www and sql) to data folder. Renamed boot name to Project Meteor.
This commit is contained in:
169
Map Server/Actors/Chara/Ai/State/AbilityState.cs
Normal file
169
Map Server/Actors/Chara/Ai/State/AbilityState.cs
Normal file
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 2015-2019 Project Meteor Dev Team
|
||||
|
||||
This file is part of Project Meteor Server.
|
||||
|
||||
Project Meteor Server is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Project Meteor Server is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Meteor.Common;
|
||||
using Meteor.Map.Actors;
|
||||
using Meteor.Map.packets.send.actor.battle;
|
||||
|
||||
namespace Meteor.Map.actors.chara.ai.state
|
||||
{
|
||||
class AbilityState : State
|
||||
{
|
||||
|
||||
private BattleCommand skill;
|
||||
|
||||
public AbilityState(Character owner, Character target, ushort skillId) :
|
||||
base(owner, target)
|
||||
{
|
||||
this.startTime = DateTime.Now;
|
||||
this.skill = Server.GetWorldManager().GetBattleCommand(skillId);
|
||||
var returnCode = skill.CallLuaFunction(owner, "onAbilityPrepare", owner, target, skill);
|
||||
|
||||
this.target = (skill.mainTarget & ValidTarget.SelfOnly) != 0 ? owner : target;
|
||||
|
||||
errorResult = new CommandResult(owner.actorId, 32553, 0);
|
||||
if (returnCode == 0)
|
||||
{
|
||||
OnStart();
|
||||
}
|
||||
else
|
||||
{
|
||||
errorResult = null;
|
||||
interrupt = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
var returnCode = skill.CallLuaFunction(owner, "onAbilityStart", owner, target, skill);
|
||||
|
||||
if (returnCode != 0)
|
||||
{
|
||||
interrupt = true;
|
||||
errorResult = new CommandResult(owner.actorId, (ushort)(returnCode == -1 ? 32558 : returnCode), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!skill.IsInstantCast())
|
||||
{
|
||||
float castTime = skill.castTimeMs;
|
||||
|
||||
// command casting duration
|
||||
if (owner is Player)
|
||||
{
|
||||
// todo: modify spellSpeed based on modifiers and stuff
|
||||
((Player)owner).SendStartCastbar(skill.id, Utils.UnixTimeStampUTC(DateTime.Now.AddMilliseconds(castTime)));
|
||||
}
|
||||
owner.GetSubState().chantId = 0xf0;
|
||||
owner.SubstateModified();
|
||||
//You ready [skill] (6F000002: BLM, 6F000003: WHM, 0x6F000008: BRD)
|
||||
owner.DoBattleAction(skill.id, (uint)0x6F000000 | skill.castType, new CommandResult(target.actorId, 30126, 1, 0, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Update(DateTime tick)
|
||||
{
|
||||
if (skill != null)
|
||||
{
|
||||
TryInterrupt();
|
||||
|
||||
if (interrupt)
|
||||
{
|
||||
OnInterrupt();
|
||||
return true;
|
||||
}
|
||||
|
||||
// todo: check weapon delay/haste etc and use that
|
||||
var actualCastTime = skill.castTimeMs;
|
||||
|
||||
if ((tick - startTime).TotalMilliseconds >= skill.castTimeMs)
|
||||
{
|
||||
OnComplete();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void OnInterrupt()
|
||||
{
|
||||
// todo: send paralyzed/sleep message etc.
|
||||
if (errorResult != null)
|
||||
{
|
||||
owner.DoBattleAction(skill.id, errorResult.animation, errorResult);
|
||||
errorResult = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnComplete()
|
||||
{
|
||||
owner.LookAt(target);
|
||||
bool hitTarget = false;
|
||||
|
||||
skill.targetFind.FindWithinArea(target, skill.validTarget, skill.aoeTarget);
|
||||
isCompleted = true;
|
||||
|
||||
owner.DoBattleCommand(skill, "ability");
|
||||
}
|
||||
|
||||
public override void TryInterrupt()
|
||||
{
|
||||
if (interrupt)
|
||||
return;
|
||||
|
||||
if (owner.statusEffects.HasStatusEffectsByFlag((uint)StatusEffectFlags.PreventAbility))
|
||||
{
|
||||
// todo: sometimes paralyze can let you attack, get random percentage of actually letting you attack
|
||||
var list = owner.statusEffects.GetStatusEffectsByFlag((uint)StatusEffectFlags.PreventAbility);
|
||||
uint effectId = 0;
|
||||
if (list.Count > 0)
|
||||
{
|
||||
// todo: actually check proc rate/random chance of whatever effect
|
||||
effectId = list[0].GetStatusEffectId();
|
||||
}
|
||||
interrupt = true;
|
||||
return;
|
||||
}
|
||||
|
||||
interrupt = !CanUse();
|
||||
}
|
||||
|
||||
private bool CanUse()
|
||||
{
|
||||
return skill.IsValidMainTarget(owner, target);
|
||||
}
|
||||
|
||||
public BattleCommand GetWeaponSkill()
|
||||
{
|
||||
return skill;
|
||||
}
|
||||
|
||||
public override void Cleanup()
|
||||
{
|
||||
owner.GetSubState().chantId = 0x0;
|
||||
owner.SubstateModified();
|
||||
owner.aiContainer.UpdateLastActionTime(skill.animationDurationSeconds);
|
||||
}
|
||||
}
|
||||
}
|
232
Map Server/Actors/Chara/Ai/State/AttackState.cs
Normal file
232
Map Server/Actors/Chara/Ai/State/AttackState.cs
Normal file
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 2015-2019 Project Meteor Dev Team
|
||||
|
||||
This file is part of Project Meteor Server.
|
||||
|
||||
Project Meteor Server is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Project Meteor Server is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Meteor.Common;
|
||||
using Meteor.Map.Actors;
|
||||
using Meteor.Map.packets.send.actor;
|
||||
using Meteor.Map.packets.send.actor.battle;
|
||||
namespace Meteor.Map.actors.chara.ai.state
|
||||
{
|
||||
class AttackState : State
|
||||
{
|
||||
private DateTime attackTime;
|
||||
|
||||
public AttackState(Character owner, Character target) :
|
||||
base(owner, target)
|
||||
{
|
||||
this.canInterrupt = false;
|
||||
this.startTime = DateTime.Now;
|
||||
|
||||
owner.ChangeState(SetActorStatePacket.MAIN_STATE_ACTIVE);
|
||||
ChangeTarget(target);
|
||||
attackTime = startTime;
|
||||
owner.aiContainer.pathFind?.Clear();
|
||||
}
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override bool Update(DateTime tick)
|
||||
{
|
||||
if ((target == null || owner.target != target || owner.target?.actorId != owner.currentLockedTarget) && owner.isAutoAttackEnabled)
|
||||
owner.aiContainer.ChangeTarget(target = owner.zone.FindActorInArea<Character>(owner.currentTarget));
|
||||
|
||||
if (target == null || target.IsDead())
|
||||
{
|
||||
if (owner.IsMonster() || owner.IsAlly())
|
||||
target = ((BattleNpc)owner).hateContainer.GetMostHatedTarget();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsAttackReady())
|
||||
{
|
||||
if (CanAttack())
|
||||
{
|
||||
TryInterrupt();
|
||||
|
||||
// todo: check weapon delay/haste etc and use that
|
||||
if (!interrupt)
|
||||
{
|
||||
OnComplete();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
SetInterrupted(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// todo: handle interrupt/paralyze etc
|
||||
}
|
||||
attackTime = DateTime.Now.AddMilliseconds(owner.GetAttackDelayMs());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void OnInterrupt()
|
||||
{
|
||||
// todo: send paralyzed/sleep message etc.
|
||||
if (errorResult != null)
|
||||
{
|
||||
owner.zone.BroadcastPacketAroundActor(owner, CommandResultX01Packet.BuildPacket(errorResult.targetId, errorResult.animation, 0x765D, errorResult));
|
||||
errorResult = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnComplete()
|
||||
{
|
||||
//BattleAction action = new BattleAction(target.actorId, 0x765D, (uint) HitEffect.Hit, 0, (byte) HitDirection.None);
|
||||
errorResult = null;
|
||||
|
||||
// todo: implement auto attack damage bonus in Character.OnAttack
|
||||
/*
|
||||
≪Auto-attack Damage Bonus≫
|
||||
Class Bonus 1 Bonus 2
|
||||
Pugilist Intelligence Strength
|
||||
Gladiator Mind Strength
|
||||
Marauder Vitality Strength
|
||||
Archer Dexterity Piety
|
||||
Lancer Piety Strength
|
||||
Conjurer Mind Piety
|
||||
Thaumaturge Mind Piety
|
||||
* The above damage bonus also applies to “Shot” attacks by archers.
|
||||
*/
|
||||
// handle paralyze/intimidate/sleep/whatever in Character.OnAttack
|
||||
|
||||
|
||||
// todo: Change this to use a BattleCommand like the other states
|
||||
|
||||
//List<BattleAction> actions = new List<BattleAction>();
|
||||
CommandResultContainer actions = new CommandResultContainer();
|
||||
|
||||
//This is all temporary until the skill sheet is finishd and the different auto attacks are added to the database
|
||||
//Some mobs have multiple unique auto attacks that they switch between as well as ranged auto attacks, so we'll need a way to handle that
|
||||
//For now, just use a temporary hardcoded BattleCommand that's the same for everyone.
|
||||
BattleCommand attackCommand = new BattleCommand(22104, "Attack");
|
||||
attackCommand.range = 5;
|
||||
attackCommand.rangeHeight = 10;
|
||||
attackCommand.worldMasterTextId = 0x765D;
|
||||
attackCommand.mainTarget = (ValidTarget)768;
|
||||
attackCommand.validTarget = (ValidTarget)17152;
|
||||
attackCommand.commandType = CommandType.AutoAttack;
|
||||
attackCommand.numHits = (byte)owner.GetMod(Modifier.HitCount);
|
||||
attackCommand.basePotency = 100;
|
||||
ActionProperty property = (owner.GetMod(Modifier.AttackType) != 0) ? (ActionProperty)owner.GetMod(Modifier.AttackType) : ActionProperty.Slashing;
|
||||
attackCommand.actionProperty = property;
|
||||
attackCommand.actionType = ActionType.Physical;
|
||||
|
||||
uint anim = (17 << 24 | 1 << 12);
|
||||
|
||||
if (owner is Player)
|
||||
anim = (25 << 24 | 1 << 12);
|
||||
|
||||
attackCommand.battleAnimation = anim;
|
||||
|
||||
if (owner.CanUse(target, attackCommand))
|
||||
{
|
||||
attackCommand.targetFind.FindWithinArea(target, attackCommand.validTarget, attackCommand.aoeTarget);
|
||||
owner.DoBattleCommand(attackCommand, "autoattack");
|
||||
}
|
||||
}
|
||||
|
||||
public override void TryInterrupt()
|
||||
{
|
||||
if (owner.statusEffects.HasStatusEffectsByFlag((uint)StatusEffectFlags.PreventAttack))
|
||||
{
|
||||
// todo: sometimes paralyze can let you attack, calculate proc rate
|
||||
var list = owner.statusEffects.GetStatusEffectsByFlag((uint)StatusEffectFlags.PreventAttack);
|
||||
uint statusId = 0;
|
||||
if (list.Count > 0)
|
||||
{
|
||||
statusId = list[0].GetStatusId();
|
||||
}
|
||||
interrupt = true;
|
||||
return;
|
||||
}
|
||||
|
||||
interrupt = !CanAttack();
|
||||
}
|
||||
|
||||
private bool IsAttackReady()
|
||||
{
|
||||
// todo: this enforced delay should really be changed if it's not retail..
|
||||
return Program.Tick >= attackTime && Program.Tick >= owner.aiContainer.GetLastActionTime();
|
||||
}
|
||||
|
||||
private bool CanAttack()
|
||||
{
|
||||
if (!owner.isAutoAttackEnabled || target.allegiance == owner.allegiance)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!owner.IsFacing(target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// todo: shouldnt need to check if owner is dead since all states would be cleared
|
||||
if (owner.IsDead() || target.IsDead())
|
||||
{
|
||||
if (owner.IsMonster() || owner.IsAlly())
|
||||
((BattleNpc)owner).hateContainer.ClearHate(target);
|
||||
|
||||
owner.aiContainer.ChangeTarget(null);
|
||||
return false;
|
||||
}
|
||||
else if (!owner.IsValidTarget(target, ValidTarget.Enemy) || !owner.aiContainer.GetTargetFind().CanTarget(target, false, true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// todo: use a mod for melee range
|
||||
else if (Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, target.positionX, target.positionY, target.positionZ) > owner.GetAttackRange())
|
||||
{
|
||||
if (owner is Player)
|
||||
{
|
||||
//The target is too far away
|
||||
((Player)owner).SendGameMessage(Server.GetWorldManager().GetActor(), 32537, 0x20);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Cleanup()
|
||||
{
|
||||
if (owner.IsDead())
|
||||
owner.Disengage();
|
||||
}
|
||||
|
||||
public override bool CanChangeState()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
63
Map Server/Actors/Chara/Ai/State/DeathState.cs
Normal file
63
Map Server/Actors/Chara/Ai/State/DeathState.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 2015-2019 Project Meteor Dev Team
|
||||
|
||||
This file is part of Project Meteor Server.
|
||||
|
||||
Project Meteor Server is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Project Meteor Server is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Meteor.Map.Actors;
|
||||
using Meteor.Map.packets.send.actor;
|
||||
|
||||
namespace Meteor.Map.actors.chara.ai.state
|
||||
{
|
||||
class DeathState : State
|
||||
{
|
||||
DateTime despawnTime;
|
||||
public DeathState(Character owner, DateTime tick, uint timeToFadeOut)
|
||||
: base(owner, null)
|
||||
{
|
||||
owner.Disengage();
|
||||
owner.ChangeState(SetActorStatePacket.MAIN_STATE_DEAD);
|
||||
owner.statusEffects.RemoveStatusEffectsByFlags((uint)StatusEffectFlags.LoseOnDeath);
|
||||
//var deathStatePacket = SetActorStatePacket.BuildPacket(owner.actorId, SetActorStatePacket.MAIN_STATE_DEAD2, owner.currentSubState);
|
||||
//owner.zone.BroadcastPacketAroundActor(owner, deathStatePacket);
|
||||
canInterrupt = false;
|
||||
startTime = tick;
|
||||
despawnTime = startTime.AddSeconds(timeToFadeOut);
|
||||
}
|
||||
|
||||
public override bool Update(DateTime tick)
|
||||
{
|
||||
// todo: set a flag on chara for accept raise, play animation and spawn
|
||||
if (owner.GetMod((uint)Modifier.Raise) > 0)
|
||||
{
|
||||
owner.Spawn(tick);
|
||||
return true;
|
||||
}
|
||||
|
||||
// todo: handle raise etc
|
||||
if (tick >= despawnTime)
|
||||
{
|
||||
// todo: for players, return them to homepoint
|
||||
owner.Despawn(tick);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
51
Map Server/Actors/Chara/Ai/State/DespawnState.cs
Normal file
51
Map Server/Actors/Chara/Ai/State/DespawnState.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 2015-2019 Project Meteor Dev Team
|
||||
|
||||
This file is part of Project Meteor Server.
|
||||
|
||||
Project Meteor Server is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Project Meteor Server is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Meteor.Map.Actors;
|
||||
using Meteor.Map.packets.send.actor;
|
||||
|
||||
namespace Meteor.Map.actors.chara.ai.state
|
||||
{
|
||||
class DespawnState : State
|
||||
{
|
||||
private DateTime respawnTime;
|
||||
public DespawnState(Character owner, uint respawnTimeSeconds) :
|
||||
base(owner, null)
|
||||
{
|
||||
startTime = Program.Tick;
|
||||
respawnTime = startTime.AddSeconds(respawnTimeSeconds);
|
||||
owner.ChangeState(SetActorStatePacket.MAIN_STATE_DEAD2);
|
||||
owner.OnDespawn();
|
||||
}
|
||||
|
||||
public override bool Update(DateTime tick)
|
||||
{
|
||||
if (tick >= respawnTime)
|
||||
{
|
||||
owner.ResetTempVars();
|
||||
owner.Spawn(tick);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
59
Map Server/Actors/Chara/Ai/State/InactiveState.cs
Normal file
59
Map Server/Actors/Chara/Ai/State/InactiveState.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 2015-2019 Project Meteor Dev Team
|
||||
|
||||
This file is part of Project Meteor Server.
|
||||
|
||||
Project Meteor Server is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Project Meteor Server is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Meteor.Map.Actors;
|
||||
|
||||
namespace Meteor.Map.actors.chara.ai.state
|
||||
{
|
||||
class InactiveState : State
|
||||
{
|
||||
private DateTime endTime;
|
||||
private uint durationMs;
|
||||
public InactiveState(Character owner, uint durationMs, bool canChangeState) :
|
||||
base(owner, null)
|
||||
{
|
||||
if (!canChangeState)
|
||||
owner.aiContainer.InterruptStates();
|
||||
this.durationMs = durationMs;
|
||||
endTime = DateTime.Now.AddMilliseconds(durationMs);
|
||||
}
|
||||
|
||||
public override bool Update(DateTime tick)
|
||||
{
|
||||
if (durationMs == 0)
|
||||
{
|
||||
if (owner.IsDead())
|
||||
return true;
|
||||
|
||||
if (!owner.statusEffects.HasStatusEffectsByFlag(StatusEffectFlags.PreventMovement))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (durationMs != 0 && tick > endTime)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
38
Map Server/Actors/Chara/Ai/State/ItemState.cs
Normal file
38
Map Server/Actors/Chara/Ai/State/ItemState.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 2015-2019 Project Meteor Dev Team
|
||||
|
||||
This file is part of Project Meteor Server.
|
||||
|
||||
Project Meteor Server is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Project Meteor Server is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
using Meteor.Map.Actors;
|
||||
using Meteor.Map.dataobjects;
|
||||
|
||||
namespace Meteor.Map.actors.chara.ai.state
|
||||
{
|
||||
class ItemState : State
|
||||
{
|
||||
ItemData item;
|
||||
new Player owner;
|
||||
public ItemState(Player owner, Character target, ushort slot, uint itemId) :
|
||||
base(owner, target)
|
||||
{
|
||||
this.owner = owner;
|
||||
this.target = target;
|
||||
}
|
||||
}
|
||||
}
|
213
Map Server/Actors/Chara/Ai/State/MagicState.cs
Normal file
213
Map Server/Actors/Chara/Ai/State/MagicState.cs
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 2015-2019 Project Meteor Dev Team
|
||||
|
||||
This file is part of Project Meteor Server.
|
||||
|
||||
Project Meteor Server is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Project Meteor Server is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Meteor.Common;
|
||||
using Meteor.Map.Actors;
|
||||
using Meteor.Map.packets.send.actor.battle;
|
||||
|
||||
namespace Meteor.Map.actors.chara.ai.state
|
||||
{
|
||||
class MagicState : State
|
||||
{
|
||||
|
||||
private BattleCommand spell;
|
||||
private Vector3 startPos;
|
||||
|
||||
public MagicState(Character owner, Character target, ushort spellId) :
|
||||
base(owner, target)
|
||||
{
|
||||
this.startPos = owner.GetPosAsVector3();
|
||||
this.startTime = DateTime.Now;
|
||||
this.spell = Server.GetWorldManager().GetBattleCommand(spellId);
|
||||
var returnCode = spell.CallLuaFunction(owner, "onMagicPrepare", owner, target, spell);
|
||||
|
||||
//modify skill based on status effects
|
||||
//Do this here to allow buffs like Resonance to increase range before checking CanCast()
|
||||
owner.statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnCastStart, "onMagicCast", owner, spell);
|
||||
|
||||
this.target = (spell.mainTarget & ValidTarget.SelfOnly) != 0 ? owner : target;
|
||||
|
||||
errorResult = new CommandResult(owner.actorId, 32553, 0);
|
||||
if (returnCode == 0 && owner.CanUse(this.target, spell, errorResult))
|
||||
{
|
||||
OnStart();
|
||||
}
|
||||
else
|
||||
{
|
||||
interrupt = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
var returnCode = spell.CallLuaFunction(owner, "onMagicStart", owner, target, spell);
|
||||
|
||||
if (returnCode != 0)
|
||||
{
|
||||
interrupt = true;
|
||||
errorResult = new CommandResult(target.actorId, (ushort)(returnCode == -1 ? 32553 : returnCode), 0, 0, 0, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// todo: check within attack range
|
||||
float[] baseCastDuration = { 1.0f, 0.25f };
|
||||
|
||||
//There are no positional spells, so just check onCombo, need to check first because certain spells change aoe type/accuracy
|
||||
//If owner is a player and the spell being used is part of the current combo
|
||||
if (owner is Player && ((Player)owner).GetClass() == spell.job)
|
||||
{
|
||||
Player p = (Player)owner;
|
||||
if (spell.comboStep == 1 || ((p.playerWork.comboNextCommandId[0] == spell.id || p.playerWork.comboNextCommandId[1] == spell.id)))
|
||||
{
|
||||
spell.CallLuaFunction(owner, "onCombo", owner, target, spell);
|
||||
spell.isCombo = true;
|
||||
}
|
||||
}
|
||||
|
||||
//Check combo stuff here because combos can impact spell cast times
|
||||
|
||||
float spellSpeed = spell.castTimeMs;
|
||||
|
||||
if (!spell.IsInstantCast())
|
||||
{
|
||||
// command casting duration
|
||||
if (owner is Player)
|
||||
{
|
||||
// todo: modify spellSpeed based on modifiers and stuff
|
||||
((Player)owner).SendStartCastbar(spell.id, Utils.UnixTimeStampUTC(DateTime.Now.AddMilliseconds(spellSpeed)));
|
||||
}
|
||||
owner.GetSubState().chantId = 0xf0;
|
||||
owner.SubstateModified();
|
||||
owner.DoBattleAction(spell.id, (uint) 0x6F000000 | spell.castType, new CommandResult(target.actorId, 30128, 1, 0, 1)); //You begin casting (6F000002: BLM, 6F000003: WHM, 0x6F000008: BRD)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Update(DateTime tick)
|
||||
{
|
||||
if (spell != null)
|
||||
{
|
||||
TryInterrupt();
|
||||
|
||||
if (interrupt)
|
||||
{
|
||||
OnInterrupt();
|
||||
return true;
|
||||
}
|
||||
|
||||
// todo: check weapon delay/haste etc and use that
|
||||
var actualCastTime = spell.castTimeMs;
|
||||
|
||||
if ((tick - startTime).TotalMilliseconds >= spell.castTimeMs)
|
||||
{
|
||||
OnComplete();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void OnInterrupt()
|
||||
{
|
||||
// todo: send paralyzed/sleep message etc.
|
||||
if (errorResult != null)
|
||||
{
|
||||
owner.GetSubState().chantId = 0x0;
|
||||
owner.SubstateModified();
|
||||
owner.DoBattleAction(spell.id, errorResult.animation, errorResult);
|
||||
errorResult = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnComplete()
|
||||
{
|
||||
//How do combos/hitdirs work for aoe abilities or does that not matter for aoe?
|
||||
HitDirection hitDir = owner.GetHitDirection(target);
|
||||
bool hitTarget = false;
|
||||
|
||||
spell.targetFind.FindWithinArea(target, spell.validTarget, spell.aoeTarget);
|
||||
isCompleted = true;
|
||||
var targets = spell.targetFind.GetTargets();
|
||||
|
||||
owner.DoBattleCommand(spell, "magic");
|
||||
}
|
||||
|
||||
public override void TryInterrupt()
|
||||
{
|
||||
if (interrupt)
|
||||
return;
|
||||
|
||||
if (owner.statusEffects.HasStatusEffectsByFlag((uint)StatusEffectFlags.PreventSpell))
|
||||
{
|
||||
// todo: sometimes paralyze can let you attack, get random percentage of actually letting you attack
|
||||
var list = owner.statusEffects.GetStatusEffectsByFlag((uint)StatusEffectFlags.PreventSpell);
|
||||
uint effectId = 0;
|
||||
if (list.Count > 0)
|
||||
{
|
||||
// todo: actually check proc rate/random chance of whatever effect
|
||||
effectId = list[0].GetStatusEffectId();
|
||||
}
|
||||
interrupt = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasMoved())
|
||||
{
|
||||
errorResult = new CommandResult(owner.actorId, 30211, 0);
|
||||
errorResult.animation = 0x7F000002;
|
||||
interrupt = true;
|
||||
return;
|
||||
}
|
||||
|
||||
interrupt = !CanCast();
|
||||
}
|
||||
|
||||
private bool CanCast()
|
||||
{
|
||||
return owner.CanUse(target, spell);
|
||||
}
|
||||
|
||||
private bool HasMoved()
|
||||
{
|
||||
return (owner.GetPosAsVector3() != startPos);
|
||||
}
|
||||
|
||||
public override void Cleanup()
|
||||
{
|
||||
owner.GetSubState().chantId = 0x0;
|
||||
owner.SubstateModified();
|
||||
|
||||
if (owner is Player)
|
||||
{
|
||||
((Player)owner).SendEndCastbar();
|
||||
}
|
||||
owner.aiContainer.UpdateLastActionTime(spell.animationDurationSeconds);
|
||||
}
|
||||
|
||||
public BattleCommand GetSpell()
|
||||
{
|
||||
return spell;
|
||||
}
|
||||
}
|
||||
}
|
85
Map Server/Actors/Chara/Ai/State/State.cs
Normal file
85
Map Server/Actors/Chara/Ai/State/State.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 2015-2019 Project Meteor Dev Team
|
||||
|
||||
This file is part of Project Meteor Server.
|
||||
|
||||
Project Meteor Server is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Project Meteor Server is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Meteor.Map.Actors;
|
||||
using Meteor.Map.packets.send.actor.battle;
|
||||
|
||||
namespace Meteor.Map.actors.chara.ai.state
|
||||
{
|
||||
class State
|
||||
{
|
||||
protected Character owner;
|
||||
protected Character target;
|
||||
|
||||
protected bool canInterrupt;
|
||||
protected bool interrupt = false;
|
||||
|
||||
protected DateTime startTime;
|
||||
|
||||
protected CommandResult errorResult;
|
||||
|
||||
protected bool isCompleted;
|
||||
|
||||
public State(Character owner, Character target)
|
||||
{
|
||||
this.owner = owner;
|
||||
this.target = target;
|
||||
this.canInterrupt = true;
|
||||
this.interrupt = false;
|
||||
}
|
||||
|
||||
public virtual bool Update(DateTime tick) { return true; }
|
||||
public virtual void OnStart() { }
|
||||
public virtual void OnInterrupt() { }
|
||||
public virtual void OnComplete() { isCompleted = true; }
|
||||
public virtual bool CanChangeState() { return false; }
|
||||
public virtual void TryInterrupt() { }
|
||||
|
||||
public virtual void Cleanup() { }
|
||||
|
||||
public bool CanInterrupt()
|
||||
{
|
||||
return canInterrupt;
|
||||
}
|
||||
|
||||
public void SetInterrupted(bool interrupt)
|
||||
{
|
||||
this.interrupt = interrupt;
|
||||
}
|
||||
|
||||
public bool IsCompleted()
|
||||
{
|
||||
return isCompleted;
|
||||
}
|
||||
|
||||
public void ChangeTarget(Character target)
|
||||
{
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public Character GetTarget()
|
||||
{
|
||||
return target;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
199
Map Server/Actors/Chara/Ai/State/WeaponSkillState.cs
Normal file
199
Map Server/Actors/Chara/Ai/State/WeaponSkillState.cs
Normal file
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 2015-2019 Project Meteor Dev Team
|
||||
|
||||
This file is part of Project Meteor Server.
|
||||
|
||||
Project Meteor Server is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Project Meteor Server is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Meteor.Common;
|
||||
using Meteor.Map.Actors;
|
||||
using Meteor.Map.packets.send.actor.battle;
|
||||
|
||||
namespace Meteor.Map.actors.chara.ai.state
|
||||
{
|
||||
class WeaponSkillState : State
|
||||
{
|
||||
|
||||
private BattleCommand skill;
|
||||
private HitDirection hitDirection;
|
||||
public WeaponSkillState(Character owner, Character target, ushort skillId) :
|
||||
base(owner, target)
|
||||
{
|
||||
this.startTime = DateTime.Now;
|
||||
this.skill = Server.GetWorldManager().GetBattleCommand(skillId);
|
||||
|
||||
var returnCode = skill.CallLuaFunction(owner, "onSkillPrepare", owner, target, skill);
|
||||
|
||||
this.target = (skill.mainTarget & ValidTarget.SelfOnly) != 0 ? owner : target;
|
||||
|
||||
errorResult = new CommandResult(owner.actorId, 32553, 0);
|
||||
if (returnCode == 0 && owner.CanUse(this.target, skill, errorResult))
|
||||
{
|
||||
OnStart();
|
||||
}
|
||||
else
|
||||
{
|
||||
errorResult = null;
|
||||
interrupt = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
var returnCode = skill.CallLuaFunction(owner, "onSkillStart", owner, target, skill);
|
||||
|
||||
if (returnCode != 0)
|
||||
{
|
||||
interrupt = true;
|
||||
errorResult = new CommandResult(owner.actorId, (ushort)(returnCode == -1 ? 32558 : returnCode), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
hitDirection = owner.GetHitDirection(target);
|
||||
|
||||
//Do positionals and combo effects first because these can influence accuracy and amount of targets/numhits, which influence the rest of the steps
|
||||
//If there is no positon required or if the position bonus should be activated
|
||||
if ((skill.positionBonus & utils.BattleUtils.ConvertHitDirToPosition(hitDirection)) == skill.positionBonus)
|
||||
{
|
||||
//If there is a position bonus
|
||||
if (skill.positionBonus != BattleCommandPositionBonus.None)
|
||||
skill.CallLuaFunction(owner, "weaponskill", "onPositional", owner, target, skill);
|
||||
|
||||
//Combo stuff
|
||||
if (owner is Player)
|
||||
{
|
||||
Player p = (Player)owner;
|
||||
//If skill is part of owner's class/job, it can be used in a combo
|
||||
if (skill.job == p.GetClass() || skill.job == p.GetCurrentClassOrJob())
|
||||
{
|
||||
//If owner is a player and the skill being used is part of the current combo
|
||||
if (p.playerWork.comboNextCommandId[0] == skill.id || p.playerWork.comboNextCommandId[1] == skill.id)
|
||||
{
|
||||
skill.CallLuaFunction(owner, "onCombo", owner, target, skill);
|
||||
skill.isCombo = true;
|
||||
}
|
||||
//or if this just the start of a combo
|
||||
else if (skill.comboStep == 1)
|
||||
skill.isCombo = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!skill.IsInstantCast())
|
||||
{
|
||||
float castTime = skill.castTimeMs;
|
||||
|
||||
// command casting duration
|
||||
if (owner is Player)
|
||||
{
|
||||
// todo: modify spellSpeed based on modifiers and stuff
|
||||
((Player)owner).SendStartCastbar(skill.id, Utils.UnixTimeStampUTC(DateTime.Now.AddMilliseconds(castTime)));
|
||||
}
|
||||
owner.GetSubState().chantId = 0xf0;
|
||||
owner.SubstateModified();
|
||||
//You ready [skill] (6F000002: BLM, 6F000003: WHM, 0x6F000008: BRD)
|
||||
owner.DoBattleAction(skill.id, (uint)0x6F000000 | skill.castType, new CommandResult(target.actorId, 30126, 1, 0, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Update(DateTime tick)
|
||||
{
|
||||
if (skill != null)
|
||||
{
|
||||
TryInterrupt();
|
||||
|
||||
if (interrupt)
|
||||
{
|
||||
OnInterrupt();
|
||||
return true;
|
||||
}
|
||||
|
||||
// todo: check weapon delay/haste etc and use that
|
||||
var actualCastTime = skill.castTimeMs;
|
||||
|
||||
if ((tick - startTime).TotalMilliseconds >= skill.castTimeMs)
|
||||
{
|
||||
OnComplete();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void OnInterrupt()
|
||||
{
|
||||
// todo: send paralyzed/sleep message etc.
|
||||
if (errorResult != null)
|
||||
{
|
||||
owner.DoBattleAction(skill.id, errorResult.animation, errorResult);
|
||||
errorResult = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnComplete()
|
||||
{
|
||||
owner.LookAt(target);
|
||||
skill.targetFind.FindWithinArea(target, skill.validTarget, skill.aoeTarget);
|
||||
isCompleted = true;
|
||||
|
||||
owner.DoBattleCommand(skill, "weaponskill");
|
||||
owner.statusEffects.RemoveStatusEffectsByFlags((uint) StatusEffectFlags.LoseOnAttacking);
|
||||
|
||||
lua.LuaEngine.GetInstance().OnSignal("weaponskillUsed");
|
||||
}
|
||||
|
||||
public override void TryInterrupt()
|
||||
{
|
||||
if (interrupt)
|
||||
return;
|
||||
|
||||
if (owner.statusEffects.HasStatusEffectsByFlag((uint)StatusEffectFlags.PreventWeaponSkill))
|
||||
{
|
||||
// todo: sometimes paralyze can let you attack, get random percentage of actually letting you attack
|
||||
var list = owner.statusEffects.GetStatusEffectsByFlag((uint)StatusEffectFlags.PreventWeaponSkill);
|
||||
uint effectId = 0;
|
||||
if (list.Count > 0)
|
||||
{
|
||||
// todo: actually check proc rate/random chance of whatever effect
|
||||
effectId = list[0].GetStatusEffectId();
|
||||
}
|
||||
interrupt = true;
|
||||
return;
|
||||
}
|
||||
|
||||
interrupt = !CanUse();
|
||||
}
|
||||
|
||||
private bool CanUse()
|
||||
{
|
||||
return owner.CanUse(target, skill);
|
||||
}
|
||||
|
||||
public BattleCommand GetWeaponSkill()
|
||||
{
|
||||
return skill;
|
||||
}
|
||||
|
||||
public override void Cleanup()
|
||||
{
|
||||
owner.aiContainer.UpdateLastActionTime(skill.animationDurationSeconds);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user