something is no yes

This commit is contained in:
2025-08-31 20:47:08 +02:00
parent fb1c3f8290
commit 224d55aec6
44 changed files with 11265 additions and 128 deletions

View File

@@ -1,9 +1,11 @@
using System;
using System.Runtime.CompilerServices;
using RebootKit.Engine.Extensions;
using RebootKit.Engine.Main;
using RebootKit.Engine.Network;
using RebootKit.Engine.Simulation;
using RebootReality.jelycho.Player;
using TriInspector;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.AI;
@@ -23,6 +25,41 @@ namespace RebootReality.jelycho.Enemies {
}
}
public enum ZombieBodyPartType {
Body,
Head,
LeftArm,
RightArm,
LeftLeg,
RightLeg
}
// @TODO: weird naming
[Serializable]
public class ZombieBodyPart {
public Transform root;
public Transform[] wearables;
public Collider[] colliders;
public ParticleSystem bloodStreamParticles;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsAlive() {
return root.gameObject.activeSelf;
}
public void HideParts() {
root.gameObject.SetActive(false);
foreach (Transform wearable in wearables) {
wearable.gameObject.SetActive(false);
}
foreach (Collider collider in colliders) {
collider.enabled = false;
}
}
}
[DeclareBoxGroup("Body parts")]
public class ZombieActor : Actor, IKillable {
static readonly Logger s_Logger = new Logger(nameof(ZombieActor));
@@ -41,14 +78,19 @@ namespace RebootReality.jelycho.Enemies {
[SerializeField] NavMeshAgent m_NavAgent;
[SerializeField] Collider[] m_RagdollColliders;
[SerializeField] Collider m_RootCollider;
[SerializeField] Rigidbody[] m_RagdollRigidbodies;
[SerializeField] Collider[] m_Hitboxes;
[SerializeField] float m_MaxAttackDistance = 1.0f;
[SerializeField] float m_LoseInterestMinDistance = 10.0f;
[SerializeField] ulong m_BaseDamage = 10;
[SerializeField] float m_AttackDelay = 1.0f;
[SerializeField, Group("Body parts")] ZombieBodyPart m_Head;
[SerializeField, Group("Body parts")] ZombieBodyPart m_LeftArm;
[SerializeField, Group("Body parts")] ZombieBodyPart m_RightArm;
[SerializeField, Group("Body parts")] ZombieBodyPart m_LeftLeg;
[SerializeField, Group("Body parts")] ZombieBodyPart m_RightLeg;
AIState m_State = AIState.Idle;
@@ -144,7 +186,7 @@ namespace RebootReality.jelycho.Enemies {
SetIdleState();
return;
}
if (distToPlayerSq <= m_MaxAttackDistance * m_MaxAttackDistance) {
m_NextAttackTimer -= dt;
if (m_NextAttackTimer <= 0.0f) {
@@ -181,6 +223,7 @@ namespace RebootReality.jelycho.Enemies {
void Die() {
s_Logger.Info("Die");
EnableRagdoll();
m_NavAgent.enabled = false;
m_State = AIState.Dead;
died.Invoke();
@@ -235,19 +278,13 @@ namespace RebootReality.jelycho.Enemies {
}
void SetRagdollLocal(bool active) {
foreach (Collider ragdollCollider in m_RagdollColliders) {
ragdollCollider.enabled = active;
}
m_RootCollider.enabled = !active;
foreach (Rigidbody ragdollRigidbody in m_RagdollRigidbodies) {
ragdollRigidbody.isKinematic = !active;
}
m_Animator.enabled = !active;
foreach (Collider hitbox in m_Hitboxes) {
hitbox.enabled = !active;
}
}
//
@@ -280,6 +317,70 @@ namespace RebootReality.jelycho.Enemies {
return damage;
}
//
// @MARK: damage?
//
public void HitFeedback(float3 hitPosition) {
if (RR.World.Context is WorldContext worldContext) {
worldContext.FeedbacksManager.SpawnBloodSplash(hitPosition);
}
}
public void ReceiveDamage(ulong damage, ZombieBodyPartType bodyPartType) {
if (!IsAlive()) {
return;
}
ZombieBodyPart bodyPart = GetBodyParty(bodyPartType);
if (!bodyPart.IsAlive()) {
return;
}
bodyPart.HideParts();
bodyPart.bloodStreamParticles.Play();
Die();
}
public bool HasBodyPart(ZombieBodyPartType bodyPart) {
if (Health <= 0) {
return false;
}
if (bodyPart == ZombieBodyPartType.Body) {
return true;
}
ZombieBodyPart part = GetBodyParty(bodyPart);
if (part == null) {
return false;
}
return part.IsAlive();
}
ZombieBodyPart GetBodyParty(ZombieBodyPartType bodyPart) {
return bodyPart switch {
ZombieBodyPartType.Body => null,
ZombieBodyPartType.Head => m_Head,
ZombieBodyPartType.LeftArm => m_LeftArm,
ZombieBodyPartType.RightArm => m_RightArm,
ZombieBodyPartType.LeftLeg => m_LeftLeg,
ZombieBodyPartType.RightLeg => m_RightLeg,
_ => throw new ArgumentOutOfRangeException(nameof(bodyPart), bodyPart, null)
};
}
///
/// @MARK: Utility
///
[Button]
void SetupHurtboxes() {
foreach (ZombieHurtbox hurtbox in GetComponentsInChildren<ZombieHurtbox>()) {
hurtbox.owner = this;
}
}
}
enum ZombieActorEvents {

View File

@@ -0,0 +1,28 @@
using RebootKit.Engine.Main;
using RebootKit.Engine.Simulation;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Assertions;
namespace RebootReality.jelycho.Enemies {
public class ZombieHurtbox : MonoBehaviour, IHurtbox {
public ZombieActor owner;
[SerializeField] ZombieBodyPartType m_BodyPart = ZombieBodyPartType.Body;
void Awake() {
Assert.IsNotNull(owner);
}
public void ReceiveDamage(Actor attacker, ulong damage, float3 worldHitPos) {
if (!owner.HasBodyPart(m_BodyPart)) {
return;
}
owner.ReceiveDamage(damage, m_BodyPart);
if (RR.World.Context is WorldContext worldContext) {
worldContext.FeedbacksManager.SpawnBloodSplash(worldHitPos);
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 37d2b8f1679f41288194769e98584e0e
timeCreated: 1756341610

View File

@@ -24,8 +24,10 @@ namespace RebootReality.jelycho.Feedbacks {
public class FeedbacksManagerActor : Actor {
static readonly Logger s_Logger = new Logger(nameof(FeedbacksManagerActor));
[SerializeField] Volume m_Volume;
[SerializeField] ParticleSystem m_BloodSplashParticles;
[SerializeField] Volume m_Volume;
[SerializeField] float m_QuickAttackIndicatorDuration = 0.3f;
[SerializeField] float m_QuickAttackIndicatorPaniniAppearSpeed = 2.0f;
[SerializeField] float m_QuickAttackIndicatorChromaticAppearSpeed = 2.0f;
@@ -115,6 +117,14 @@ namespace RebootReality.jelycho.Feedbacks {
m_IsChargeReady = false;
}
public void SpawnBloodSplash(float3 worldPosition) {
ParticleSystem.EmitParams emitParams = new ParticleSystem.EmitParams {
position = worldPosition
};
m_BloodSplashParticles.Emit(emitParams, 128);
}
//
// @MARK: Actor
//

View File

@@ -0,0 +1,86 @@
using System;
using Oddworm.Framework;
using RebootKit.Engine.Foundation;
using RebootKit.Engine.Main;
using RebootKit.Engine.Simulation;
using Unity.Mathematics;
using UnityEngine;
using Logger = RebootKit.Engine.Foundation.Logger;
namespace RebootReality.jelycho {
public class HitboxManagerActor : Actor {
static readonly Logger s_Logger = new Logger(nameof(HitboxManagerActor));
static readonly Collider[] s_CollidersBuffer = new Collider[512];
[ConfigVar("debug.hitbox", 0, "Shows hitboxes")]
static ConfigVar s_ShowHitboxesDebug;
static bool ShouldShowHitboxes() {
return s_ShowHitboxesDebug.IndexValue > 0;
}
[SerializeField] Color m_HitboxDebugColor = Color.red;
[SerializeField] float m_HitboxDebugDuration = 3.0f;
[SerializeField] LayerMask m_HurtboxesLayerMask;
//
// @MARK: HitboxManager
//
public void HitSphere(float3 origin, float radius, Actor attacker, ulong damage) {
if (RR.IsServer()) {
HitSphereLocal(origin, radius, attacker, damage);
} else {
throw new NotImplementedException("Hitting on client not implemented yet!");
}
}
void HitSphereLocal(float3 origin, float radius, Actor attacker, ulong damage) {
if (ShouldShowHitboxes()) {
DbgDraw.Sphere(origin,
Quaternion.identity,
Vector3.one * radius,
m_HitboxDebugColor,
m_HitboxDebugDuration);
}
int count = Physics.OverlapSphereNonAlloc(origin, radius, s_CollidersBuffer, m_HurtboxesLayerMask);
for (int i = 0; i < count; ++i) {
if (s_CollidersBuffer[i].TryGetComponent(out IHurtbox hurtbox)) {
float3 p = s_CollidersBuffer[i].ClosestPoint(origin);
if (ShouldShowHitboxes()) {
DbgDraw.Sphere(p, Quaternion.identity, Vector3.one * 0.1f, Color.magenta, m_HitboxDebugDuration);
}
hurtbox.ReceiveDamage(attacker, damage, p);
}
// if (s_CollidersBuffer[i].TryGetComponent(out IKillable killable)) {
// Actor killableActor = killable as Actor;
// if (killableActor == attacker) {
// continue;
// }
//
// if (killable is IHasHitFeedback hitFeedback) {
// float3 p = s_CollidersBuffer[i].ClosestPoint(origin);
// if (ShouldShowHitboxes()) {
// DbgDraw.Sphere(p, Quaternion.identity, Vector3.one * 0.1f, Color.magenta, m_HitboxDebugDuration);
// }
//
// hitFeedback.HitFeedback(p);
// }
//
// // @TODO: send command to server to hit an killable
// killable.OnHit(attacker, damage);
// }
}
}
//
// @MARK: Actor
//
protected override IActorData CreateActorData() {
return new NoActorData();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f3973c6291404892a6d48dcb056b6de5
timeCreated: 1755826207

View File

@@ -0,0 +1,8 @@
using RebootKit.Engine.Simulation;
using Unity.Mathematics;
namespace RebootReality.jelycho {
public interface IHurtbox {
void ReceiveDamage(Actor attacker, ulong damage, float3 worldHitPos);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 82f5148be9a14cd4a59245f69d6f4079
timeCreated: 1756341517

View File

@@ -9,8 +9,6 @@ namespace RebootReality.jelycho.Items {
static readonly Logger s_Logger = new Logger(nameof(ItemActor));
class ItemActorData : IActorData {
public ItemConfig Config;
public void Serialize(NetworkBufferWriter writer) { }
public void Deserialize(NetworkBufferReader reader) { }
@@ -33,9 +31,7 @@ namespace RebootReality.jelycho.Items {
// @MARK: Actor
//
protected override IActorData CreateActorData() {
return new ItemActorData {
Config = Config
};
return new ItemActorData { };
}
//

View File

@@ -37,6 +37,9 @@ namespace RebootReality.jelycho.Items {
public class ItemConfig {
public Sprite icon;
[Header("Stats")]
public ulong baseDamage = 10;
[Header("Mounting")]
[MaxLength(32)] public string characterEquippedMountSlotName = "hand_right";

View File

@@ -1,31 +1,24 @@
using RebootKit.Engine.Simulation;
using RebootReality.jelycho.Enemies;
using RebootKit.Engine.Main;
using RebootKit.Engine.Simulation;
using RebootReality.jelycho.Player;
using UnityEngine;
namespace RebootReality.jelycho.Items {
public class ItemQuickAttackAction : IItemQuickAttackAction {
static readonly Collider[] s_CollidersBuffer = new Collider[512];
public void Attack(Actor attacker, ItemActor itemActor) {
if (RR.World.Context is not WorldContext worldContext) {
return;
}
PlayerActor playerActor = attacker as PlayerActor;
if (playerActor == null) {
return;
}
Vector3 attackPosition = playerActor.GetAttackPosition();
float attackRadius = 1.0f;
int count = Physics.OverlapSphereNonAlloc(attackPosition, 3.0f, s_CollidersBuffer);
for (int i = 0; i < count; ++i) {
if (s_CollidersBuffer[i].TryGetComponent(out IKillable killable)) {
Actor killableActor = killable as Actor;
if (killableActor == attacker) {
continue;
}
playerActor.DealDamage(killable);
}
}
worldContext.HitboxManager.HitSphere(attackPosition, attackRadius, attacker, itemActor.Config.baseDamage);
}
}
}

View File

@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using Oddworm.Framework;
using R3;
using RebootKit.Engine.Extensions;
using RebootKit.Engine.Foundation;
using RebootKit.Engine.Main;
using RebootKit.Engine.Network;
using RebootKit.Engine.Simulation;
@@ -11,7 +9,6 @@ using RebootReality.jelycho.Items;
using Unity.Collections;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.AddressableAssets;
using Logger = RebootKit.Engine.Foundation.Logger;
namespace RebootReality.jelycho.Player {
@@ -298,6 +295,10 @@ namespace RebootReality.jelycho.Player {
m_QuickAttackComboCounter = 0;
PlayQuickAttackAnimation(m_QuickAttackComboCounter);
m_QuickAttackState = QuickAttackState.PlayingAnimation;
if (m_EquippedItem.Config.quickAttackAction != null) {
m_EquippedItem.Config.quickAttackAction.Attack(this, m_EquippedItem);
}
} else if (m_QuickAttackState == QuickAttackState.PlayingAnimation) {
m_QuickAttackComboCounter = 0;
} else if (m_QuickAttackState == QuickAttackState.WaitingForNextAttack) {
@@ -872,7 +873,10 @@ namespace RebootReality.jelycho.Player {
}
public Vector3 GetAttackPosition() {
return m_Camera.transform.position + m_Camera.transform.forward * 1.5f;
float3 origin = m_Camera.Camera.transform.position;
float3 dir = m_Camera.Camera.transform.forward;
float3 pos = origin + dir * 1.5f;
return pos;
}
//

View File

@@ -14,5 +14,6 @@ namespace RebootReality.jelycho {
[field: SerializeField] public FeedbacksManagerActor FeedbacksManager { get; private set; }
[field: SerializeField] public InfectedAreaManager InfectedAreaManager { get; private set; }
[field: SerializeField] public WavesManagerActor WavesManager { get; private set; }
[field: SerializeField] public HitboxManagerActor HitboxManager { get; private set; }
}
}