mirror of
https://bitbucket.org/Ioncannon/project-meteor-server.git
synced 2025-05-20 08:26:59 -04:00
Cleaned up namespaces (still have to do Map Project) and removed references to FFXIV Classic from the code. Removed the Launcher Editor project as it is no longer needed (host file editing is cleaner).
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 FFXIVClassic_Map_Server.Actors;
|
||||
using FFXIVClassic_Map_Server.packets.send.actor.battle;
|
||||
|
||||
namespace FFXIVClassic_Map_Server.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 FFXIVClassic_Map_Server.Actors;
|
||||
using FFXIVClassic_Map_Server.packets.send.actor;
|
||||
using FFXIVClassic_Map_Server.packets.send.actor.battle;
|
||||
namespace FFXIVClassic_Map_Server.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 FFXIVClassic_Map_Server.Actors;
|
||||
using FFXIVClassic_Map_Server.packets.send.actor;
|
||||
|
||||
namespace FFXIVClassic_Map_Server.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 FFXIVClassic_Map_Server.Actors;
|
||||
using FFXIVClassic_Map_Server.packets.send.actor;
|
||||
|
||||
namespace FFXIVClassic_Map_Server.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 FFXIVClassic_Map_Server.Actors;
|
||||
|
||||
namespace FFXIVClassic_Map_Server.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 FFXIVClassic_Map_Server.Actors;
|
||||
using FFXIVClassic_Map_Server.dataobjects;
|
||||
|
||||
namespace FFXIVClassic_Map_Server.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 FFXIVClassic_Map_Server.Actors;
|
||||
using FFXIVClassic_Map_Server.packets.send.actor.battle;
|
||||
|
||||
namespace FFXIVClassic_Map_Server.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 FFXIVClassic_Map_Server.Actors;
|
||||
using FFXIVClassic_Map_Server.packets.send.actor.battle;
|
||||
|
||||
namespace FFXIVClassic_Map_Server.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 FFXIVClassic_Map_Server.Actors;
|
||||
using FFXIVClassic_Map_Server.packets.send.actor.battle;
|
||||
|
||||
namespace FFXIVClassic_Map_Server.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