Skip to content
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

Open
wants to merge 25 commits into
base: master
Choose a base branch
from

Conversation

felipecastagnarodecarvalho
Copy link
Contributor

All Sadhu Skills:

  • Enira
  • Moksha
  • Patati
  • Possession
  • Prakriti
  • SpiritExpert
  • Tanoti

Also there was an implementation of the art Spirit Expert: Wandering Soul which creates a dummy that acts by himself.

Copy link
Member

@exectails exectails left a 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>();
Copy link
Member

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.

Copy link
Contributor Author

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);
			}
		}

Copy link
Member

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/Network/Send.cs Outdated Show resolved Hide resolved
src/ZoneServer/Network/Send.cs Show resolved Hide resolved
this.MoveSpeedType = type;
character.Properties.Invalidate(PropertyName.MSPD);

Send.ZC_MSPD(character.Owner, character);
Copy link
Member

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.

Copy link
Contributor Author

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.

system/scripts/zone/ais/sadhu_dummy.cs Show resolved Hide resolved
@@ -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
Copy link
Member

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);
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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)
Copy link
Member

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)
Copy link
Member

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())
Copy link
Member

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)
Copy link
Member

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());
Copy link
Member

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);
Copy link
Member

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)
Copy link
Member

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);
Copy link
Member

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
Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants