Step 1: the Salty project, two modules, tests and the gym

Salty.uproject (UE 5.8) from the Third Person template with class redirects, the SaltyCore and Salty modules,
the three build targets, USaltyAssetManager calling InitGlobalData, Config/Tags with the 23 root namespaces,
Scripts/run-tests.sh, build.sh and Authoring/create_gym.py, L_Gym, Git LFS attributes and the placeholder test.
Template variants kept as reference (D-41). The four Fab packs stay out of the repository for now (~10 GB).
The editor serves the engine MCP plugin on 127.0.0.1:8000 through DefaultEditorPerProjectUserSettings.ini.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Rainer Leit
2026-09-16 20:10:43 +03:00
co-authored by Claude Fable 5.1
parent 0e61a77346
commit 4f2c55cd2a
1434 changed files with 11358 additions and 27 deletions
@@ -0,0 +1,49 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CombatActivationVolume.h"
#include "Components/BoxComponent.h"
#include "GameFramework/Character.h"
#include "CombatActivatable.h"
ACombatActivationVolume::ACombatActivationVolume()
{
PrimaryActorTick.bCanEverTick = false;
// create the box volume
RootComponent = Box = CreateDefaultSubobject<UBoxComponent>(TEXT("Box"));
check(Box);
// set the box's extent
Box->SetBoxExtent(FVector(500.0f, 500.0f, 500.0f));
// set the default collision profile to overlap all dynamic
Box->SetCollisionProfileName(FName("OverlapAllDynamic"));
// bind the begin overlap
Box->OnComponentBeginOverlap.AddDynamic(this, &ACombatActivationVolume::OnOverlap);
}
void ACombatActivationVolume::OnOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
// has a Character entered the volume?
ACharacter* PlayerCharacter = Cast<ACharacter>(OtherActor);
if (PlayerCharacter)
{
// is the Character controlled by a player
if (PlayerCharacter->IsPlayerControlled())
{
// process the actors to activate list
for (AActor* CurrentActor : ActorsToActivate)
{
// is the referenced actor activatable?
if(ICombatActivatable* Activatable = Cast<ICombatActivatable>(CurrentActor))
{
Activatable->ActivateInteraction(PlayerCharacter);
}
}
}
}
}
@@ -0,0 +1,40 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "CombatActivationVolume.generated.h"
class UBoxComponent;
/**
* A simple volume that activates a list of actors when the player pawn enters.
*/
UCLASS()
class ACombatActivationVolume : public AActor
{
GENERATED_BODY()
/** Collision box volume */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category ="Components", meta = (AllowPrivateAccess = "true"))
UBoxComponent* Box;
protected:
/** List of actors to activate when this volume is entered */
UPROPERTY(EditAnywhere, Category="Activation Volume")
TArray<AActor*> ActorsToActivate;
public:
/** Constructor */
ACombatActivationVolume();
protected:
/** Handles overlaps with the box volume */
UFUNCTION()
void OnOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
};
@@ -0,0 +1,38 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CombatCheckpointVolume.h"
#include "CombatCharacter.h"
#include "CombatPlayerController.h"
ACombatCheckpointVolume::ACombatCheckpointVolume()
{
// create the box volume
RootComponent = Box = CreateDefaultSubobject<UBoxComponent>(TEXT("Box"));
check(Box);
// set the box's extent
Box->SetBoxExtent(FVector(500.0f, 500.0f, 500.0f));
// set the default collision profile to overlap all dynamic
Box->SetCollisionProfileName(FName("OverlapAllDynamic"));
// bind the begin overlap
Box->OnComponentBeginOverlap.AddDynamic(this, &ACombatCheckpointVolume::OnOverlap);
}
void ACombatCheckpointVolume::OnOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
// has the player entered this volume?
ACombatCharacter* PlayerCharacter = Cast<ACombatCharacter>(OtherActor);
if (PlayerCharacter)
{
if (ACombatPlayerController* PC = Cast<ACombatPlayerController>(PlayerCharacter->GetController()))
{
// update the player's respawn checkpoint
PC->SetRespawnTransform(PlayerCharacter->GetActorTransform());
}
}
}
@@ -0,0 +1,29 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/BoxComponent.h"
#include "CombatCheckpointVolume.generated.h"
UCLASS(abstract)
class ACombatCheckpointVolume : public AActor
{
GENERATED_BODY()
/** Collision box volume */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Components, meta = (AllowPrivateAccess = "true"))
UBoxComponent* Box;
public:
/** Constructor */
ACombatCheckpointVolume();
protected:
/** Handles overlaps with the box volume */
UFUNCTION()
void OnOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
};
@@ -0,0 +1,83 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CombatDamageableBox.h"
#include "Components/StaticMeshComponent.h"
#include "TimerManager.h"
#include "Engine/World.h"
ACombatDamageableBox::ACombatDamageableBox()
{
PrimaryActorTick.bCanEverTick = false;
// create the mesh
RootComponent = Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
// set the collision properties
Mesh->SetCollisionProfileName(FName("BlockAllDynamic"));
// enable physics
Mesh->SetSimulatePhysics(true);
// disable navigation relevance so boxes don't affect NavMesh generation
Mesh->bNavigationRelevant = false;
}
void ACombatDamageableBox::RemoveFromLevel()
{
// destroy this actor
Destroy();
}
void ACombatDamageableBox::EndPlay(EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
// clear the death timer
GetWorld()->GetTimerManager().ClearTimer(DeathTimer);
}
void ACombatDamageableBox::ApplyDamage(float Damage, AActor* DamageCauser, const FVector& DamageLocation, const FVector& DamageImpulse)
{
// only process damage if we still have HP
if (CurrentHP > 0.0f)
{
// apply the damage
CurrentHP -= Damage;
// are we dead?
if (CurrentHP <= 0.0f)
{
HandleDeath();
}
// apply a physics impulse to the box, ignoring its mass
Mesh->AddImpulseAtLocation(DamageImpulse * Mesh->GetMass(), DamageLocation);
// call the BP handler to play effects, etc.
OnBoxDamaged(DamageLocation, DamageImpulse);
}
}
void ACombatDamageableBox::HandleDeath()
{
// change the collision object type to Visibility so we ignore most interactions but still retain physics collisions
Mesh->SetCollisionObjectType(ECC_Visibility);
// call the BP handler to play effects, etc.
OnBoxDestroyed();
// set up the death cleanup timer
GetWorld()->GetTimerManager().SetTimer(DeathTimer, this, &ACombatDamageableBox::RemoveFromLevel, DeathDelayTime);
}
void ACombatDamageableBox::ApplyHealing(float Healing, AActor* Healer)
{
// stub
}
void ACombatDamageableBox::NotifyDanger(const FVector& DangerLocation, AActor* DangerSource)
{
// stub
}
@@ -0,0 +1,71 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "CombatDamageable.h"
#include "CombatDamageableBox.generated.h"
/**
* A simple physics box that reacts to damage through the ICombatDamageable interface
*/
UCLASS(abstract)
class ACombatDamageableBox : public AActor, public ICombatDamageable
{
GENERATED_BODY()
/** Damageable box mesh */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
UStaticMeshComponent* Mesh;
public:
/** Constructor */
ACombatDamageableBox();
protected:
/** Amount of HP this box starts with. */
UPROPERTY(EditAnywhere, Category="Damage")
float CurrentHP = 3.0f;
/** Time to wait before we remove this box from the level. */
UPROPERTY(EditAnywhere, Category="Damage", meta = (ClampMin = 0, ClampMax = 10, Units = "s"))
float DeathDelayTime = 6.0f;
/** Timer to defer destruction of this box after its HP are depleted */
FTimerHandle DeathTimer;
/** Blueprint damage handler for effect playback */
UFUNCTION(BlueprintImplementableEvent, Category="Damage")
void OnBoxDamaged(const FVector& DamageLocation, const FVector& DamageImpulse);
/** Blueprint destruction handler for effect playback */
UFUNCTION(BlueprintImplementableEvent, Category="Damage")
void OnBoxDestroyed();
/** Timer callback to remove the box from the level after it dies */
void RemoveFromLevel();
public:
/** EndPlay cleanup */
void EndPlay(EEndPlayReason::Type EndPlayReason) override;
// ~Begin CombatDamageable interface
/** Handles damage and knockback events */
virtual void ApplyDamage(float Damage, AActor* DamageCauser, const FVector& DamageLocation, const FVector& DamageImpulse) override;
/** Handles death events */
virtual void HandleDeath() override;
/** Handles healing events */
virtual void ApplyHealing(float Healing, AActor* Healer) override;
/** Allows reaction to incoming attacks */
virtual void NotifyDanger(const FVector& DangerLocation, AActor* DangerSource) override;
// ~End CombatDamageable interface
};
@@ -0,0 +1,56 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CombatDummy.h"
#include "Components/SceneComponent.h"
#include "Components/StaticMeshComponent.h"
#include "PhysicsEngine/PhysicsConstraintComponent.h"
ACombatDummy::ACombatDummy()
{
PrimaryActorTick.bCanEverTick = true;
// create the root
Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
SetRootComponent(Root);
// create the base plate
BasePlate = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Base Plate"));
BasePlate->SetupAttachment(RootComponent);
// create the dummy
Dummy = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Dummy"));
Dummy->SetupAttachment(RootComponent);
Dummy->SetSimulatePhysics(true);
// create the physics constraint
PhysicsConstraint = CreateDefaultSubobject<UPhysicsConstraintComponent>(TEXT("Physics Constraint"));
PhysicsConstraint->SetupAttachment(RootComponent);
PhysicsConstraint->SetConstrainedComponents(BasePlate, NAME_None, Dummy, NAME_None);
}
void ACombatDummy::ApplyDamage(float Damage, AActor* DamageCauser, const FVector& DamageLocation, const FVector& DamageImpulse)
{
// apply impulse to the dummy
Dummy->AddImpulseAtLocation(DamageImpulse, DamageLocation);
// call the BP handler
BP_OnDummyDamaged(DamageLocation, DamageImpulse.GetSafeNormal());
}
void ACombatDummy::HandleDeath()
{
// unused
}
void ACombatDummy::ApplyHealing(float Healing, AActor* Healer)
{
// unused
}
void ACombatDummy::NotifyDanger(const FVector& DangerLocation, AActor* DangerSource)
{
// unused
}
@@ -0,0 +1,63 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "CombatDamageable.h"
#include "CombatDummy.generated.h"
class UStaticMeshComponent;
class UPhysicsConstraintComponent;
/**
* A simple invincible combat training dummy
*/
UCLASS(abstract)
class ACombatDummy : public AActor, public ICombatDamageable
{
GENERATED_BODY()
/** Root component */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
USceneComponent* Root;
/** Static base plate */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
UStaticMeshComponent* BasePlate;
/** Physics enabled dummy mesh */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
UStaticMeshComponent* Dummy;
/** Physics constraint holding the dummy and base plate together */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
UPhysicsConstraintComponent* PhysicsConstraint;
public:
/** Constructor */
ACombatDummy();
// ~Begin CombatDamageable interface
/** Handles damage and knockback events */
virtual void ApplyDamage(float Damage, AActor* DamageCauser, const FVector& DamageLocation, const FVector& DamageImpulse) override;
/** Handles death events */
virtual void HandleDeath() override;
/** Handles healing events */
virtual void ApplyHealing(float Healing, AActor* Healer) override;
/** Allows reaction to incoming attacks */
virtual void NotifyDanger(const FVector& DangerLocation, AActor* DangerSource) override;
// ~End CombatDamageable interface
protected:
/** Blueprint handle to apply damage effects */
UFUNCTION(BlueprintImplementableEvent, Category="Combat", meta = (DisplayName = "On Dummy Damaged"))
void BP_OnDummyDamaged(const FVector& Location, const FVector& Direction);
};
@@ -0,0 +1,27 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CombatLavaFloor.h"
#include "CombatDamageable.h"
#include "Components/StaticMeshComponent.h"
ACombatLavaFloor::ACombatLavaFloor()
{
PrimaryActorTick.bCanEverTick = false;
// create the mesh
RootComponent = Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
// bind the hit handler
Mesh->OnComponentHit.AddDynamic(this, &ACombatLavaFloor::OnFloorHit);
}
void ACombatLavaFloor::OnFloorHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
// check if the hit actor is damageable by casting to the interface
if (ICombatDamageable* Damageable = Cast<ICombatDamageable>(OtherActor))
{
// damage the actor
Damageable->ApplyDamage(Damage, this, Hit.ImpactPoint, FVector::ZeroVector);
}
}
@@ -0,0 +1,40 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "CombatLavaFloor.generated.h"
class UStaticMeshComponent;
class UPrimitiveComponent;
/**
* A basic actor that applies damage on contact through the ICombatDamageable interface.
*/
UCLASS(abstract)
class ACombatLavaFloor : public AActor
{
GENERATED_BODY()
/** Floor mesh */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
UStaticMeshComponent* Mesh;
protected:
/** Amount of damage to deal on contact */
UPROPERTY(EditAnywhere, Category="Damage")
float Damage = 10000.0f;
public:
/** Constructor */
ACombatLavaFloor();
protected:
/** Blocking hit handler */
UFUNCTION()
void OnFloorHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);
};