UI/FittingScreen.razor
@using System;
@using Sandbox;
@using Sandbox.UI;
@* The Fitting Room: move, size, turn or mirror any S&S2 piece on the gladiator and save it.
Saved to fitting.json in the game's data folder; only what you changed is written. *@
<root class="screen">
<div class="sas-panel fit-panel">
<div class="sas-label title">fitting room</div>
<div class="cats">
@foreach ( var c in Cats )
{
var cat = c;
<div class="cat @(Cat == cat.Name ? "active" : "")" onclick=@(() => { Cat = cat.Name; Index = 0; StateHasChanged(); })>@cat.Name</div>
}
</div>
<div class="row">
<div class="sas-arrow left" onclick=@(() => Step( -1 ))></div>
<div class="sas-arrow right" onclick=@(() => Step( 1 ))></div>
<div class="key">@($"{Key} ({Index + 1}/{Keys.Count})")</div>
</div>
@foreach ( var c in Controls )
{
var ctl = c;
<div class="row">
<div class="lbl">@ctl.Label</div>
<div class="val">@ctl.Value( E )</div>
<div class="small-btn" onclick=@(() => Move( ctl.What, -ctl.Step * 10 ))>--</div>
<div class="small-btn" onclick=@(() => Move( ctl.What, -ctl.Step ))>-</div>
<div class="small-btn" onclick=@(() => Move( ctl.What, ctl.Step ))>+</div>
<div class="small-btn" onclick=@(() => Move( ctl.What, ctl.Step * 10 ))>++</div>
</div>
}
<div class="row">
<div class="lbl">mirror</div>
<div class="small-btn @(E.Flip ? "on" : "")" onclick=@(() => Edit( E with { Flip = !E.Flip } ))>@(E.Flip ? "yes" : "no")</div>
<div class="small-btn" onclick=@(() => { Fit.Reset( Key ); StateHasChanged(); })>reset this</div>
</div>
<div class="row poses">
@foreach ( var a in Anims )
{
var anim = a;
<div class="small-btn @(Anim == anim ? "on" : "")" onclick=@(() => { Anim = anim; StateHasChanged(); })>@anim</div>
}
</div>
<div class="help">
Drag the gladiator with the mouse, or use the buttons (arrow keys move too, hold shift for 10).
Joints move a whole limb; left-side numbers are mirrored onto the right side.
</div>
<div class="row">
<div class="btn" onclick=@Save>save</div>
<div class="btn" onclick=@(() => GameManager.Instance.Screen = GameScreen.Title)>back</div>
</div>
<div class="saved">@Saved</div>
</div>
<div class="drag-area" onmousedown=@StartDrag onmouseup=@(() => dragging = false)></div>
</root>
@code
{
record CatDef( string Name, Func<List<string>> Keys );
static List<string> Range( string prefix, int count ) => Enumerable.Range( 1, count ).Select( i => $"{prefix}.{i}" ).ToList();
static readonly CatDef[] Cats =
{
new( "body", () => new() { "body.head", "body.torso", "body.uarm", "body.farm", "body.thigh", "body.shin", "body.foot" } ),
new( "joints", () => new() { "joint.root", "joint.neck", "joint.shoulder", "joint.elbow", "joint.hand", "joint.shield", "joint.hip", "joint.knee", "joint.ankle" } ),
new( "face", () => new() { "face" } ),
new( "race", () => new() { "race.elf", "race.orc", "race.devil", "race.undead" } ),
new( "hair", () => Range( "hair", Catalog.HairCount ) ),
new( "beard", () => Range( "beard", Catalog.BeardCount ) ),
new( "helmets", () => Range( "helm", Catalog.Gear( GearSlot.Helm ).Length ) ),
new( "chest", () => Range( "chest", Catalog.Gear( GearSlot.Chest ).Length ) ),
new( "shoulders", () => Range( "shoulder", Catalog.Gear( GearSlot.Shoulders ).Length ) ),
new( "gauntlets", () => Range( "gauntlet", Catalog.Gear( GearSlot.Gauntlets ).Length ) ),
new( "thighs", () => Range( "thigh", Catalog.Gear( GearSlot.Thighs ).Length ) ),
new( "greaves", () => Range( "greave", Catalog.Gear( GearSlot.Greaves ).Length ) ),
new( "boots", () => Range( "boot", Catalog.Gear( GearSlot.Boots ).Length ) ),
new( "shields", () => Range( "shield", Catalog.Gear( GearSlot.Shield ).Length ) ),
new( "weapons", () => Range( "weapon", Catalog.Weapons.Length - 1 ) ),
};
static readonly string[] Anims = { "idle", "walk", "quick", "normal", "power", "taunt", "rest", "win", "chained" };
string Cat = "body";
int Index;
string Anim = "idle";
string Saved;
bool dragging;
Vector2 dragStart;
FitEntry dragFrom;
List<string> Keys => Cats.First( c => c.Name == Cat ).Keys();
string Key => Keys[Math.Clamp( Index, 0, Keys.Count - 1 )];
FitEntry E => Fit.Get( Key );
int Number => int.TryParse( Key.Split( '.' ).Last(), out var n ) ? n : 0;
void Step( int d )
{
Index = ((Index + d) % Keys.Count + Keys.Count) % Keys.Count;
StateHasChanged();
}
void Edit( FitEntry e )
{
Fit.Set( Key, e );
Saved = null;
StateHasChanged();
}
void Move( string what, float by )
{
var e = E;
Edit( what switch
{
"x" => e with { X = e.X + by },
"y" => e with { Y = e.Y + by },
"s" => e with { Scale = MathF.Max( 0.1f, e.Scale + by ) },
_ => e with { Turn = e.Turn + by }
} );
}
record Control( string Label, string What, float Step, Func<FitEntry, string> Value );
static readonly Control[] Controls =
{
new( "left/right", "x", 1f, e => e.X.ToString( "0.0" ) ),
new( "up/down", "y", 1f, e => e.Y.ToString( "0.0" ) ),
new( "size", "s", 0.02f, e => e.Scale.ToString( "0.00" ) ),
new( "turn", "t", 1f, e => e.Turn.ToString( "0" ) ),
};
void Save()
{
Fit.Save();
Saved = $"Saved to {Fit.FileName} in the game's data folder.";
StateHasChanged();
}
/// <summary>The gladiator wearing whatever you're fitting.</summary>
Look Preview
{
get
{
var l = Profile.Current?.Look ?? Look.Default;
if ( Cat is "body" or "joints" or "face" )
{
foreach ( var s in Catalog.Slots ) l.Set( s, -1 );
l.Weapon = 1;
return l;
}
int n = Number;
switch ( Cat )
{
case "race": l.Race = Array.FindIndex( Catalog.Races, r => r.Name.ToLower() == Key.Split( '.' ).Last() ); l.Skin = 0; break;
case "hair": l.Hair = n; l.Helm = -1; break;
case "beard": l.Beard = n; break;
case "helmets": l.Helm = n - 1; break;
case "chest": l.Chest = n - 1; break;
case "shoulders": l.Shoulders = n - 1; break;
case "gauntlets": l.Gauntlets = n - 1; break;
case "thighs": l.Thighs = n - 1; break;
case "greaves": l.Greaves = n - 1; break;
case "boots": l.Boots = n - 1; break;
case "shields": l.Shield = n - 1; break;
case "weapons": l.Weapon = n; break;
}
return l;
}
}
void StartDrag()
{
dragging = true;
dragStart = Mouse.Position;
dragFrom = E;
}
/// <summary>Rig pixels per screen pixel at the gladiator, from the 3D camera.</summary>
float RigPerScreenPixel()
{
var w = World3D.Instance;
var cam = Scene?.Camera;
if ( !w.IsValid() || !cam.IsValid() || !w.Hero.IsValid() ) return 1f;
var feet = w.Hero.Feet;
var right = cam.WorldRotation.Right;
var a = cam.PointToScreenPixels( feet );
var b = cam.PointToScreenPixels( feet + right * (World3D.U * 100f) );
float d = (b - a).Length;
return d > 0.01f ? 100f / d : 1f;
}
public override void Tick()
{
base.Tick();
var w = World3D.Instance;
if ( w.IsValid() )
{
w.HeroLook = Preview;
w.HeroAnim = Anim;
w.HeroMood = Mood.Neutral;
}
if ( dragging )
{
var delta = (Mouse.Position - dragStart) * RigPerScreenPixel();
Fit.Set( Key, dragFrom with { X = dragFrom.X + delta.x, Y = dragFrom.Y + delta.y } );
StateHasChanged();
}
float step = Input.Keyboard.Down( "shift" ) ? 10f : 1f;
if ( Input.Keyboard.Pressed( "leftarrow" ) ) Move( "x", -step );
if ( Input.Keyboard.Pressed( "rightarrow" ) ) Move( "x", step );
if ( Input.Keyboard.Pressed( "uparrow" ) ) Move( "y", -step );
if ( Input.Keyboard.Pressed( "downarrow" ) ) Move( "y", step );
}
protected override int BuildHash() => HashCode.Combine( Cat, Index, Anim, Saved, Fit.Version );
}