161 lines
6.1 KiB
C#
161 lines
6.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using NitroxClient.Communication.Abstract;
|
|
using NitroxClient.MonoBehaviours;
|
|
using NitroxModel.DataStructures;
|
|
using NitroxModel.Packets;
|
|
using UnityEngine;
|
|
using static NitroxModel.Packets.RangedAttackLastTargetUpdate;
|
|
|
|
namespace NitroxClient.GameLogic;
|
|
|
|
public class AI
|
|
{
|
|
private readonly IPacketSender packetSender;
|
|
private readonly Dictionary<Creature, CreatureAction> actions = [];
|
|
private readonly Dictionary<string, Type> cachedCreatureActionTypeByFullName;
|
|
|
|
/// <summary>
|
|
/// Contains the types derived from CreatureAction which should be synced.
|
|
/// Actions concerning the creature movement should be ignored as it's already done through SplineFollowing
|
|
/// </summary>
|
|
private readonly HashSet<Type> creatureActionWhitelist =
|
|
[
|
|
typeof(AttackLastTarget), typeof(RangedAttackLastTarget), typeof(AttackCyclops), typeof(Poop)
|
|
];
|
|
|
|
/// <summary>
|
|
/// In the future, ensure all creatures are synced. We want each of them to be individually
|
|
/// checked (that all their actions are synced) before marking them as synced.
|
|
/// </summary>
|
|
private readonly HashSet<Type> syncedCreatureWhitelist =
|
|
[
|
|
typeof(ReaperLeviathan), typeof(SeaDragon), typeof(SeaTreader), typeof(GhostLeviathan)
|
|
];
|
|
|
|
public AI(IPacketSender packetSender)
|
|
{
|
|
this.packetSender = packetSender;
|
|
Assembly assembly = Assembly.GetAssembly(typeof(CreatureAction));
|
|
cachedCreatureActionTypeByFullName = assembly.GetTypes()
|
|
.Where(type => typeof(CreatureAction).IsAssignableFrom(type))
|
|
.ToDictionary(t => t.FullName, t => t);
|
|
}
|
|
|
|
public void BroadcastNewAction(NitroxId creatureId, Creature creature, CreatureAction newAction)
|
|
{
|
|
if (!syncedCreatureWhitelist.Contains(creature.GetType()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
packetSender.Send(new CreatureActionChanged(creatureId, newAction.GetType().FullName));
|
|
}
|
|
|
|
public void CreatureActionChanged(NitroxId id, string creatureActionTypeName)
|
|
{
|
|
if (!NitroxEntity.TryGetComponentFrom(id, out Creature creature))
|
|
{
|
|
return;
|
|
}
|
|
if (cachedCreatureActionTypeByFullName.TryGetValue(creatureActionTypeName, out Type creatureActionType) &&
|
|
creature.TryGetComponent(creatureActionType, out Component component) && component is CreatureAction creatureAction)
|
|
{
|
|
actions[creature] = creatureAction;
|
|
}
|
|
}
|
|
|
|
public static void AggressiveWhenSeeTargetChanged(NitroxId creatureId, NitroxId targetId, bool locked, float aggressionAmount)
|
|
{
|
|
if (!NitroxEntity.TryGetComponentFrom(creatureId, out AggressiveWhenSeeTarget aggressiveWhenSeeTarget) ||
|
|
!NitroxEntity.TryGetObjectFrom(targetId, out GameObject targetObject))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Creature creature = aggressiveWhenSeeTarget.creature;
|
|
|
|
// Code from AggressiveWhenSeeTarget.ScanForAggressionTarget
|
|
creature.Aggression.Value = aggressionAmount;
|
|
LastTarget lastTarget = aggressiveWhenSeeTarget.lastTarget;
|
|
lastTarget.SetTargetInternal(targetObject);
|
|
lastTarget.targetLocked = locked;
|
|
|
|
if (aggressiveWhenSeeTarget.sightedSound && !aggressiveWhenSeeTarget.sightedSound.GetIsPlaying())
|
|
{
|
|
// This call doesn't broadcast a sound packet
|
|
aggressiveWhenSeeTarget.sightedSound.StartEvent();
|
|
}
|
|
|
|
if (creature.TryGetComponent(out AttackLastTarget attackLastTarget))
|
|
{
|
|
attackLastTarget.currentTarget = targetObject;
|
|
}
|
|
}
|
|
|
|
public static void AttackCyclopsTargetChanged(NitroxId creatureId, NitroxId targetId, float aggressiveToNoiseAmount)
|
|
{
|
|
if (!NitroxEntity.TryGetComponentFrom(creatureId, out AttackCyclops attackCyclops) ||
|
|
!NitroxEntity.TryGetObjectFrom(targetId, out GameObject targetObject))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Kinda stuff from AttackCyclops.UpdateAggression
|
|
attackCyclops.aggressiveToNoise.Value = aggressiveToNoiseAmount;
|
|
// Force currentTarget to null to ensure SetCurrentTarget detects a change
|
|
attackCyclops.currentTarget = null;
|
|
attackCyclops.SetCurrentTarget(targetObject, targetObject.GetComponent<CyclopsDecoy>());
|
|
}
|
|
|
|
public static void RangedAttackLastTargetUpdate(NitroxId creatureId, NitroxId targetId, int attackTypeIndex, ActionState state)
|
|
{
|
|
if (!NitroxEntity.TryGetComponentFrom(creatureId, out RangedAttackLastTarget rangedAttackLastTarget) ||
|
|
!NitroxEntity.TryGetObjectFrom(targetId, out GameObject targetObject))
|
|
{
|
|
return;
|
|
}
|
|
|
|
RangedAttackLastTarget.RangedAttackType attackType = rangedAttackLastTarget.attackTypes[attackTypeIndex];
|
|
rangedAttackLastTarget.currentAttack = attackType;
|
|
rangedAttackLastTarget.currentTarget = targetObject;
|
|
|
|
switch (state)
|
|
{
|
|
case ActionState.CHARGING:
|
|
rangedAttackLastTarget.StartCharging(attackType);
|
|
break;
|
|
case ActionState.CASTING:
|
|
rangedAttackLastTarget.StartCasting(attackType);
|
|
break;
|
|
}
|
|
}
|
|
|
|
public static void CreaturePoopPerformed(NitroxId creatureId)
|
|
{
|
|
if (NitroxEntity.TryGetComponentFrom(creatureId, out Poop poop))
|
|
{
|
|
// Code from Poop.Perform
|
|
SafeAnimator.SetBool(poop.creature.GetAnimator(), poop.animationParameterName, false);
|
|
poop.recourceSpawned = true;
|
|
}
|
|
}
|
|
|
|
public bool TryGetActionForCreature(Creature creature, out CreatureAction action)
|
|
{
|
|
return actions.TryGetValue(creature, out action);
|
|
}
|
|
|
|
public bool IsCreatureActionWhitelisted(CreatureAction creatureAction)
|
|
{
|
|
return creatureActionWhitelist.Contains(creatureAction.GetType());
|
|
}
|
|
|
|
public bool IsCreatureWhitelisted(Creature creature)
|
|
{
|
|
return syncedCreatureWhitelist.Contains(creature.GetType());
|
|
}
|
|
}
|