OK I’m not sure if what I’m doing is correct or wrong, but here’s my original code (so I have something to fall back on if things go wrong):
// For cases when the Player's Mounting level is higher than the minimum required
// mounting level, this event will determine, through probability, if the player is
// worth mounting the animal or not. The higher the player's level, the less likely
// the animal is to start a fight with the player
public static event Action OnSkillBasedMountAttempt;
public static void InvokeOnSkillBasedMountAttempt()
{
OnSkillBasedMountAttempt?.Invoke();
}
// Boolean to determine the result of 'OnSkillBasedMountFail', in
// 'AnimalStateMachine.DetermineChanceOfMountSuccess()', and
// 'AnimalDwellState.DetermineChanceOfMountSuccess()'. If it's true,
// the player can drive the animal. If false, avoid the player from driving the animal
// by invoking combat against the player from the animal
// (The checking for true or false will be done in 'MRider.cs')
public static bool isMountSuccessful;
and here’s an example of a function that uses that:
// Enter():
// Attack the player if he failed to mount the animal
// (Due to level not being high enough to mount the animal to begin with)
EquestrianismEvents.OnLowLevelMount += SwitchToChasingState;
// Attack the player if he failed to mount the animal
// (Due to risk probability being high, because the level is not much higher than
// the level required to start mounting the animal)
EquestrianismEvents.OnSkillBasedMountAttempt += DetermineChanceOfMountSuccess;
// Exit():
// Attack the player if he failed to mount the animal
// (Due to level not being high enough to mount the animal to begin with)
EquestrianismEvents.OnLowLevelMount -= SwitchToChasingState;
// Attack the player if he failed to mount the animal
// (Due to risk probability being high, because the level is not much higher than
// the level required to start mounting the animal)
EquestrianismEvents.OnSkillBasedMountAttempt -= DetermineChanceOfMountSuccess;
public void DetermineChanceOfMountSuccess()
{
stateMachine.StartCoroutine(DelayedDetermineChanceOfMountSuccess());
}
private IEnumerator DelayedDetermineChanceOfMountSuccess()
{
yield return null; // Spare a frame for 'GetLastFailedMountAnimal()' to be available
if (stateMachine.PlayerStateMachine.GetLastFailedMountAnimal().GetComponent<Animal>().GetUniqueID() == stateMachine.GetComponent<Animal>().GetUniqueID())
{
EquestrianismEvents.isMountSuccessful = false; // This is a static variable, make sure to always reverse it after each attempt
// If the animal was not mounted before, there's a chance of resistance
var playerEquestrianismLevel = stateMachine.PlayerStateMachine.SkillStore.GetSkillLevel(Skill.Equestrianism);
// Values between 0 and 1
// Chance of successful mounting
float successfulMountChance = Mathf.Clamp01(((float)playerEquestrianismLevel - (float)stateMachine.MinimumEquestrianismLevelToMount) / ((float)stateMachine.MaximumMountResistanceEquestrianismLevel - (float)stateMachine.MinimumEquestrianismLevelToMount));
// Chance of random mounting
float randomMountChance = Random.value;
if (randomMountChance <= successfulMountChance)
{
MalbersAnimations.InventorySystem.NotificationManager.Instance.OpenNotification($"Mounting Success:\nRandomChance: {randomMountChance}\nSuccess Chance: {successfulMountChance}");
EquestrianismEvents.isMountSuccessful = true;
}
else
{
MalbersAnimations.InventorySystem.NotificationManager.Instance.OpenNotification($"Mounting Failed:\nRandom Chance: {randomMountChance}\nSuccess Chance: {successfulMountChance}");
EquestrianismEvents.isMountSuccessful = false;
}
// IsMountSuccessful is used in 'MRider.cs' to determine if the player can mount the animal,
// or will we invoke an attacking state by the animal again
}
}
and here’s an example of a brand new attempt, in ‘EquestrianismEvents.cs’:
public static event Action<Action<bool>> OnSkillBasedMountAttempt;
public static void InvokeOnSkillBasedMountAttempt(Action<bool> callback)
{
OnSkillBasedMountAttempt?.Invoke(callback);
}
and here’s something that corresponds to it:
public void DetermineChanceOfMountSuccess(Action<bool> callback)
{
stateMachine.StartCoroutine(DelayedDetermineChanceOfMountSuccess(callback));
}
private IEnumerator DelayedDetermineChanceOfMountSuccess(Action<bool> callback)
{
Debug.Log($"DelayedDetermineChanceOfMountSuccess called");
yield return null; // Spare a frame for 'GetLastFailedMountAnimal()' to be available
if (stateMachine.PlayerStateMachine.GetLastFailedMountAnimal().GetComponent<Animal>().GetUniqueID() == stateMachine.GetComponent<Animal>().GetUniqueID())
{
// If the animal was not mounted before, there's a chance of resistance
var playerEquestrianismLevel = stateMachine.PlayerStateMachine.SkillStore.GetSkillLevel(Skill.Equestrianism);
// Values between 0 and 1
// Chance of successful mounting
float successfulMountChance = Mathf.Clamp01(
((float)playerEquestrianismLevel - (float)stateMachine.MinimumEquestrianismLevelToMount) /
((float)stateMachine.MaximumMountResistanceEquestrianismLevel - (float)stateMachine.MinimumEquestrianismLevelToMount)
);
// Chance of random mounting
float randomMountChance = UnityEngine.Random.value;
Debug.Log($"player equestrianism level: {playerEquestrianismLevel}, minimum level: {stateMachine.MinimumEquestrianismLevelToMount}, max mount level: {stateMachine.MaximumMountResistanceEquestrianismLevel}, minimum level: {stateMachine.MinimumEquestrianismLevelToMount}, successful mount chance: {(float)((playerEquestrianismLevel - stateMachine.MinimumEquestrianismLevelToMount) / (stateMachine.MaximumMountResistanceEquestrianismLevel - stateMachine.MinimumEquestrianismLevelToMount))}, random mount chance: {randomMountChance}");
if (randomMountChance <= successfulMountChance)
{
MalbersAnimations.InventorySystem.NotificationManager.Instance.OpenNotification($"Mounting Success:\nRandomChance: {randomMountChance}\nSuccess Chance: {successfulMountChance}");
callback?.Invoke(true);
}
else
{
MalbersAnimations.InventorySystem.NotificationManager.Instance.OpenNotification($"Mounting Failed:\nRandom Chance: {randomMountChance}\nSuccess Chance: {successfulMountChance}");
callback?.Invoke(false);
}
}
}
Please let me know if I’m on the right or wrong track
and in ‘MRider.cs’:
// NEW CONTEXT:
// TEST CODE ADDED BY BAHAA - IF YOU MADE IT THIS FAR, IT MEANS YOUR
// EQUESTRIANISM LEVEL IS HIGH ENOUGH TO WARRANT AN ATTEMPT TO
// MOUNT AN ANIMAL. INVOKE AN EVENT HERE TO DETERMINE
// THE PROBABILITY OF FAILURE. IF IT'S HIGH, GET THE ANIMAL
// TO ATTACK THE PLAYER AGAIN. IF NOT, THEN ALLOW THE MOUNTING
EquestrianismEvents.InvokeOnSkillBasedMountAttempt(isMountSuccessful => {
if (isMountSuccessful)
{
// CODE ADDED BY BAHAA - SOLUTION TO ENSURE ANIMALS AND
// CHARACTERS (NPCs, PLAYER, etc) DON'T COLLIDE WHEN THE ANIMAL
// IS DRIVEN, BUT CAN COLLIDE WHEN THE ANIMAL IS NOT DRIVEN, SO
// COMBAT DOESN'T HAVE ANY ISSUES, AND A MORE REALISTIC INTERACTION
int animalLayer = LayerMask.NameToLayer("Animal");
int charactersLayer = LayerMask.NameToLayer("Characters");
Physics.IgnoreLayerCollision(animalLayer, charactersLayer, true);
Debug.Log($"[MountAnimal] Ignoring collisions between 'Animal' layer (Layer {animalLayer}) and 'Character' Layer (Layer {charactersLayer})");
if (!Montura.InstantMount) //If is instant Mount play it
{
Debbuging("Mount Animal", "cyan");
Mounted = true; //Update MountSide Parameter In the Animator
SetMountSide(MountTrigger.MountID); //Update MountSide Parameter In the Animator
// Anim?.Play(MountTrigger.MountAnimation, MountLayerIndex); //Play the Mounting Animations
}
else
{
Debbuging("Instant Mount", "cyan");
Anim?.Play(Montura.MountIdle, MountLayerIndex); //Ingore the Mounting Animations
Anim?.Update(0); //Update the Animator ????
Start_Mounting();
End_Mounting();
// ADDED BY BAHAA (next 3 lines, that is):
AnimalMountManager.isPlayerOnAnimalMount = true;
AnimalMountManager.InvokeOnAnimalMounted();
Debug.Log($"isPlayerOnAnimalMount: {AnimalMountManager.isPlayerOnAnimalMount}");
Montura.Rider.transform.SetParent(Montura.transform);
}
}
else
{
// TEST CODE BY BAHAA - IF MOUNTING WAS NOT SUCCESSFUL,
// YOU INVOKE THE SAME 'OnFailedMount' THAT YOU CALL
// WHEN THE LEVEL IS NOT HIGH ENOUGH AGAIN (Starting an
// Animal Attacking System against the player)
EquestrianismEvents.InvokeOnLowLevelMount();
}
});
}
// OLD CONTEXT:
// TEST CODE ADDED BY BAHAA - IF YOU MADE IT THIS FAR, IT MEANS YOUR
// EQUESTRIANISM LEVEL IS HIGH ENOUGH TO WARRANT AN ATTEMPT TO
// MOUNT AN ANIMAL. INVOKE AN EVENT HERE TO DETERMINE
// THE PROBABILITY OF FAILURE. IF IT'S HIGH, GET THE ANIMAL
// TO ATTACK THE PLAYER AGAIN. IF NOT, THEN ALLOW THE MOUNTING
EquestrianismEvents.InvokeOnSkillBasedMountAttempt();
// TEST CODE ADDED BY BAHAA - When the 'OnSkillBasedMountFail' is called,
// (LINE ABOVE), some probabilistic calculations will happen in
// both patrol and/or dwell states to determine if mounting was
// successful or not, otherwise we will invoke on failed mount (just like above)
// again:
if (EquestrianismEvents.isMountSuccessful) // TEST - ONLY THE IF STATEMENT LINE IS ADDED BY BAHAA
{
// CODE ADDED BY BAHAA - SOLUTION TO ENSURE ANIMALS AND
// CHARACTERS (NPCs, PLAYER, etc) DON'T COLLIDE WHEN THE ANIMAL
// IS DRIVEN, BUT CAN COLLIDE WHEN THE ANIMAL IS NOT DRIVEN, SO
// COMBAT DOESN'T HAVE ANY ISSUES, AND A MORE REALISTIC INTERACTION
int animalLayer = LayerMask.NameToLayer("Animal");
int charactersLayer = LayerMask.NameToLayer("Characters");
Physics.IgnoreLayerCollision(animalLayer, charactersLayer, true);
Debug.Log($"[MountAnimal] Ignoring collisions between 'Animal' layer (Layer {animalLayer}) and 'Character' Layer (Layer {charactersLayer})");
if (!Montura.InstantMount) //If is instant Mount play it
{
Debbuging("Mount Animal", "cyan");
Mounted = true; //Update MountSide Parameter In the Animator
SetMountSide(MountTrigger.MountID); //Update MountSide Parameter In the Animator
// Anim?.Play(MountTrigger.MountAnimation, MountLayerIndex); //Play the Mounting Animations
}
else
{
Debbuging("Instant Mount", "cyan");
Anim?.Play(Montura.MountIdle, MountLayerIndex); //Ingore the Mounting Animations
Anim?.Update(0); //Update the Animator ????
Start_Mounting();
End_Mounting();
// ADDED BY BAHAA (next 3 lines, that is):
AnimalMountManager.isPlayerOnAnimalMount = true;
AnimalMountManager.InvokeOnAnimalMounted();
Debug.Log($"isPlayerOnAnimalMount: {AnimalMountManager.isPlayerOnAnimalMount}");
Montura.Rider.transform.SetParent(Montura.transform);
}
}
else
{
// TEST CODE BY BAHAA - IF MOUNTING WAS NOT SUCCESSFUL,
// YOU INVOKE THE SAME 'OnFailedMount' THAT YOU CALL
// WHEN THE LEVEL IS NOT HIGH ENOUGH AGAIN (Starting an
// Animal Attacking System against the player)
EquestrianismEvents.InvokeOnLowLevelMount();
}
Problem is, based on notifications, everything gets called twice now…
And if I were to take a wild guess, it’s because there’s two animals in the scene, and as a result, the animals can invoke a false state with one of them, leading to unintended behaviour. @Brian_Trotter any ideas why that would be the case?
Edit: I added this line check again:
if (stateMachine.PlayerStateMachine.SkillStore.GetNearestAnimalStateMachine().GetComponent<Animal>().GetUniqueID() == stateMachine.GetComponent<Animal>().GetUniqueID())
and returned to my coroutine approach, ending up with something like this:
public void DetermineChanceOfMountSuccess(Action<bool> callback)
{
stateMachine.StartCoroutine(DelayedDetermineChanceOfMountSuccess(callback));
}
private IEnumerator DelayedDetermineChanceOfMountSuccess(Action<bool> callback)
{
yield return null; // Spare a frame for 'SkillStore.GetNearestAnimalStateMachine()' to get ready
if (stateMachine.PlayerStateMachine.SkillStore.GetNearestAnimalStateMachine().GetComponent<Animal>().GetUniqueID() == stateMachine.GetComponent<Animal>().GetUniqueID())
{
Debug.Log($"DetermineChanceOfMountSuccess called from {stateMachine.gameObject.name} Attacking State");
// If the animal was not mounted before, there's a chance of resistance
var playerEquestrianismLevel = stateMachine.PlayerStateMachine.SkillStore.GetSkillLevel(Skill.Equestrianism);
// Values between 0 and 1
// Chance of successful mounting
float successfulMountChance = Mathf.Clamp01
(
((float)playerEquestrianismLevel - (float)stateMachine.MinimumEquestrianismLevelToMount) /
((float)stateMachine.MaximumMountResistanceEquestrianismLevel - (float)stateMachine.MinimumEquestrianismLevelToMount)
);
// Chance of random mounting
float randomMountChance = UnityEngine.Random.value;
Debug.Log($"player equestrianism level: {playerEquestrianismLevel}, minimum level: {stateMachine.MinimumEquestrianismLevelToMount}, max mount level: {stateMachine.MaximumMountResistanceEquestrianismLevel}, minimum level: {stateMachine.MinimumEquestrianismLevelToMount}, successful mount chance: {(float)((playerEquestrianismLevel - stateMachine.MinimumEquestrianismLevelToMount) / (stateMachine.MaximumMountResistanceEquestrianismLevel - stateMachine.MinimumEquestrianismLevelToMount))}, random mount chance: {randomMountChance}");
if (randomMountChance <= successfulMountChance)
{
MalbersAnimations.InventorySystem.NotificationManager.Instance.OpenNotification($"Mounting Success:\nRandomChance: {randomMountChance}\nSuccess Chance: {successfulMountChance}");
callback?.Invoke(true);
}
else
{
MalbersAnimations.InventorySystem.NotificationManager.Instance.OpenNotification($"Mounting Failed:\nRandom Chance: {randomMountChance}\nSuccess Chance: {successfulMountChance}");
callback?.Invoke(false);
}
}
}
This is not only easier to read, but @Brian_Trotter 's suggestion, although it didn’t solve the “who is calling this script”, it absolutely killed the lag issue I had, where I’d be capable of driving the animal based on the results of my previous attempt, rather than the new one. Thank you Brian!
I feel much better now (tomorrow I’ll work on an animal shop. I decided to make the wild animals wild, with their own characteristics, and then you got animals you can buy, which I’ll work on, and these ones will be your friends through it all) - I still want to fully understand what the new approach of ‘callback?.Invoke(true)’ did to help out, though (because, no offence, it didn’t solve my “two scripts being called at the same time” the way the following line did):
if (stateMachine.PlayerStateMachine.SkillStore.GetNearestAnimalStateMachine().GetComponent<Animal>().GetUniqueID() == stateMachine.GetComponent<Animal>().GetUniqueID())
In simple terms, the line gets the animal you just tried to mount after the mounting frame, and compares it to the animal you’re trying to mount