-
Notifications
You must be signed in to change notification settings - Fork 93
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sadhu skills #302
base: master
Are you sure you want to change the base?
Sadhu skills #302
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR. I took a quick glance and I noticed that some of it still seems a bit prototypical, and I also have some questions and concerns before we continue. Please refer to my comments.
/// <para>Key: <see cref="Character.Handle"/></para> | ||
/// <para>Value: <see cref="Character"/></para> | ||
/// </summary> | ||
private readonly Dictionary<int, Character> _dummies = new Dictionary<int, Character>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you tell me more about your reasoning for creating a whole new category for dummy characters? Seeing how a dummy character is presumably the same as a normal character, with the only exception being that it's not controlled by a client, it seems like this might add a lot of unnecessary future maintenance work, because we will always have to remember to handle the dummies in some cases but not others.
To make matters worse, the game has a separate "Dummy PC" concept (ZC_ENTER_DUMMYPC
). I don't know if that one is still in use, but if it is, and if they will require a category of their own, that will only complicate things even more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some of Melia methods takes in consideration that a character on the _characters dictionary have a connection and dummies don't have it. That should be the main difference. Well, I am out of ideas on how to improve it. I don't think it's should be overcomplicated: is it a dummy? Instead of adding to the _characters dictionary add it to the _dummies.
Broadcasts is an example that we should be threating characters differently from dummies:
/// <summary>
/// Broadcasts packet to all characters on map.
/// </summary>
/// <param name="packet"></param>
public virtual void Broadcast(Packet packet)
{
lock (_characters)
{
foreach (var character in _characters.Values)
character.Connection.Send(packet);
}
}
/// <summary>
/// Broadcasts packet to all characters on map, that are within
/// visible range of source.
/// </summary>
/// <param name="packet">Packet to broadcast.</param>
/// <param name="source">Reference actor for visible range to broadcast in.</param>
/// <param name="includeSource">Send to source as well?</param>
public virtual void Broadcast(Packet packet, IActor source, bool includeSource = true)
{
lock (_characters)
{
foreach (var character in _characters.Values.Where(a => (includeSource || a != source) && a.Position.InRange2D(source.Position, VisibleRange)))
character.Connection.Send(packet);
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, you're saying,
it's should be overcomplicated: is it a dummy? Instead of adding to the _characters dictionary add it to the _dummies.
but while duplicating dozens of lines of code is technically simple, it makes future maintenance and understanding the code much more complicated. And in a project that many more people will work on over many more years, that is far from ideal.
Instead, I would argue that an overall less complicated way is to view dummies as that which they are: characters without a connection. One way to handle that could be to check the character type before sending packets. But another, even more opaque way would be to give dummy characters a dummy connection. Simply a connection type that doesn't do anything when Send
is called. That way it doesn't matter if you ever do send a packet for any reason, as it will effectively be ignored.
I would implement this, but looking at the current buff handlers, it's hard to tell how this would and should affect the existing code, so you would have to do it.
src/ZoneServer/World/Actors/CombatEntities/Components/MovementComponent.cs
Outdated
Show resolved
Hide resolved
this.MoveSpeedType = type; | ||
character.Properties.Invalidate(PropertyName.MSPD); | ||
|
||
Send.ZC_MSPD(character.Owner, character); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you tell me what the exact purpose of this else if
is? It seems like you could just pass the character into ZC_MSPD
, which would allow you to leave it as one if.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is needed because DummyCharacters don't have connection so I need to pass the Owner to the other method.
@@ -197,6 +197,17 @@ public float SCR_CalculateDamage(ICombatEntity attacker, ICombatEntity target, S | |||
|
|||
SCR_Combat_AfterCalc(attacker, target, skill, modifier, skillHitResult); | |||
|
|||
// TODO: There may be a better place to check this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The better place is the buff handlers, from where you can hook into all this without having to add the code here^^ Notice the SCR_Combat_AfterCalc
call just above, which makes the server call the handlers of all active buffs to see if they'd like to change anything about this skill hit.
@@ -710,7 +710,6 @@ private static void GrantDefaults(Character character, JobId jobId) | |||
{ | |||
LearnSkill(character, SkillId.Hammer_Attack); | |||
LearnSkill(character, SkillId.Hammer_Attack_TH); | |||
LearnSkill(character, SkillId.Sadhu_OutofBodyCancel); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you tell me why you removed this? It's part of the official data, which there probably is a reason for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This skill is not being used anymore and has no purposes. Sadhu has been remake I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is based on official data though, and removing it creates a dissonance between server and client. Unless this causes issues, it should stay this way.
} | ||
} | ||
|
||
public override void OnEnd(Buff buff) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I gotta say, frankly, I'm a bit unhappy with these buff handlers. The code is pretty difficult to follow, especially with all the conditionals for dummy characters and lack of comments. Additionally, you're duplicating a lot of code even though you went to the trouble of adding a base handler class, which could handle much of it. Such as the entire OnActivate
method, which is verbatim the same for all of them, the same one line to get the skill character, and more.
I would like to see these handlers receive a bit more structure and documentation, because as is, I can't follow any of this without reading, studying, and analyzing the entire code and the skill descriptions. And while this might be acceptable for the time being, at some point someone else will want to or have to change something about this job, and if they have to spend the same amount of time as you did initially writing this code, that's far from ideal.
/// Spawns a dummy character that will looks like the original character | ||
/// </summary> | ||
/// <param name="casterCharacter"></param> | ||
private Character SpawnDummyClone(Character casterCharacter, Position position) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you considered moving the creation code to the dummy character class, to decouple it from the skill handlers? While they aren't going to be used by anything else for now, I suspect we'll have different use cases for them, such as vendors or clones spawned for fun or events,
{ | ||
var skillTreeData = ZoneServer.Instance.Data.SkillTreeDb.FindSkills(JobId.Sadhu, 45); | ||
|
||
foreach (var availableSkill in casterCharacter.Skills.GetList()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tip:
var sadhuSkills = casterCharacter.Skills.GetList(a => a.Id != skill.Id && skillTreeData.Any(a => a.SkillId == skill.Id));
foreach(var availableSkill in sadhuSkills)
Send.ZC_NORMAL.EnableUseSkillWhileOutOfBody(casterCharacter, buffId, (int)availableSkill.Id);
/// <param name="character"></param> | ||
/// <param name="buffId"></param> | ||
/// <param name="skillId"></param> | ||
public static void EnableUseSkillWhileOutOfBody(Character character, BuffId buffId, int skillId) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless there's a reason for making skillId
an integer, you should rather use SkillId
, for some type safety. Otherwise, any arbitrary integer, short or byte could easily be passed in.
packet.PutInt(NormalOp.Zone.EndOutOfBodyBuff); | ||
|
||
packet.PutInt(character.Handle); | ||
packet.PutLpString(buffId.ToString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While the buff ids might seem like they're just the buff class names, they aren't (necessarily). The names in the enum get modified slightly to prevent errors we would get if we used the class names as is in C#. For this sender that means it needs to get the actual class name from the buff data, because the buff id is not reliable. This might not actually be an issue with the Sadhu skills/buffs, but if their names should change, or other buffs use this packet as well, it would cause problems that would be difficult to diagnose.
/// <param name="target"></param> | ||
public void Handle(Skill skill, ICombatEntity caster, Position originPos, Position farPos, ICombatEntity target) | ||
{ | ||
Handle(skill, caster, originPos, farPos, target, BuffId.OOBE_Anila_Buff); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing this.
, coding conventions, yada yada^^
/// <param name="farPos"></param> | ||
/// <param name="target"></param> | ||
/// <param name="buffId"></param> | ||
public void Handle(Skill skill, ICombatEntity caster, Position originPos, Position farPos, ICombatEntity target, BuffId buffId) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's unfortunately not very clear what's going on here if you're not deep into this job already, made worse by all the duplicated code between Handle
and CreateClone
. Like with the buff handlers, I'd like to see a bit more structure that is easier to follow here.
if (this.Entity is Character entityCharacter && entityCharacter is DummyCharacter dummyCharacter) | ||
{ | ||
SetFixedMoveSpeed(55); | ||
entityCharacter.Properties.Modify(PropertyName.MSPD_BM, 55); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is redundant, as it's essentially what SetFixedMoveSpeed
does.
|
||
protected IEnumerable Attack() | ||
{ | ||
// Remove the dummy character if the master is gone |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should not send raw packets from AI scripts unless there's absolutely no other way to accomplish what you need to do, because scripts, by their nature, should be simple to understand for users who are less experienced.
A cleaner solution would be to call some type of despawn method, which then internally figures out how to remove the dummy or whatever other actor the AI might be controlling.
All Sadhu Skills:
Also there was an implementation of the art Spirit Expert: Wandering Soul which creates a dummy that acts by himself.