Arena/Gladiator.cs
using System;
public enum Mood { Neutral, Happy, Grin, Upset, Hurt }
public enum PopupKind { Damage, Miss, Gain, Text, Enchant }
public sealed class Gladiator : Component
{
public const float WalkSpeed = 420f;
public const float LeapTime = 0.6f;
public const float LeapHeight = 170f;
public class Popup
{
public string Text;
public PopupKind Kind;
public string Colour;
public RealTimeSince Age;
public float OffsetX;
}
[Sync( SyncFlags.FromHost )] public string DisplayName { get; set; }
/// <summary>Shown on the VS screen, e.g. "Fearful prisoner".</summary>
[Sync( SyncFlags.FromHost )] public string Title { get; set; }
[Sync( SyncFlags.FromHost )] public bool IsAI { get; set; }
[Sync( SyncFlags.FromHost )] public Guid ControllerId { get; set; }
[Sync( SyncFlags.FromHost )] public int Level { get; set; } = 1;
[Sync( SyncFlags.FromHost )] public int Strength { get; set; } = 3;
[Sync( SyncFlags.FromHost )] public int Attack { get; set; } = 3;
[Sync( SyncFlags.FromHost )] public int Defence { get; set; } = 3;
[Sync( SyncFlags.FromHost )] public int Vitality { get; set; } = 3;
[Sync( SyncFlags.FromHost )] public int Agility { get; set; } = 3;
[Sync( SyncFlags.FromHost )] public int Stamina { get; set; } = 3;
[Sync( SyncFlags.FromHost )] public int Charisma { get; set; } = 3;
[Sync( SyncFlags.FromHost )] public int Magicka { get; set; } = 1;
[Sync( SyncFlags.FromHost )] public int Health { get; set; }
[Sync( SyncFlags.FromHost )] public int Energy { get; set; }
[Sync( SyncFlags.FromHost )] public int Magic { get; set; }
[Sync( SyncFlags.FromHost )] public int ArmourPoints { get; set; }
[Sync( SyncFlags.FromHost )] public int MaxArmour { get; set; }
/// <summary>Duels: armour broken = beaten.</summary>
[Sync( SyncFlags.FromHost )] public bool Broken { get; set; }
/// <summary>Turns left frozen solid.</summary>
[Sync( SyncFlags.FromHost )] public int Frozen { get; set; }
[Sync( SyncFlags.FromHost )] public int Step { get; set; }
[Sync( SyncFlags.FromHost )] public float PowerRoll { get; set; } = 0.3f;
[Sync( SyncFlags.FromHost )] public int Race { get; set; }
[Sync( SyncFlags.FromHost )] public int Skin { get; set; }
[Sync( SyncFlags.FromHost )] public int Hair { get; set; }
[Sync( SyncFlags.FromHost )] public int Beard { get; set; }
[Sync( SyncFlags.FromHost )] public int HairColour { get; set; }
[Sync( SyncFlags.FromHost )] public int Cloth { get; set; }
[Sync( SyncFlags.FromHost )] public int Helm { get; set; } = -1;
[Sync( SyncFlags.FromHost )] public int Chest { get; set; } = -1;
[Sync( SyncFlags.FromHost )] public int Shoulders { get; set; } = -1;
[Sync( SyncFlags.FromHost )] public int Gauntlets { get; set; } = -1;
[Sync( SyncFlags.FromHost )] public int Thighs { get; set; } = -1;
[Sync( SyncFlags.FromHost )] public int Greaves { get; set; } = -1;
[Sync( SyncFlags.FromHost )] public int Boots { get; set; } = -1;
[Sync( SyncFlags.FromHost )] public int Shield { get; set; } = -1;
[Sync( SyncFlags.FromHost )] public int WeaponId { get; set; }
[Sync( SyncFlags.FromHost )] public Enchant Enchant { get; set; }
[Sync( SyncFlags.FromHost )] public int EnchantTier { get; set; }
/// <summary>Spells known, one bit per <see cref="Spell"/>.</summary>
[Sync( SyncFlags.FromHost )] public int SpellBits { get; set; }
[Sync( SyncFlags.FromHost )] public int HealthPotions { get; set; }
[Sync( SyncFlags.FromHost )] public int EnergyPotions { get; set; }
[Sync( SyncFlags.FromHost )] public int MagicPotions { get; set; }
public int MaxHealth => Catalog.MaxHealth( Vitality );
public int MaxEnergy => Catalog.MaxEnergy( Stamina );
public int MaxMagic => Catalog.MaxMagicka( Magicka );
public bool IsDead => Health <= 0 || Broken;
public bool IsLocalPlayer => !IsAI && ControllerId == Connection.Local.Id;
public WeaponDef Weapon => Catalog.Weapons[Math.Clamp( WeaponId, 0, Catalog.Weapons.Length - 1 )];
public bool Knows( Spell s ) => (SpellBits & (1 << (int)s)) != 0;
public int Potions( Potion p ) => p switch { Potion.Health => HealthPotions, Potion.Energy => EnergyPotions, _ => MagicPotions };
public void UsePotion( Potion p )
{
switch ( p )
{
case Potion.Health: HealthPotions--; break;
case Potion.Energy: EnergyPotions--; break;
default: MagicPotions--; break;
}
}
public Look Look => new()
{
Race = Race, Skin = Skin, Hair = Hair, Beard = Beard, HairColour = HairColour, Cloth = Cloth,
Helm = Helm, Chest = Chest, Shoulders = Shoulders, Gauntlets = Gauntlets, Thighs = Thighs, Greaves = Greaves,
Boots = Boots, Shield = Shield, Weapon = WeaponId
};
public void SetLook( Look l )
{
Race = l.Race; Skin = l.Skin; Hair = l.Hair; Beard = l.Beard; HairColour = l.HairColour; Cloth = l.Cloth;
Helm = l.Helm; Chest = l.Chest; Shoulders = l.Shoulders; Gauntlets = l.Gauntlets; Thighs = l.Thighs;
Greaves = l.Greaves; Boots = l.Boots; Shield = l.Shield; WeaponId = l.Weapon;
MaxArmour = l.ArmourPoints();
}
public void ResetVitals()
{
Health = MaxHealth;
Energy = MaxEnergy;
Magic = MaxMagic;
ArmourPoints = MaxArmour;
Broken = false;
Frozen = 0;
}
public void RollPower() => PowerRoll = Random.Shared.Float( 0.2f, 0.4f );
// ---- client side presentation ----
public float DisplayX { get; private set; } = float.NaN;
public float Lift { get; private set; }
public bool Walking { get; private set; }
public List<Popup> Popups { get; } = new();
string oneShot;
RealTimeSince oneShotTime;
RealTimeSince loopTime = 0;
RealTimeSince deadTime;
bool wasDead;
bool leaping;
float leapFrom;
RealTimeSince leapTime;
Mood mood;
RealTimeUntil moodUntil;
public Mood CurrentMood => IsDead ? Mood.Hurt : !moodUntil ? mood : (Health < MaxHealth * 0.3f || (MaxArmour > 0 && ArmourPoints < MaxArmour * 0.3f)) ? Mood.Upset : Mood.Neutral;
public (string Anim, float Time) CurrentAnim
{
get
{
if ( IsDead ) return ("die", (float)deadTime);
if ( oneShot is not null && PuppetRig.Anims.TryGetValue( oneShot, out var a ) && oneShotTime < a.Length ) return (oneShot, (float)oneShotTime);
if ( Walking ) return ("walk", (float)loopTime);
var fight = Scene.Get<ArenaFight>();
if ( fight.IsValid() && fight.State == FightState.Over && fight.Winner == this ) return ("win", (float)loopTime);
return ("idle", (float)loopTime);
}
}
protected override void OnUpdate()
{
var fight = Scene.Get<ArenaFight>();
if ( !fight.IsValid() ) return;
float target = fight.StepToX( Step );
if ( float.IsNaN( DisplayX ) ) DisplayX = target;
if ( IsDead && !wasDead ) deadTime = 0;
wasDead = IsDead;
if ( leaping )
{
float t = Math.Clamp( leapTime / LeapTime, 0f, 1f );
DisplayX = leapFrom + (target - leapFrom) * t;
Lift = MathF.Sin( t * MathF.PI ) * LeapHeight;
Walking = false;
if ( t >= 1f ) { leaping = false; Lift = 0; }
}
else
{
float diff = target - DisplayX;
float move = Math.Clamp( diff, -WalkSpeed * Time.Delta, WalkSpeed * Time.Delta );
DisplayX += move;
Walking = MathF.Abs( diff ) > 2f;
}
Popups.RemoveAll( p => p.Age > 1.6f );
}
[Rpc.Broadcast( NetFlags.HostOnly )]
public void PlayAnim( string anim )
{
oneShot = anim;
oneShotTime = 0;
}
[Rpc.Broadcast( NetFlags.HostOnly )]
public void PlayLeap()
{
leaping = true;
leapFrom = DisplayX;
leapTime = 0;
oneShot = "leap";
oneShotTime = 0;
}
[Rpc.Broadcast( NetFlags.HostOnly )]
public void SetMood( Mood newMood, float seconds )
{
mood = newMood;
moodUntil = seconds;
}
[Rpc.Broadcast( NetFlags.HostOnly )]
public void ShowPopup( string text, PopupKind kind, string colour )
{
Popups.Add( new Popup { Text = text, Kind = kind, Colour = colour, Age = 0, OffsetX = Popups.Count * 70f } );
}
}