using Unity.Mathematics;
using UnityEngine;
[System.Serializable]
public unsafe struct Health_Native : IComponent_Native
{
public string healthQuotientString()
{
return health + " / " + healthCapacity;
}
public enum TakeDamageResult
{
ALREADY_DEAD,
TOOK_DAMAGE_DID_NOT_DIE,
TOOK_DAMAGE_AND_DIED,
}
public GameObject_Native* gameObject { get; set; }
public Transform_Native* transform; //cache for speed
public float healthCapacity;
private float _health;
public float health { get => _health; set => _health = math.clamp(value, 0f, healthCapacity); }
public bool isDead => health <= 0;
public bool isFull => health > -healthCapacity;
public byte relativeDifficulty; //cannot be zero
[SerializeField] private float healthRegenPerFrame;
[Header("FX")]
[SerializeField] private VFXID_Native onDieVFX; //attach sound to vfx
public void init(GameObject_Native* _gameObjectPtr)
{
this.gameObject = _gameObjectPtr;
this.transform = this.gameObject->transform;
this.lastTakeDamageTime = -1000;
health = healthCapacity;
}
public void resetFromPooling()
{
health = healthCapacity;
}
public Attacker* lastInflictingAttacker { get; private set; }
public float lastTakeDamageTime { get; private set; }
public void parallelTick()
{
if(!isDead) health += healthRegenPerFrame;
}
public TakeDamageResult takeDamage(float amount, Attacker* inflicting, ref UpdateParams p)
{
if (isDead) return TakeDamageResult.ALREADY_DEAD;
health -= amount;
lastInflictingAttacker = inflicting;
lastTakeDamageTime = p.time;
if (isDead) //died
{
// if (deactivateGameObjectOnDie) this.gameObject->active = false;
if(onDieVFX != VFXID_Native.NONE)p.vfxManagerNative.play(onDieVFX, this.transform->worldPosition.toFloat3());
return TakeDamageResult.TOOK_DAMAGE_AND_DIED;
}
else
{
return TakeDamageResult.TOOK_DAMAGE_DID_NOT_DIE;
}
}
public bool tryGetRecentOpposingAttacker(float time, out AttackTarget attackTarget)
{
attackTarget = default;
if (lastInflictingAttacker == null || !lastInflictingAttacker->gameObject->active) return false;
const float recentTimeThreshold = 1f;
if (time > lastTakeDamageTime + recentTimeThreshold) return false;
var opposerHealth = lastInflictingAttacker->gameObject->getComponent<Health_Native>();
if (opposerHealth->isDead) return false;
attackTarget = new AttackTarget(opposerHealth, opposerHealth->transform->gridPosition);
return true;
}
#if UNITY_EDITOR
public void onValidate()
{
if (relativeDifficulty == 0) relativeDifficulty = 1;
}
#endif
}
using System;
using Unity.Mathematics;
using UnityEngine;
public static unsafe class UnitComponent_NativeExtensions
{
/// <summary>
/// a shorthand for casting
/// </summary>
public static UnitComponent_Native* toUnitComponentPtr(this IntPtr intPtr)
{
return (UnitComponent_Native*) intPtr;
}
}
/// <summary>
/// responsible for returning to pool
/// </summary>
[System.Serializable]
public unsafe struct UnitComponent_Native : IComponent_Native
{
public GameObject_Native* gameObject { get; set; }
private Health_Native* health;
private FlowFieldMovement* movement;
private Attacker* attacker;
private EncodedAnimator* encodedAnimator;
public UnitType unitType;
public byte byteUnitType => (byte) unitType; //convenience, since NativeHashmaps can't use enums...
public bool inTower { get; set; }
[SerializeField] private float attackAnimDuration;
public EncodedAnimState encodedAnimState { get; private set; }
public float2 pointingDirectionLocal { get; private set; }
private float lastPointingDirectionUpdateTime;
public void init(GameObject_Native* _gameObject, Health_Native* _health, FlowFieldMovement* _movement, Attacker* _attacker, EncodedAnimator* _encodedAnimator)
{
this.gameObject = _gameObject;
this.health = _health;
this.movement = _movement;
this.attacker = _attacker;
this.encodedAnimator = _encodedAnimator;
float2 pointing = new(1, 0);
pointing = pointing.rotate(this.gameObject->idMod30 * 120); //randomize pointing direction
pointingDirectionLocal = pointing;
}
public void tick(ref UpdateParams p)
{
if (health->isDead)
{
this.gameObject->active = false;
fixed (UnitComponent_Native* selfPtr = &this) p.unitManagerNative.returnToPool(selfPtr);
}
else
{
resolveAnimState(p.time);
encodedAnimator->encodedAnimState = encodedAnimState;
encodedAnimator->pointingDirectionLocal = pointingDirectionLocal;
}
}
public void resetFromPooling()
{
health->resetFromPooling();
}
private void resolveAnimState(float time)
{
if (time < attacker->lastAttackTime + attackAnimDuration)
{
encodedAnimState = EncodedAnimState.ATTACKING;
var d = attacker->lastVictimLocalPosition - this.gameObject->transform->localPosition;
updatePointingDirection(time,d,3f);
}
else if (attacker->isAiming())
{
encodedAnimState = EncodedAnimState.AIMING;
var d = attacker->lastVictimLocalPosition - this.gameObject->transform->localPosition;
updatePointingDirection(time,d,3f);
}
else if (movement->state == FlowFieldMovement.State.MOVING_TO_DESTINATION)
{
encodedAnimState = EncodedAnimState.MOVING;
updatePointingDirection(time,movement->lastFlowFieldMovementDirectionLocal);
}
else
{
encodedAnimState = EncodedAnimState.IDLE;
}
}
private void updatePointingDirection(float time, float2 targetDirection, float maxStepMultiplier = 1f)
{
if (targetDirection.Equals(float2.zero)) return;
const float minDirectionTime = 0.1f; //to really stop jumpiness
if (time < lastPointingDirectionUpdateTime+ minDirectionTime) return;
const float maxStep = 12f;
//SMOOTH rotation so it doesn't get all jumpy on diagonal movement (Crossing small sections of the grid that point in different directions)
pointingDirectionLocal =pointingDirectionLocal.rotateTowardsDirection(targetDirection, maxStep * maxStepMultiplier);
lastPointingDirectionUpdateTime = time;
}
}