using System; using System.Collections.Generic; using Unity.Entities; using Unity.Jobs; using UnityEngine; using UnityEngine.Profiling; [RequireComponent(typeof(ReplicatedAbility))] public class Ability_ProjectileLauncher : MonoBehaviour { public enum Phase { Idle, Active, Cooldown, } public struct LocalState : IComponentData { public int lastFireTick; } [Serializable] public struct Settings : IComponentData { public float activationDuration; public float cooldownDuration; public CharacterPredictedState.StateData.Action fireAction; public float projectileRange; [NonSerialized] public uint projectileRegistryId; } public struct PredictedState : IPredictedData, IComponentData { public Phase phase; public int phaseStartTick; public int fireRequestedTick; public void SetPhase(Phase phase, int tick) { this.phase = phase; this.phaseStartTick = tick; } public void Serialize(ref NetworkWriter writer, IEntityReferenceSerializer refSerializer) { writer.WriteInt32("phase", (int)phase); writer.WriteInt32("phaseStart", phaseStartTick); writer.WriteInt32("fireRequestedTick", fireRequestedTick); } public void Deserialize(ref NetworkReader reader, IEntityReferenceSerializer refSerializer, int tick) { phase = (Phase)reader.ReadInt32(); phaseStartTick = reader.ReadInt32(); fireRequestedTick = reader.ReadInt32(); } #if UNITY_EDITOR public bool VerifyPrediction(ref PredictedState state) { return phase == state.phase && phaseStartTick == state.phaseStartTick; } #endif } public struct InterpolatedState : IInterpolatedData, IComponentData { public int fireTick; public void Serialize(ref NetworkWriter writer, IEntityReferenceSerializer refSerializer) { writer.WriteInt32("fireTick", fireTick); } public void Deserialize(ref NetworkReader reader, IEntityReferenceSerializer refSerializer, int tick) { fireTick = reader.ReadInt32(); } public void Interpolate(ref InterpolatedState first, ref InterpolatedState last, float t) { this = first; } } public Settings settings; public ProjectileTypeDefinition projectileType; private void OnEnable() { var gameObjectEntity = GetComponent(); var entityManager = gameObjectEntity.EntityManager; var abilityEntity = gameObjectEntity.Entity; // Default components entityManager.AddComponentData(abilityEntity, new CharacterAbility()); entityManager.AddComponentData(abilityEntity, new AbilityControl()); // Ability components entityManager.AddComponentData(abilityEntity, new LocalState()); entityManager.AddComponentData(abilityEntity, new PredictedState()); entityManager.AddComponentData(abilityEntity, new InterpolatedState()); settings.projectileRegistryId = projectileType.registryId; entityManager.AddComponentData(abilityEntity, settings); // Setup replicated ability var replicatedAbility = entityManager.GetComponentObject(abilityEntity); replicatedAbility.predictedHandlers = new IPredictedDataHandler[2]; replicatedAbility.predictedHandlers[0] = new PredictedEntityHandler(entityManager, abilityEntity); replicatedAbility.predictedHandlers[1] = new PredictedEntityHandler(entityManager, abilityEntity); replicatedAbility.interpolatedHandlers = new IInterpolatedDataHandler[1]; replicatedAbility.interpolatedHandlers[0] = new InterpolatedEntityHandler(entityManager, abilityEntity); } } [DisableAutoCreation] class ProjectileLauncher_RequestActive : BaseComponentDataSystem { public ProjectileLauncher_RequestActive(GameWorld world) : base(world) { ExtraComponentRequirements = new ComponentType[] { typeof(ServerEntity) } ; } protected override void Update(Entity entity, CharacterAbility charAbility, AbilityControl abilityCtrl, Ability_ProjectileLauncher.PredictedState predictedState) { Profiler.BeginSample("ProjectileLauncher_RequestActive"); var command = EntityManager.GetComponentObject(charAbility.character).command; var character = EntityManager.GetComponentObject(charAbility.character); var isAlive = character.State.locoState != CharacterPredictedState.StateData.LocoState.Dead; var fireRequested = command.secondaryFire && isAlive; var isActive = predictedState.phase == Ability_ProjectileLauncher.Phase.Active; abilityCtrl.requestsActive = !character.State.abilityActive && (isActive || fireRequested) ? 1 : 0; EntityManager.SetComponentData(entity, abilityCtrl); EntityManager.SetComponentData(entity, predictedState); Profiler.EndSample(); } } [DisableAutoCreation] class ProjectileLauncher_Update : BaseComponentDataSystem { public ProjectileLauncher_Update(GameWorld world) : base(world) { ExtraComponentRequirements = new ComponentType[] { typeof(ServerEntity) } ; } protected override void Update(Entity entity, AbilityControl abilityCtrl, Ability_ProjectileLauncher.PredictedState predictedState, Ability_ProjectileLauncher.Settings settings) { Profiler.BeginSample("ProjectileLauncher_Update"); var time = m_world.worldTime; switch (predictedState.phase) { case Ability_ProjectileLauncher.Phase.Idle: if (abilityCtrl.activeAllowed == 1) { var charAbility = EntityManager.GetComponentData(entity); var character = EntityManager.GetComponentObject(charAbility.character); var charPredictedState = EntityManager.GetComponentObject(charAbility.character); predictedState.SetPhase(Ability_ProjectileLauncher.Phase.Active, time.tick); charPredictedState.State.SetAction(settings.fireAction, time.tick); // Only spawn once for each tick (so it does not fire again when re-predicting) var localState = EntityManager.GetComponentData(entity); if (time.tick > localState.lastFireTick) { localState.lastFireTick = time.tick; EntityManager.SetComponentData(entity, localState); var eyePos = charPredictedState.State.position + Vector3.up*character.eyeHeight; var interpolatedState = EntityManager.GetComponentData(entity); var command = EntityManager.GetComponentObject(charAbility.character) .command; var endPos = eyePos + command.lookDir * settings.projectileRange; ProjectileRequest.Create(PostUpdateCommands, time.tick, time.tick - command.renderTick, settings.projectileRegistryId, charAbility.character, charPredictedState.teamId, eyePos, endPos); interpolatedState.fireTick = time.tick; EntityManager.SetComponentData(entity, interpolatedState); } } break; case Ability_ProjectileLauncher.Phase.Active: { var phaseDuration = time.DurationSinceTick(predictedState.phaseStartTick); if (phaseDuration > settings.activationDuration) { var charAbility = EntityManager.GetComponentData(entity); var character = EntityManager.GetComponentObject(charAbility.character); predictedState.SetPhase(Ability_ProjectileLauncher.Phase.Cooldown, time.tick); character.State.SetAction(CharacterPredictedState.StateData.Action.None, time.tick); } break; } case Ability_ProjectileLauncher.Phase.Cooldown: { var phaseDuration = time.DurationSinceTick(predictedState.phaseStartTick); if (phaseDuration > settings.cooldownDuration) { predictedState.SetPhase(Ability_ProjectileLauncher.Phase.Idle, time.tick); } break; } } EntityManager.SetComponentData(entity, predictedState); Profiler.EndSample(); } }