Files
2025-07-06 00:23:46 +02:00

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