using System;
namespace AfterHours.Customers
{
///
/// Kern-Mechanik des Spiels: Wie wohl fühlt sich ein Kunde gerade?
/// Fällt der Komfort unter , legt der Kunde
/// die Ware zurück und geht.
///
/// Engine-frei und damit testbar – siehe Tests/EditMode/ComfortModelTests.cs.
///
public static class ComfortModel
{
public const float AbandonThreshold = 0.25f;
/// Komfortverlust pro anderem Kunden im Nahbereich.
private const float CrowdingPenaltyPerPerson = 0.09f;
/// Malus, wenn Personal ungefragt in Reichweite steht.
private const float UnwantedStaffPenalty = 0.18f;
/// Malus, wenn das Regal von der Straße aus einsehbar ist.
private const float ExposedShelfPenalty = 0.22f;
/// Erholung pro Sekunde, wenn niemand stört.
private const float RecoveryPerSecond = 0.12f;
///
/// Zielkomfort für die aktuelle Situation (0..1).
///
/// Charaktereigenschaft des Kunden, 0..1. Höher = unerschrockener.
/// Diskretionsbedarf des betrachteten Produkts, 0..1.
/// Andere Kunden im Nahbereich.
/// Personal steht daneben, ohne dass Beratung gewünscht war.
/// Regal ist vom Schaufenster aus einsehbar.
public static float EvaluateTarget(
float baseTolerance,
float discretionDemand,
int nearbyCustomers,
bool staffNearbyUnrequested,
bool shelfExposedToStreet)
{
if (nearbyCustomers < 0) throw new ArgumentOutOfRangeException(nameof(nearbyCustomers));
float tolerance = Clamp01(baseTolerance);
float sensitivity = Clamp01(discretionDemand);
// Ein toleranter Kunde bei unkritischer Ware ist praktisch unstörbar.
float exposure = sensitivity * (1f - tolerance * 0.6f);
float penalty = 0f;
penalty += nearbyCustomers * CrowdingPenaltyPerPerson;
if (staffNearbyUnrequested) penalty += UnwantedStaffPenalty;
if (shelfExposedToStreet) penalty += ExposedShelfPenalty;
return Clamp01(1f - penalty * exposure * 2f);
}
/// Bewegt den aktuellen Komfort auf den Zielwert zu (Frame-Schritt).
public static float Step(float current, float target, float deltaTime)
{
if (target >= current)
{
// Erholung ist träge …
return Clamp01(current + RecoveryPerSecond * deltaTime);
}
// … Unbehagen schlägt sofort durch.
return Clamp01(target);
}
public static bool ShouldAbandon(float comfort) => comfort < AbandonThreshold;
private static float Clamp01(float v) => v < 0f ? 0f : (v > 1f ? 1f : v);
}
}