{"TotalCount":46,"Files":[{"Ident":"farten.shoes_and_daggers","Path":"Arena/EnemyMaker.cs","FileName":"EnemyMaker.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":443203,"IsPrivate":false,"Code":"using System;\n\n/// \u003Csummary\u003E\n/// Builds an opponent. Duels: a ragged fellow prisoner with next to no gear. Arena: a gladiator around\n/// your level with gear their level allows.\n/// \u003C/summary\u003E\npublic static class EnemyMaker\n{\n\t// stat weights: Str, Atk, Def, Vit, Agi, Sta, Cha, Mag\n\tstatic readonly (string Title, float[] Weights)[] Styles =\n\t{\n\t\t(\u0022Brute\u0022,    new[] { 3f, 1.5f, 1f, 2.5f, 0.5f, 1.5f, 0.5f, 0.2f }),\n\t\t(\u0022Duelist\u0022,  new[] { 1.5f, 3f, 1f, 1.5f, 2.5f, 1.5f, 0.5f, 0.3f }),\n\t\t(\u0022Showman\u0022,  new[] { 1.5f, 1.5f, 0.5f, 1.5f, 1.5f, 2f, 3f, 0.3f }),\n\t\t(\u0022Tank\u0022,     new[] { 1.5f, 1f, 3f, 3f, 0.5f, 2f, 0.5f, 0.2f }),\n\t\t(\u0022Berserker\u0022,new[] { 3f, 2f, 0.5f, 1.5f, 1.5f, 2.5f, 0.5f, 0.2f }),\n\t\t(\u0022Mage\u0022,     new[] { 1f, 1.5f, 1f, 1.5f, 1.5f, 1.5f, 1f, 3f }),\n\t};\n\n\tpublic static void Build( Gladiator g, Profile player, FightMode mode )\n\t{\n\t\tvar rng = Random.Shared;\n\t\tbool duel = mode == FightMode.Duel;\n\t\tint level = duel ? Math.Max( 1, player.Level - rng.Int( 0, 1 ) ) : Math.Max( 1, player.Level \u002B rng.Int( -1, 1 ) );\n\t\tg.Level = level;\n\t\tg.DisplayName = rng.FromArray( duel ? Catalog.PrisonerNames : Catalog.EnemyNames );\n\n\t\t// a bit less than your budget in the dungeon, the same as you in the arena\n\t\tint budget = Profile.StatCount * Profile.StartStat \u002B Profile.StartPoints \u002B (level - 1) * 5 - (duel ? 2 : 0);\n\t\tvar style = duel ? Styles[rng.Int( 0, 4 )] : rng.FromArray( Styles );\n\t\tg.Title = duel ? rng.FromArray( Catalog.PrisonerTitles ) : $\u0022Level {level} {style.Title.ToLower()}\u0022;\n\t\tvar w = style.Weights;\n\t\tvar stats = new int[Profile.StatCount];\n\t\tfor ( int i = 0; i \u003C stats.Length; i\u002B\u002B ) stats[i] = Profile.StartStat;\n\t\tint left = budget - stats.Length * Profile.StartStat;\n\t\tfloat total = w.Sum();\n\t\twhile ( left \u003E 0 )\n\t\t{\n\t\t\tfloat r = rng.Float( 0, total );\n\t\t\tint pick = 0;\n\t\t\twhile ( pick \u003C stats.Length - 1 \u0026\u0026 r \u003E w[pick] ) { r -= w[pick]; pick\u002B\u002B; }\n\t\t\tstats[pick]\u002B\u002B;\n\t\t\tleft--;\n\t\t}\n\n\t\tg.Strength = stats[0]; g.Attack = stats[1]; g.Defence = stats[2]; g.Vitality = stats[3];\n\t\tg.Agility = stats[4]; g.Stamina = stats[5]; g.Charisma = stats[6]; g.Magicka = stats[7];\n\n\t\tvar look = Look.Default;\n\t\tlook.Race = rng.Float() \u003C 0.55f ? 0 : rng.Int( 1, Catalog.Races.Length - 1 );\n\t\tlook.Skin = rng.Int( 0, Catalog.Races[look.Race].Skins.Length - 1 );\n\t\tlook.Hair = rng.Float() \u003C 0.15f ? 0 : rng.Int( 1, Catalog.HairCount );\n\t\tlook.Beard = rng.Float() \u003C 0.5f ? 0 : rng.Int( 1, Catalog.BeardCount );\n\t\tlook.HairColour = rng.Int( 0, Catalog.HairColours.Length - 1 );\n\t\tlook.Cloth = rng.Int( 0, Catalog.ClothColours.Length - 1 );\n\n\t\tif ( duel )\n\t\t{\n\t\t\t// prisoners: a cheap weapon and a rag or two, nothing more\n\t\t\tlook.Weapon = rng.Int( 1, 5 );\n\t\t\tlook.Chest = rng.Float() \u003C 0.8f ? 0 : -1;\n\t\t\tif ( rng.Float() \u003C 0.4f ) look.Helm = rng.Int( 0, 1 );\n\t\t\tif ( rng.Float() \u003C 0.25f ) look.Boots = 0;\n\t\t\tif ( look.Chest \u003C 0 \u0026\u0026 look.Helm \u003C 0 ) look.Chest = 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// best weapon their strength allows, sometimes one worse\n\t\t\tint weapon = Catalog.Weapons.Where( x =\u003E x.Strength \u003C= g.Strength ).Max( x =\u003E x.Id );\n\t\t\tif ( weapon \u003E 1 \u0026\u0026 rng.Float() \u003C 0.3f ) weapon--;\n\t\t\tlook.Weapon = Math.Max( 1, weapon );\n\n\t\t\t// each slot: something their level allows, with a few gaps\n\t\t\tforeach ( var slot in Catalog.Slots )\n\t\t\t{\n\t\t\t\tvar allowed = Catalog.Gear( slot ).Where( x =\u003E x.Level \u003C= level ).ToList();\n\t\t\t\tif ( allowed.Count == 0 || rng.Float() \u003C (slot == GearSlot.Shield ? 0.45f : 0.2f) ) continue;\n\t\t\t\tint pick = allowed.Count - 1 - Math.Min( allowed.Count - 1, rng.Int( 0, 2 ) );\n\t\t\t\tlook.Set( slot, allowed[pick].Index );\n\t\t\t}\n\n\t\t\t// magic weapons and spells show up as they get stronger\n\t\t\tif ( level \u003E= 5 \u0026\u0026 rng.Float() \u003C 0.25f \u002B level * 0.02f )\n\t\t\t{\n\t\t\t\tg.Enchant = (Enchant)rng.Int( 1, 4 );\n\t\t\t\tg.EnchantTier = level \u003E= 16 ? rng.Int( 2, 3 ) : level \u003E= 10 ? rng.Int( 1, 2 ) : 1;\n\t\t\t}\n\t\t\tforeach ( var s in Catalog.Spells )\n\t\t\t\tif ( g.Magicka \u003E= s.MinMagicka \u0026\u0026 rng.Float() \u003C 0.5f ) g.SpellBits |= 1 \u003C\u003C (int)s.Id;\n\t\t\tif ( level \u003E= 4 \u0026\u0026 rng.Float() \u003C 0.4f ) g.HealthPotions = 1;\n\t\t}\n\n\t\tg.SetLook( look );\n\t}\n}\n"},{"Ident":"farten.shoes_and_daggers","Path":"Puppet/Puppet.cs","FileName":"Puppet.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":443203,"IsPrivate":false,"Code":"// The old flat UI puppet is gone: every gladiator is now drawn in 3D by World/SpritePuppet.cs.\n// This file is intentionally empty and can be deleted.\n"},{"Ident":"farten.shoes_and_daggers","Path":"UI/GameHud.razor","FileName":"GameHud.razor","PackageType":"game","CodeKind":"Game","AssetVersionId":443203,"IsPrivate":false,"Code":"@using System;\n@using Sandbox;\n@using Sandbox.UI;\n@inherits PanelComponent\n\n@* Every place is a lit 3D scene (World3D); the screens are just the Flash-game UI drawn over it. *@\n\u003Croot\u003E\n\t\u003Cdiv class=\u0022stage\u0022\u003E\n\t\t@if ( Gm.IsValid() )\n\t\t{\n\t\t\tvar s = Gm.Screen;\n\t\t\tif ( s == GameScreen.Title ) { \u003CTitleScreen\u003E\u003C/TitleScreen\u003E }\n\t\t\telse if ( s == GameScreen.Create ) { \u003CCreateScreen\u003E\u003C/CreateScreen\u003E }\n\t\t\telse if ( s == GameScreen.Intro ) { \u003CIntroScreen\u003E\u003C/IntroScreen\u003E }\n\t\t\telse if ( s == GameScreen.Town ) { \u003CTownScreen\u003E\u003C/TownScreen\u003E }\n\t\t\telse if ( s == GameScreen.Weaponsmith ) { \u003CShopScreen Armoury=@false\u003E\u003C/ShopScreen\u003E }\n\t\t\telse if ( s == GameScreen.Armoury ) { \u003CShopScreen Armoury=@true\u003E\u003C/ShopScreen\u003E }\n\t\t\telse if ( s == GameScreen.Magic ) { \u003CMagicScreen\u003E\u003C/MagicScreen\u003E }\n\t\t\telse if ( s == GameScreen.Church ) { \u003CChurchScreen\u003E\u003C/ChurchScreen\u003E }\n\t\t\telse if ( s == GameScreen.Stats ) { \u003CStatsScreen\u003E\u003C/StatsScreen\u003E }\n\t\t\telse if ( s == GameScreen.Arena ) { \u003CArenaView\u003E\u003C/ArenaView\u003E\u003CFightHud\u003E\u003C/FightHud\u003E }\n\t\t\telse if ( s == GameScreen.Fitting ) { \u003CFittingScreen\u003E\u003C/FittingScreen\u003E }\n\t\t}\n\t\u003C/div\u003E\n\u003C/root\u003E\n\n@code\n{\n\tGameManager Gm =\u003E GameManager.Instance;\n\n\tprotected override void OnUpdate()\n\t{\n\t\t// draw everything on a fixed 1920x1080 stage, scaled to fit any window\n\t\tvar sp = GetComponent\u003CScreenPanel\u003E();\n\t\tif ( sp.IsValid() \u0026\u0026 Screen.Width \u003E 0 \u0026\u0026 Screen.Height \u003E 0 )\n\t\t{\n\t\t\tsp.AutoScreenScale = false;\n\t\t\tsp.Scale = MathF.Min( Screen.Width / 1920f, Screen.Height / 1080f );\n\t\t}\n\t}\n\n\tprotected override int BuildHash() =\u003E HashCode.Combine( Gm?.Screen );\n}\n"},{"Ident":"farten.shoes_and_daggers","Path":"UI/StatsList.razor","FileName":"StatsList.razor","PackageType":"game","CodeKind":"Game","AssetVersionId":443203,"IsPrivate":false,"Code":"@using System;\n@using Sandbox;\n@using Sandbox.UI;\n\n@* The stat list with a \u002B per stat (Flash-game order); hover a stat for what it does. *@\n\u003Croot\u003E\n\t@if ( P is not null )\n\t{\n\t\t@foreach ( var idx in Profile.StatOrder )\n\t\t{\n\t\t\tvar i = idx;\n\t\t\t\u003Cdiv class=\u0022stat-line\u0022 onmouseover=@(() =\u003E { Hover = i; StateHasChanged(); }) onmouseout=@(() =\u003E { if ( Hover == i ) { Hover = -1; StateHasChanged(); } })\u003E\n\t\t\t\t\u003Cdiv class=\u0022sas-label stat-name\u0022\u003E@Profile.StatNames[i].ToLower()\u003C/div\u003E\n\t\t\t\t\u003Cdiv class=\u0022stat-value\u0022\u003E@P.GetStat( i )\u003C/div\u003E\n\t\t\t\t\u003Cdiv class=\u0022sas-pm plus @(P.StatPoints \u003E 0 ? \u0022\u0022 : \u0022off\u0022)\u0022 onclick=@(() =\u003E Spend( i ))\u003E\u003C/div\u003E\n\t\t\t\u003C/div\u003E\n\t\t}\n\t\t\u003Cdiv class=\u0022stat-help\u0022\u003E@(Hover \u003E= 0 ? Profile.StatHelp[Hover] : \u0022\u0022)\u003C/div\u003E\n\t}\n\u003C/root\u003E\n\n@code\n{\n\tProfile P =\u003E Profile.Current;\n\tint Hover = -1;\n\n\tvoid Spend( int idx )\n\t{\n\t\tGameManager.Instance?.SpendStat( idx );\n\t\tStateHasChanged();\n\t}\n\n\tprotected override int BuildHash()\n\t{\n\t\tif ( P is null ) return 0;\n\t\treturn HashCode.Combine( P.StatPoints, P.Strength, P.Attack, P.Defence, P.Vitality, P.Agility, P.Stamina, HashCode.Combine( P.Charisma, P.Magicka, Hover ) );\n\t}\n}\n"},{"Ident":"farten.shoes_and_daggers","Path":"World/Arena3D.cs","FileName":"Arena3D.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":443203,"IsPrivate":false,"Code":"// Replaced by World/World3D.cs (every place is a 3D scene now, not just the arena).\n// This file is intentionally empty and can be deleted.\n"},{"Ident":"farten.shoes_and_daggers","Path":"Flash/Core/Player.cs","FileName":"Player.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":443203,"IsPrivate":false,"Code":"using System;\nusing System.Collections.Generic;\n\nnamespace Flash;\n\n/// \u003Csummary\u003EWhat the engine needs from whoever hosts it (s\u0026amp;box, or the test harness).\u003C/summary\u003E\npublic interface IFlashHost\n{\n\tvoid Trace( string message );\n\t/// \u003Csummary\u003EPlays a sound; returns a handle (or null).\u003C/summary\u003E\n\tobject PlaySound( SoundDef sound, double volume, double pan, int loops, double offsetSeconds );\n\tvoid StopSound( object handle );\n\tbool IsSoundPlaying( object handle );\n\tdouble SoundPosition( object handle );\n\tvoid SetSoundVolume( object handle, double volume, double pan );\n\tstring LoadShared( string name );\n\tvoid SaveShared( string name, string data );\n\tvoid GetUrl( string url, string window );\n\tvoid SetFullScreen( bool on );\n}\n\n/// \u003Csummary\u003EOne thing to draw, in stage coordinates.\u003C/summary\u003E\npublic sealed class RenderItem\n{\n\tpublic DisplayObject Obj;\n\tpublic Matrix World;\n\tpublic ColorTransform Cx;\n\tpublic List\u003CFilter\u003E Filters; // own \u002B inherited\n\tpublic double[] Clip; // stage-space rect x0,y0,x1,y1 or null (bounding box of the masks)\n\tpublic List\u003CRenderItem\u003E Masks; // mask shapes (stage space) that clip this item, or null\n\tpublic int Index;\n}\n\n/// \u003Csummary\u003ERuns a SWF: timelines, ActionScript, input, sound.\u003C/summary\u003E\npublic sealed class Player\n{\n\tpublic readonly Library Lib;\n\tpublic readonly Avm1 Vm;\n\tpublic MovieClip Root;\n\tpublic IFlashHost Host;\n\tpublic double TimeMs;\n\tpublic long FrameNumber;\n\tdouble accum;\n\n\tMovieClip execHead;\n\treadonly List\u003C(DisplayObject target, byte[] code, AsFunction fn, Value thisV, Value[] args)\u003E queue = new();\n\tint instanceCounter;\n\tpublic readonly Dictionary\u003Cstring, AsFunction\u003E RegisteredClasses = new();\n\n\tpublic Player( Library lib, IFlashHost host )\n\t{\n\t\tLib = lib;\n\t\tHost = host;\n\t\tVm = new Avm1( this ) { SwfVersion = lib.Version };\n\t}\n\n\tpublic string NextInstanceName() =\u003E \u0022instance\u0022 \u002B (\u002B\u002BinstanceCounter);\n\n\t/// \u003Csummary\u003ELayout tweaks by StablePath (from the editor).\u003C/summary\u003E\n\tpublic readonly Dictionary\u003Cstring, LayoutAdjust\u003E Layout = new();\n\n\tpublic void ApplyLayout( DisplayObject d )\n\t{\n\t\tif ( Layout.Count == 0 ) { d.Adjust = null; return; }\n\t\td.Adjust = Layout.TryGetValue( d.StablePath, out var a ) ? a : null;\n\t}\n\n\t/// \u003Csummary\u003ERe-match every object against the layout table (after loading or editing).\u003C/summary\u003E\n\tpublic void ReapplyLayout()\n\t{\n\t\tif ( Root == null ) return;\n\t\tvoid Walk( DisplayObject d )\n\t\t{\n\t\t\tif ( d != Root ) ApplyLayout( d );\n\t\t\tif ( d is MovieClip mc ) foreach ( var c in mc.Children ) Walk( c );\n\t\t\tif ( d is ButtonObj b ) foreach ( var l in b.StateChildren.Values ) foreach ( var c in l ) Walk( c );\n\t\t}\n\t\tWalk( Root );\n\t}\n\n\t// ======================================================================= lifecycle\n\n\tpublic void Start()\n\t{\n\t\tRoot = new MovieClip( this, Lib.Main ) { Name = \u0022\u0022 };\n\t\tRoot.Def = null;\n\t\t// DoInitAction blocks define classes/prototypes before anything else runs\n\t\tforeach ( var ia in Lib.Init ) Vm.RunScript( Root, ia.Code );\n\t\tConstruct( Root );\n\t\tRunQueue();\n\t\tSyncTexts( Root );\n\t}\n\n\t/// \u003Csummary\u003EAdvance by real time; runs as many SWF frames as are due.\u003C/summary\u003E\n\tpublic void Tick( double dtSeconds )\n\t{\n\t\tTimeMs \u002B= dtSeconds * 1000.0;\n\t\tRunIntervals();\n\t\taccum \u002B= dtSeconds;\n\t\tdouble frameTime = 1.0 / Lib.FrameRate;\n\t\tint n = 0;\n\t\twhile ( accum \u003E= frameTime \u0026\u0026 n \u003C 4 )\n\t\t{\n\t\t\taccum -= frameTime;\n\t\t\tFrame();\n\t\t\tn\u002B\u002B;\n\t\t}\n\t\tif ( accum \u003E frameTime * 4 ) accum = 0;\n\t}\n\n\t/// \u003Csummary\u003EOne SWF frame.\u003C/summary\u003E\n\tpublic void Frame()\n\t{\n\t\tFrameNumber\u002B\u002B;\n\t\t// newest clips first (Flash/Ruffle AVM1 execution order)\n\t\tvar list = new List\u003CMovieClip\u003E();\n\t\tMovieClip prev = null;\n\t\tfor ( var c = execHead; c != null; c = c.NextExec )\n\t\t{\n\t\t\tif ( c.Removed )\n\t\t\t{\n\t\t\t\tif ( prev == null ) execHead = c.NextExec; else prev.NextExec = c.NextExec;\n\t\t\t\tc.InExecList = false;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlist.Add( c );\n\t\t\tprev = c;\n\t\t}\n\t\tforeach ( var c in list )\n\t\t{\n\t\t\tif ( c.Removed ) continue;\n\t\t\tif ( !c.Initialized ) { DispatchEvent( c, \u0022Load\u0022 ); c.Initialized = true; }\n\t\t\telse DispatchEvent( c, \u0022EnterFrame\u0022 );\n\t\t\tif ( c.Playing \u0026\u0026 !c.Removed ) c.AdvanceFrame();\n\t\t}\n\t\tBuiltins.UpdateSounds( this );\n\t\tRunQueue();\n\t\tUpdateDrag();\n\t\tSyncTexts( Root );\n\t\tRecheckHover();\n\t}\n\n\t// ======================================================================= creation\n\n\tpublic DisplayObject Instantiate( int charId, MovieClip parent )\n\t{\n\t\tvar def = Lib.Get( charId );\n\t\tswitch ( def )\n\t\t{\n\t\t\tcase SpriteDef sd:\n\t\t\t\t{\n\t\t\t\t\tvar mc = new MovieClip( this, sd );\n\t\t\t\t\tif ( sd.ExportName != null \u0026\u0026 RegisteredClasses.TryGetValue( sd.ExportName, out var cls ) )\n\t\t\t\t\t{\n\t\t\t\t\t\tmc.RegisteredClass = cls;\n\t\t\t\t\t\tvar proto = cls.Get( \u0022prototype\u0022, Vm ).AsObj;\n\t\t\t\t\t\tif ( proto != null ) mc.Proto = proto;\n\t\t\t\t\t}\n\t\t\t\t\treturn mc;\n\t\t\t\t}\n\t\t\tcase ShapeDef s: return new ShapeObj( this, s );\n\t\t\tcase MorphDef m: return new MorphObj( this, m );\n\t\t\tcase StaticTextDef t: return new StaticTextObj( this, t );\n\t\t\tcase EditTextDef e: return new EditText( this, e );\n\t\t\tcase ButtonDef b: return new ButtonObj( this, b );\n\t\t}\n\t\treturn null;\n\t}\n\n\t/// \u003Csummary\u003EA newly placed/attached object starts running (load event \u002B first frame).\u003C/summary\u003E\n\tpublic void Construct( DisplayObject d, AsObject initObject = null, bool byScript = false )\n\t{\n\t\tif ( d is not MovieClip mc ) return;\n\t\tif ( !mc.InExecList )\n\t\t{\n\t\t\tmc.NextExec = execHead;\n\t\t\texecHead = mc;\n\t\t\tmc.InExecList = true;\n\t\t}\n\t\t// a registered AS2 class\u0027s constructor runs first for timeline-placed clips\n\t\tif ( mc.RegisteredClass != null \u0026\u0026 !byScript ) QueueCall( mc.RegisteredClass, Value.From( mc ), Array.Empty\u003CValue\u003E() );\n\t\t// Load event fires before the first frame\u0027s scripts\n\t\tDispatchEvent( mc, \u0022Load\u0022 );\n\t\tmc.Initialized = true;\n\t\tif ( mc.Playing ) mc.Seek( 1, true );\n\t\telse if ( mc.CurrentFrame == 0 ) mc.Seek( 1, true );\n\n\t\tif ( initObject != null )\n\t\t{\n\t\t\tvar keys = initObject.EnumerableKeys();\n\t\t\tfor ( int i = keys.Count - 1; i \u003E= 0; i-- ) mc.Set( keys[i], initObject.Get( keys[i], Vm ), Vm );\n\t\t}\n\t\tif ( mc.RegisteredClass != null \u0026\u0026 byScript ) SafeCall( mc.RegisteredClass, Value.From( mc ), Array.Empty\u003CValue\u003E() );\n\t}\n\n\tpublic void Unload( DisplayObject d, bool recursiveOnly = false )\n\t{\n\t\tif ( d.Removed ) return;\n\t\tif ( d is MovieClip mc ) DispatchEvent( mc, \u0022Unload\u0022 );\n\t\td.OnRemoved();\n\t\tif ( ReferenceEquals( hover, d ) ) hover = null;\n\t\tif ( ReferenceEquals( pressed, d ) ) pressed = null;\n\t\tif ( ReferenceEquals( dragTarget, d ) ) dragTarget = null;\n\t\tfocusedText = focusedText != null \u0026\u0026 focusedText.Removed ? null : focusedText;\n\t}\n\n\t// ======================================================================= actions\n\n\tpublic void QueueAction( DisplayObject target, byte[] code ) =\u003E queue.Add( (target, code, null, default, null) );\n\n\tpublic void QueueCall( AsFunction fn, Value thisV, Value[] args ) =\u003E queue.Add( (null, null, fn, thisV, args) );\n\n\tpublic void RunQueue()\n\t{\n\t\tint guard = 0;\n\t\twhile ( queue.Count \u003E 0 \u0026\u0026 guard\u002B\u002B \u003C 100000 )\n\t\t{\n\t\t\tvar item = queue[0];\n\t\t\tqueue.RemoveAt( 0 );\n\t\t\tif ( item.code != null )\n\t\t\t{\n\t\t\t\tif ( item.target is { Removed: true } ) continue;\n\t\t\t\tVm.RunScript( item.target, item.code );\n\t\t\t}\n\t\t\telse if ( item.fn != null )\n\t\t\t{\n\t\t\t\tSafeCall( item.fn, item.thisV, item.args );\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic Value SafeCall( AsFunction fn, Value thisV, Value[] args )\n\t{\n\t\ttry { return Vm.CallFunction( fn, thisV, args ); }\n\t\tcatch ( AsThrow t ) { Vm.Trace( \u0022Uncaught exception: \u0022 \u002B t.Thrown.ToStr( Vm ) ); }\n\t\tcatch ( ScriptAbort a ) { Vm.Trace( \u0022Script aborted: \u0022 \u002B a.Message ); }\n\t\treturn Value.Undefined;\n\t}\n\n\t/// \u003Csummary\u003EClip event (onClipEvent handlers on the placement \u002B the onX method).\u003C/summary\u003E\n\tpublic void DispatchEvent( MovieClip mc, string ev )\n\t{\n\t\tif ( mc.ClipActions != null )\n\t\t\tforeach ( var ca in mc.ClipActions )\n\t\t\t\tif ( ca.Events.Contains( ev ) ) QueueAction( mc, ca.Code );\n\t\tstring method = ev switch\n\t\t{\n\t\t\t\u0022Load\u0022 =\u003E \u0022onLoad\u0022, \u0022EnterFrame\u0022 =\u003E \u0022onEnterFrame\u0022, \u0022Unload\u0022 =\u003E \u0022onUnload\u0022, \u0022MouseDown\u0022 =\u003E \u0022onMouseDown\u0022,\n\t\t\t\u0022MouseUp\u0022 =\u003E \u0022onMouseUp\u0022, \u0022MouseMove\u0022 =\u003E \u0022onMouseMove\u0022, \u0022KeyDown\u0022 =\u003E \u0022onKeyDown\u0022, \u0022KeyUp\u0022 =\u003E \u0022onKeyUp\u0022, \u0022Data\u0022 =\u003E \u0022onData\u0022,\n\t\t\t_ =\u003E null\n\t\t};\n\t\tif ( method == null ) return;\n\t\tvar f = mc.Get( method, Vm ).AsObj as AsFunction;\n\t\tif ( f != null ) QueueCall( f, Value.From( mc ), Array.Empty\u003CValue\u003E() );\n\t}\n\n\tvoid BroadcastClipEvent( MovieClip root, string ev )\n\t{\n\t\tfor ( var c = execHead; c != null; c = c.NextExec )\n\t\t\tif ( !c.Removed ) DispatchEvent( c, ev );\n\t}\n\n\t// ======================================================================= text fields\n\n\tvoid SyncTexts( MovieClip mc )\n\t{\n\t\tforeach ( var c in mc.Children )\n\t\t{\n\t\t\tif ( c is EditText et ) et.Sync();\n\t\t\telse if ( c is MovieClip m ) SyncTexts( m );\n\t\t\telse if ( c is ButtonObj b )\n\t\t\t\tforeach ( var l in b.StateChildren.Values ) foreach ( var x in l ) if ( x is EditText bt ) { bt.Parent = null; }\n\t\t}\n\t}\n\n\tpublic EditText focusedText;\n\n\t// ======================================================================= mouse\n\n\tpublic double MouseX, MouseY;\n\tpublic bool MouseIsDown;\n\tDisplayObject hover, pressed;\n\tpublic DisplayObject Hovered =\u003E hover;\n\tpublic DisplayObject DropTarget;\n\tpublic bool HandCursor =\u003E hover is ButtonObj b ? b.UseHandCursor : hover is MovieClip m \u0026\u0026 m.UseHandCursor \u0026\u0026 hover != null;\n\n\tpublic void MouseMove( double x, double y )\n\t{\n\t\tMouseX = x; MouseY = y;\n\t\tBroadcastClipEvent( Root, \u0022MouseMove\u0022 );\n\t\tBroadcastListeners( \u0022Mouse\u0022, \u0022onMouseMove\u0022 );\n\t\tUpdateDrag();\n\t\tRecheckHover();\n\t\tRunQueue();\n\t}\n\n\tpublic void MouseDown()\n\t{\n\t\tMouseIsDown = true;\n\t\tBroadcastClipEvent( Root, \u0022MouseDown\u0022 );\n\t\tBroadcastListeners( \u0022Mouse\u0022, \u0022onMouseDown\u0022 );\n\t\tRecheckHover();\n\t\tvar target = hover;\n\t\tpressed = target;\n\t\tif ( target is ButtonObj b ) { b.State = \u0022d\u0022; RunButtonActions( b, \u0022press\u0022 ); }\n\t\telse if ( target is MovieClip mc ) CallHandler( mc, \u0022onPress\u0022 );\n\t\t// clicking an input field focuses it\n\t\tvar t = FindTextField( Root, MouseX, MouseY );\n\t\tfocusedText = t != null \u0026\u0026 t.Edit.Input ? t : null;\n\t\tRunQueue();\n\t}\n\n\tpublic void MouseUp()\n\t{\n\t\tMouseIsDown = false;\n\t\tBroadcastClipEvent( Root, \u0022MouseUp\u0022 );\n\t\tBroadcastListeners( \u0022Mouse\u0022, \u0022onMouseUp\u0022 );\n\t\tRecheckHover();\n\t\tvar p = pressed;\n\t\tpressed = null;\n\t\tif ( p != null \u0026\u0026 !p.Removed )\n\t\t{\n\t\t\tif ( ReferenceEquals( p, hover ) )\n\t\t\t{\n\t\t\t\tif ( p is ButtonObj b ) { b.State = \u0022o\u0022; RunButtonActions( b, \u0022release\u0022 ); }\n\t\t\t\telse if ( p is MovieClip mc ) CallHandler( mc, \u0022onRelease\u0022 );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif ( p is ButtonObj b ) { b.State = \u0022u\u0022; RunButtonActions( b, \u0022releaseOutside\u0022 ); }\n\t\t\t\telse if ( p is MovieClip mc ) CallHandler( mc, \u0022onReleaseOutside\u0022 );\n\t\t\t}\n\t\t}\n\t\tRunQueue();\n\t}\n\n\tvoid RecheckHover()\n\t{\n\t\tif ( Root == null ) return;\n\t\tvar now = FindInteractive( Root, MouseX, MouseY );\n\t\tif ( ReferenceEquals( now, hover ) ) return;\n\t\tvar old = hover;\n\t\thover = now;\n\t\tif ( old != null \u0026\u0026 !old.Removed )\n\t\t{\n\t\t\tif ( old is ButtonObj ob ) { ob.State = \u0022u\u0022; RunButtonActions( ob, MouseIsDown \u0026\u0026 ReferenceEquals( pressed, old ) ? \u0022dragOut\u0022 : \u0022rollOut\u0022 ); }\n\t\t\telse if ( old is MovieClip om ) CallHandler( om, MouseIsDown \u0026\u0026 ReferenceEquals( pressed, old ) ? \u0022onDragOut\u0022 : \u0022onRollOut\u0022 );\n\t\t}\n\t\tif ( now != null )\n\t\t{\n\t\t\tif ( now is ButtonObj nb ) { nb.State = MouseIsDown \u0026\u0026 ReferenceEquals( pressed, now ) ? \u0022d\u0022 : \u0022o\u0022; RunButtonActions( nb, MouseIsDown \u0026\u0026 ReferenceEquals( pressed, now ) ? \u0022dragOver\u0022 : \u0022rollOver\u0022 ); }\n\t\t\telse if ( now is MovieClip nm ) CallHandler( nm, MouseIsDown \u0026\u0026 ReferenceEquals( pressed, now ) ? \u0022onDragOver\u0022 : \u0022onRollOver\u0022 );\n\t\t}\n\t}\n\n\tDisplayObject FindInteractive( MovieClip mc, double x, double y )\n\t{\n\t\tif ( !mc.Visible || mc.Removed ) return null;\n\t\tfor ( int i = mc.Children.Count - 1; i \u003E= 0; i-- )\n\t\t{\n\t\t\tvar c = mc.Children[i];\n\t\t\tif ( c.Removed || !c.Visible || c.ClipDepth \u003E 0 || c.MaskFor != null ) continue;\n\t\t\tif ( c is ButtonObj b )\n\t\t\t{\n\t\t\t\tif ( b.Enabled \u0026\u0026 b.HitTestPoint( x, y, true ) ) return b;\n\t\t\t}\n\t\t\telse if ( c is MovieClip m )\n\t\t\t{\n\t\t\t\tif ( m.Enabled \u0026\u0026 m.HasButtonHandlers )\n\t\t\t\t{\n\t\t\t\t\tif ( m.HitTestPoint( x, y, true ) ) return m;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tvar r = FindInteractive( m, x, y );\n\t\t\t\tif ( r != null ) return r;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tEditText FindTextField( MovieClip mc, double x, double y )\n\t{\n\t\tfor ( int i = mc.Children.Count - 1; i \u003E= 0; i-- )\n\t\t{\n\t\t\tvar c = mc.Children[i];\n\t\t\tif ( c.Removed || !c.Visible ) continue;\n\t\t\tif ( c is EditText e \u0026\u0026 e.HitTestPoint( x, y, false ) ) return e;\n\t\t\tif ( c is MovieClip m ) { var r = FindTextField( m, x, y ); if ( r != null ) return r; }\n\t\t}\n\t\treturn null;\n\t}\n\n\tvoid RunButtonActions( ButtonObj b, string cond )\n\t{\n\t\tvar target = b.Parent ?? Root;\n\t\tforeach ( var a in b.Button.Actions )\n\t\t\tif ( a.On.Contains( cond ) ) QueueAction( target, a.Code );\n\t\t// AS2 handlers assigned by script: myButton.onRelease = function() {...}\n\t\tstring handler = cond switch\n\t\t{\n\t\t\t\u0022press\u0022 =\u003E \u0022onPress\u0022, \u0022release\u0022 =\u003E \u0022onRelease\u0022, \u0022releaseOutside\u0022 =\u003E \u0022onReleaseOutside\u0022, \u0022rollOver\u0022 =\u003E \u0022onRollOver\u0022,\n\t\t\t\u0022rollOut\u0022 =\u003E \u0022onRollOut\u0022, \u0022dragOut\u0022 =\u003E \u0022onDragOut\u0022, \u0022dragOver\u0022 =\u003E \u0022onDragOver\u0022, _ =\u003E null\n\t\t};\n\t\tif ( handler == null ) return;\n\t\tvar f = b.Get( handler, Vm ).AsObj as AsFunction;\n\t\tif ( f != null ) QueueCall( f, Value.From( b ), Array.Empty\u003CValue\u003E() );\n\t}\n\n\tvoid CallHandler( MovieClip mc, string name )\n\t{\n\t\tvar f = mc.Get( name, Vm ).AsObj as AsFunction;\n\t\tif ( f != null ) QueueCall( f, Value.From( mc ), Array.Empty\u003CValue\u003E() );\n\t}\n\n\t// ======================================================================= keyboard\n\n\tpublic readonly HashSet\u003Cint\u003E KeysDown = new();\n\tpublic int LastKey, LastAscii;\n\n\tpublic void KeyDown( int keyCode, int ascii )\n\t{\n\t\tKeysDown.Add( keyCode );\n\t\tLastKey = keyCode; LastAscii = ascii;\n\t\tBroadcastClipEvent( Root, \u0022KeyDown\u0022 );\n\t\tBroadcastListeners( \u0022Key\u0022, \u0022onKeyDown\u0022 );\n\t\tRunQueue();\n\t}\n\n\tpublic void KeyUp( int keyCode, int ascii )\n\t{\n\t\tKeysDown.Remove( keyCode );\n\t\tLastKey = keyCode; LastAscii = ascii;\n\t\tBroadcastClipEvent( Root, \u0022KeyUp\u0022 );\n\t\tBroadcastListeners( \u0022Key\u0022, \u0022onKeyUp\u0022 );\n\t\tRunQueue();\n\t}\n\n\tvoid BroadcastListeners( string globalName, string method )\n\t{\n\t\tvar obj = Vm.Global.Get( globalName, Vm );\n\t\tif ( !obj.IsObject ) return;\n\t\tvar listeners = obj.Obj.Get( \u0022_listeners\u0022, Vm ).AsObj as AsArray;\n\t\tif ( listeners == null ) return;\n\t\tforeach ( var l in listeners.Items.ToArray() )\n\t\t{\n\t\t\tvar f = Vm.GetMember( l, method ).AsObj as AsFunction;\n\t\t\tif ( f != null ) QueueCall( f, l, Array.Empty\u003CValue\u003E() );\n\t\t}\n\t}\n\n\t// ======================================================================= drag\n\n\tDisplayObject dragTarget;\n\tbool dragLock;\n\tdouble[] dragRect;\n\tdouble dragOffX, dragOffY;\n\n\tpublic void StartDrag( DisplayObject d, bool lockCenter, double[] rect )\n\t{\n\t\tdragTarget = d;\n\t\tdragLock = lockCenter;\n\t\tdragRect = rect;\n\t\tvar (px, py) = ParentMouse( d );\n\t\tdragOffX = lockCenter ? 0 : d.X - px;\n\t\tdragOffY = lockCenter ? 0 : d.Y - py;\n\t\tUpdateDrag();\n\t}\n\n\tpublic void StopDrag()\n\t{\n\t\tdragTarget = null;\n\t}\n\n\t(double, double) ParentMouse( DisplayObject d )\n\t{\n\t\tvar pm = d.Parent?.WorldMatrix ?? Matrix.Identity;\n\t\treturn pm.Inverse().Apply( MouseX, MouseY );\n\t}\n\n\tvoid UpdateDrag()\n\t{\n\t\tif ( dragTarget == null || dragTarget.Removed ) return;\n\t\tvar (px, py) = ParentMouse( dragTarget );\n\t\tdouble x = px \u002B dragOffX, y = py \u002B dragOffY;\n\t\tif ( dragRect != null )\n\t\t{\n\t\t\tx = Math.Clamp( x, Math.Min( dragRect[0], dragRect[2] ), Math.Max( dragRect[0], dragRect[2] ) );\n\t\t\ty = Math.Clamp( y, Math.Min( dragRect[1], dragRect[3] ), Math.Max( dragRect[1], dragRect[3] ) );\n\t\t}\n\t\tdragTarget.X = x; dragTarget.Y = y;\n\t}\n\n\t// ======================================================================= intervals\n\n\tsealed class Interval { public int Id; public AsFunction Fn; public Value This; public Value[] Args; public double Every, Next; public bool Once; }\n\treadonly List\u003CInterval\u003E intervals = new();\n\tint intervalId;\n\n\tpublic int SetInterval( AsFunction fn, Value thisV, double ms, Value[] args, bool once = false )\n\t{\n\t\tvar iv = new Interval { Id = \u002B\u002BintervalId, Fn = fn, This = thisV, Args = args, Every = Math.Max( 1, ms ), Next = TimeMs \u002B Math.Max( 1, ms ), Once = once };\n\t\tintervals.Add( iv );\n\t\treturn iv.Id;\n\t}\n\n\tpublic void ClearInterval( int id ) =\u003E intervals.RemoveAll( i =\u003E i.Id == id );\n\n\tvoid RunIntervals()\n\t{\n\t\tif ( intervals.Count == 0 ) return;\n\t\tforeach ( var iv in intervals.ToArray() )\n\t\t{\n\t\t\tif ( TimeMs \u003C iv.Next ) continue;\n\t\t\tiv.Next = TimeMs \u002B iv.Every;\n\t\t\tif ( iv.Once ) intervals.Remove( iv );\n\t\t\tSafeCall( iv.Fn, iv.This, iv.Args );\n\t\t\tRunQueue();\n\t\t}\n\t}\n\n\t// ======================================================================= sound / url\n\n\tpublic readonly List\u003C(SoundDef def, object handle, DisplayObject owner)\u003E TimelineSounds = new();\n\n\t/// \u003Csummary\u003EVolume set by a Sound object with no target (or _root): scales every sound, 0..1.\u003C/summary\u003E\n\tpublic double GlobalVolume = 1;\n\n\tpublic void SetGlobalVolume( double v )\n\t{\n\t\tGlobalVolume = Math.Clamp( v, 0, 1 );\n\t\tforeach ( var s in TimelineSounds ) Host?.SetSoundVolume( s.handle, GlobalVolume, 0 );\n\t\tBuiltins.RefreshSoundVolumes( this );\n\t}\n\n\tpublic void StartSound( int soundId, bool stop, int loops, DisplayObject owner, bool noMultiple = false )\n\t{\n\t\tif ( Lib.Get( soundId ) is not SoundDef sd ) return;\n\t\t// forget finished ones\n\t\tfor ( int i = TimelineSounds.Count - 1; i \u003E= 0; i-- )\n\t\t\tif ( Host == null || !Host.IsSoundPlaying( TimelineSounds[i].handle ) ) TimelineSounds.RemoveAt( i );\n\t\tif ( stop )\n\t\t{\n\t\t\tfor ( int i = TimelineSounds.Count - 1; i \u003E= 0; i-- )\n\t\t\t\tif ( TimelineSounds[i].def == sd ) { Host?.StopSound( TimelineSounds[i].handle ); TimelineSounds.RemoveAt( i ); }\n\t\t\treturn;\n\t\t}\n\t\tif ( noMultiple )\n\t\t\tforeach ( var t in TimelineSounds ) if ( t.def == sd ) return;\n\t\tvar h = Host?.PlaySound( sd, GlobalVolume, 0, Math.Max( 1, loops ), 0 );\n\t\tif ( h != null ) TimelineSounds.Add( (sd, h, owner) );\n\t}\n\n\tpublic void StopAllSounds()\n\t{\n\t\tforeach ( var s in TimelineSounds ) Host?.StopSound( s.handle );\n\t\tTimelineSounds.Clear();\n\t\tBuiltins.StopAllSoundObjects( this );\n\t}\n\n\tpublic void GetUrl( string url, string window )\n\t{\n\t\tif ( url.StartsWith( \u0022FSCommand:\u0022, StringComparison.OrdinalIgnoreCase ) )\n\t\t{\n\t\t\tvar cmd = url.Substring( 10 );\n\t\t\tif ( cmd == \u0022fullscreen\u0022 ) Host?.SetFullScreen( window == \u0022true\u0022 );\n\t\t\treturn;\n\t\t}\n\t\tHost?.GetUrl( url, window );\n\t}\n\n\t// ======================================================================= rendering\n\n\t/// \u003Csummary\u003EFlattens the display list into draw items, back to front.\u003C/summary\u003E\n\tpublic void CollectRender( List\u003CRenderItem\u003E output )\n\t{\n\t\toutput.Clear();\n\t\tif ( Root == null ) return;\n\t\tCollect( Root, Matrix.Identity, ColorTransform.Identity, null, null, output );\n\t\tfor ( int i = 0; i \u003C output.Count; i\u002B\u002B ) output[i].Index = i;\n\t}\n\n\tvoid Collect( DisplayObject d, Matrix parentM, ColorTransform parentCx, List\u003CFilter\u003E parentFilters, List\u003CRenderItem\u003E masks, List\u003CRenderItem\u003E output )\n\t{\n\t\tif ( d.Removed || !d.Visible || (d.Adjust != null \u0026\u0026 d.Adjust.Hidden) ) return;\n\t\tvar m = parentM.Mul( d.EffectiveMatrix );\n\t\tvar cx = parentCx.Mul( d.Cx );\n\t\tif ( cx.Am \u003C= 0 \u0026\u0026 cx.Aa \u003C= 0 ) return;\n\t\tvar filters = parentFilters;\n\t\tif ( d.Filters != null \u0026\u0026 d.Filters.Count \u003E 0 )\n\t\t{\n\t\t\tfilters = parentFilters == null ? new List\u003CFilter\u003E( d.Filters ) : new List\u003CFilter\u003E( parentFilters );\n\t\t\tif ( parentFilters != null ) filters.AddRange( d.Filters );\n\t\t}\n\t\tif ( d.MaskedBy != null \u0026\u0026 !d.MaskedBy.Removed )\n\t\t{\n\t\t\tvar mi = new List\u003CRenderItem\u003E();\n\t\t\tCollectMask( d.MaskedBy, d.MaskedBy.RenderParent?.WorldMatrix ?? Matrix.Identity, mi );\n\t\t\tmasks = masks == null ? mi : Concat( masks, mi );\n\t\t}\n\n\t\tswitch ( d )\n\t\t{\n\t\t\tcase MovieClip mc:\n\t\t\t\t{\n\t\t\t\t\tList\u003CRenderItem\u003E layerMask = null;\n\t\t\t\t\tint maskUntil = int.MinValue;\n\t\t\t\t\tforeach ( var c in mc.Children )\n\t\t\t\t\t{\n\t\t\t\t\t\tif ( c.Depth \u003E maskUntil ) { layerMask = null; maskUntil = int.MinValue; }\n\t\t\t\t\t\tif ( c.ClipDepth \u003E 0 )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlayerMask = new List\u003CRenderItem\u003E();\n\t\t\t\t\t\t\tCollectMask( c, m, layerMask );\n\t\t\t\t\t\t\tmaskUntil = c.TimelineDepth \u003E= 0 ? c.ClipDepth \u002B MovieClip.TimelineDepthOffset : c.ClipDepth;\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( c.MaskFor != null ) continue; // used as a setMask mask\n\t\t\t\t\t\tCollect( c, m, cx, filters, layerMask != null ? (masks == null ? layerMask : Concat( masks, layerMask )) : masks, output );\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase ButtonObj b:\n\t\t\t\t{\n\t\t\t\t\tvar list = b.Current;\n\t\t\t\t\tif ( list != null ) foreach ( var c in list ) Collect( c, m, cx, filters, masks, output );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\t{\n\t\t\t\t\tvar it = new RenderItem { Obj = d, World = m, Cx = cx, Filters = filters, Masks = masks };\n\t\t\t\t\tif ( masks != null ) it.Clip = MaskBounds( masks );\n\t\t\t\t\toutput.Add( it );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t}\n\t}\n\n\tstatic List\u003CRenderItem\u003E Concat( List\u003CRenderItem\u003E a, List\u003CRenderItem\u003E b ) { var r = new List\u003CRenderItem\u003E( a ); r.AddRange( b ); return r; }\n\n\t/// \u003Csummary\u003EThe shapes of a mask (visibility and color are ignored, as in Flash).\u003C/summary\u003E\n\tvoid CollectMask( DisplayObject d, Matrix parentM, List\u003CRenderItem\u003E output )\n\t{\n\t\tif ( d.Removed ) return;\n\t\tvar m = parentM.Mul( d.EffectiveMatrix );\n\t\tswitch ( d )\n\t\t{\n\t\t\tcase MovieClip mc: foreach ( var c in mc.Children ) if ( c.ClipDepth == 0 ) CollectMask( c, m, output ); break;\n\t\t\tcase ButtonObj b: if ( b.Current != null ) foreach ( var c in b.Current ) CollectMask( c, m, output ); break;\n\t\t\tdefault: output.Add( new RenderItem { Obj = d, World = m, Cx = ColorTransform.Identity } ); break;\n\t\t}\n\t}\n\n\tstatic double[] MaskBounds( List\u003CRenderItem\u003E masks )\n\t{\n\t\tdouble x0 = double.MaxValue, y0 = double.MaxValue, x1 = double.MinValue, y1 = double.MinValue;\n\t\tforeach ( var mi in masks )\n\t\t{\n\t\t\tvar b = mi.Obj.BoundsIn( mi.World );\n\t\t\tif ( b == null ) continue;\n\t\t\tx0 = Math.Min( x0, b.Value.x0 ); y0 = Math.Min( y0, b.Value.y0 ); x1 = Math.Max( x1, b.Value.x1 ); y1 = Math.Max( y1, b.Value.y1 );\n\t\t}\n\t\treturn x0 == double.MaxValue ? new double[] { 0, 0, 0, 0 } : new[] { x0, y0, x1, y1 };\n\t}\n\n\tstatic double[] Intersect( double[] a, double[] b )\n\t{\n\t\tif ( a == null ) return b;\n\t\tif ( b == null ) return a;\n\t\treturn new[] { Math.Max( a[0], b[0] ), Math.Max( a[1], b[1] ), Math.Min( a[2], b[2] ), Math.Min( a[3], b[3] ) };\n\t}\n}\n"},{"Ident":"farten.shoes_and_daggers","Path":"Flash/Sbox/FlashStage.cs","FileName":"FlashStage.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":443203,"IsPrivate":false,"Code":"using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Flash;\nusing Sandbox;\nusing Sandbox.UI;\nusing FMatrix = Flash.Matrix;\n\n/// \u003Csummary\u003E\n/// Runs Swords and Sandals 2 (the original SWF, decoded into Assets/flash) inside s\u0026amp;box.\n/// Put it on a GameObject with a ScreenPanel. The game keeps its original 640x420 shape inside a 4:3 frame.\n/// Press F2 in game to move things around (see FlashLayout).\n/// \u003C/summary\u003E\n[Title( \u0022Flash Stage\u0022 ), Category( \u0022Shoes and Daggers\u0022 ), Icon( \u0022videogame_asset\u0022 )]\npublic sealed class FlashStage : PanelComponent, IFlashHost\n{\n\t/// \u003Csummary\u003EFolder (inside Assets) with ss2.json, zooms.json, hitmasks.bin and the art.\u003C/summary\u003E\n\t[Property] public string DataPath { get; set; } = \u0022flash\u0022;\n\n\t/// \u003Csummary\u003EWidth:height of the frame the game sits in (black bars fill the rest).\u003C/summary\u003E\n\t[Property] public float FrameAspect { get; set; } = 4f / 3f;\n\n\t/// \u003Csummary\u003ESwap if images come out mirrored/rotated wrong (matrix order differs between engine versions).\u003C/summary\u003E\n\t[Property] public bool TransposeMatrix { get; set; }\n\n\t/// \u003Csummary\u003Es\u0026amp;box applies a transform matrix\u0027s translation in screen pixels; keep on unless things drift.\u003C/summary\u003E\n\t[Property] public bool ScaleMatrixTranslation { get; set; } = true;\n\n\t/// \u003Csummary\u003ELog ActionScript trace() output.\u003C/summary\u003E\n\t[Property] public bool ShowTraces { get; set; } = true;\n\n\t[Property, Range( 0, 1 )] public float MasterVolume { get; set; } = 1f;\n\t/// \u003Csummary\u003EWrite every sound start/stop to the console (for tracking down audio problems).\u003C/summary\u003E\n\t[Property] public bool LogSounds { get; set; } = true;\n\n\tpublic static FlashStage Instance { get; private set; }\n\tpublic Player Player { get; private set; }\n\tpublic Library Library { get; private set; }\n\n\tPanel stage;\n\treadonly Dictionary\u003CDisplayObject, Node\u003E nodes = new();\n\treadonly List\u003CRenderItem\u003E items = new();\n\treadonly Dictionary\u003Cstring, Texture\u003E textures = new();\n\tfloat scale = 1;\n\tfloat stageLeft, stageTop;\n\tVector2 lastMouse = new( -1, -1 );\n\tint frameStamp;\n\tpublic FlashEditor Editor { get; private set; }\n\n\tsealed class Node\n\t{\n\t\tpublic Panel Outer; // clip container (or the panel itself)\n\t\tpublic Panel Panel;\n\t\tpublic TextEntry Entry;\n\t\tpublic int Stamp;\n\t\tpublic string Image;\n\t\tpublic string Mode;\n\t\tpublic Texture Tex;\n\t\tpublic readonly TexCache Cache = new();\n\t\t// last values written to the panel\u0027s style (only write when something changed: style writes force re-layout)\n\t\tpublic bool HasGeo;\n\t\tpublic FMatrix LastM;\n\t\tpublic double LastW, LastH;\n\t\tpublic float LastScale = -1, LastOpacity = -1, LastBorder = -2, LastBlur = -2;\n\t\tpublic Color LastBorderColor;\n\t\tpublic int LastZ = int.MinValue;\n\t\tpublic double LC0 = double.NaN, LC1, LC2, LC3;\n\t\tpublic float LastStroke = -2;\n\t\tpublic Color LastStrokeColor;\n\t\tpublic int TextVersion = -1;\n\t\tpublic string Text;\n\t\tpublic string StyleKey;\n\t\tpublic bool Clipped;\n\t}\n\n\tprotected override void OnStart()\n\t{\n\t\tInstance = this;\n\t\tPanel.Style.Position = PositionMode.Absolute;\n\t\tPanel.Style.Left = 0;\n\t\tPanel.Style.Top = 0;\n\t\tPanel.Style.Width = Length.Percent( 100 );\n\t\tPanel.Style.Height = Length.Percent( 100 );\n\t\tPanel.Style.BackgroundColor = Color.Black;\n\t\tPanel.Style.PointerEvents = PointerEvents.All;\n\n\t\tstage = new Panel { Parent = Panel };\n\t\tstage.Style.Position = PositionMode.Absolute;\n\t\tstage.Style.Overflow = OverflowMode.Hidden;\n\t\tstage.Style.BackgroundColor = Color.Black;\n\t\tstage.Style.PointerEvents = PointerEvents.None;\n\n\t\ttry\n\t\t{\n\t\t\tforeach ( var f in new[] { \u0022ss2.json\u0022, \u0022zooms.json\u0022, \u0022pack.json\u0022 } )\n\t\t\t\tif ( !FileSystem.Mounted.FileExists( $\u0022{DataPath}/{f}\u0022 ) )\n\t\t\t\t\tthrow new Exception( $\u0022{DataPath}/{f} is missing from the game files. Add \\\u0022{DataPath}/*\\\u0022 to Resource Files in the project settings and publish again.\u0022 );\n\t\t\tvar json = FileSystem.Mounted.ReadAllText( $\u0022{DataPath}/ss2.json\u0022 );\n\t\t\tvar zooms = FileSystem.Mounted.ReadAllText( $\u0022{DataPath}/zooms.json\u0022 );\n\t\t\tbyte[] masks = null;\n\t\t\tif ( FileSystem.Mounted.FileExists( $\u0022{DataPath}/hitmasks.bin\u0022 ) ) masks = FileSystem.Mounted.ReadAllBytes( $\u0022{DataPath}/hitmasks.bin\u0022 ).ToArray();\n\t\t\tLibrary = Library.Load( json, zooms, masks );\n\t\t}\n\t\tcatch ( Exception e )\n\t\t{\n\t\t\tLog.Error( $\u0022FlashStage: couldn\u0027t load {DataPath}/ss2.json \u2014 {e.Message}\u0022 );\n\t\t\treturn;\n\t\t}\n\n\t\tLoadPacks();\n\t\tLog.Info( $\u0022FlashStage: loaded {Library.Chars.Count} symbols, {packIndex.Count} packed files\u0022 );\n\t\tPlayer = new Player( Library, this );\n\t\tEditor = new FlashEditor( this, Panel );\n\t\tEditor.LoadLayout();\n\t\tPlayer.Start();\n\t}\n\n\tprotected override void OnDestroy()\n\t{\n\t\tif ( Instance == this ) Instance = null;\n\t\tStopEverySound();\n\t}\n\n\tprotected override void OnUpdate()\n\t{\n\t\tif ( Player == null ) return;\n\t\tFitStage();\n\t\tEditor?.Update();\n\t\tif ( Editor == null || !Editor.Paused ) Player.Tick( Time.Delta );\n\t\tUpdateAudio( Time.Delta );\n\t\tPumpMouse();\n\t\tRender();\n\t}\n\n\t// ======================================================================= layout\n\n\tvoid FitStage()\n\t{\n\t\tvar sp = Components.Get\u003CScreenPanel\u003E();\n\t\tfloat w = Screen.Width, h = Screen.Height;\n\t\tfloat boxW = MathF.Min( w, h * FrameAspect );\n\t\tscale = boxW / (float)Library.StageWidth;\n\t\tif ( sp != null )\n\t\t{\n\t\t\tsp.AutoScreenScale = false;\n\t\t\tsp.Scale = scale;\n\t\t}\n\t\tfloat cssW = w / scale, cssH = h / scale;\n\t\tstageLeft = (cssW - (float)Library.StageWidth) / 2f;\n\t\tstageTop = (cssH - (float)Library.StageHeight) / 2f;\n\t\tstage.Style.Left = stageLeft;\n\t\tstage.Style.Top = stageTop;\n\t\tstage.Style.Width = (float)Library.StageWidth;\n\t\tstage.Style.Height = (float)Library.StageHeight;\n\t}\n\n\t/// \u003Csummary\u003EScreen pixels \u2192 Flash stage coordinates.\u003C/summary\u003E\n\tpublic Vector2 ScreenToStage( Vector2 screen ) =\u003E new( screen.x / scale - stageLeft, screen.y / scale - stageTop );\n\tpublic Vector2 StageToScreen( Vector2 st ) =\u003E new( (st.x \u002B stageLeft) * scale, (st.y \u002B stageTop) * scale );\n\tpublic float StageScale =\u003E scale;\n\tpublic Panel StagePanel =\u003E stage;\n\n\t// ======================================================================= input\n\n\tvoid PumpMouse()\n\t{\n\t\tvar m = Mouse.Position;\n\t\tif ( m == lastMouse ) return;\n\t\tlastMouse = m;\n\t\tvar s = ScreenToStage( m );\n\t\tif ( Editor != null \u0026\u0026 Editor.Active ) { Editor.MouseMove( s ); return; }\n\t\tPlayer.MouseMove( s.x, s.y );\n\t}\n\n\tprotected override void OnMouseDown( MousePanelEvent e )\n\t{\n\t\tif ( Player == null || e.MouseButton != MouseButtons.Left ) return;\n\t\tvar s = ScreenToStage( Mouse.Position );\n\t\tif ( Editor != null \u0026\u0026 Editor.Active ) { Editor.MouseDown( s ); return; }\n\t\tPlayer.MouseMove( s.x, s.y );\n\t\tPlayer.MouseDown();\n\t\tFocusInputUnderMouse();\n\t}\n\n\tprotected override void OnMouseUp( MousePanelEvent e )\n\t{\n\t\tif ( Player == null || e.MouseButton != MouseButtons.Left ) return;\n\t\tif ( Editor != null \u0026\u0026 Editor.Active ) { Editor.MouseUp(); return; }\n\t\tPlayer.MouseUp();\n\t}\n\n\tvoid FocusInputUnderMouse()\n\t{\n\t\tvar t = Player.focusedText;\n\t\tif ( t != null \u0026\u0026 nodes.TryGetValue( t, out var n ) \u0026\u0026 n.Entry != null ) n.Entry.Focus();\n\t}\n\n\t// ======================================================================= IFlashHost\n\n\tpublic void Trace( string message ) { if ( ShowTraces ) Log.Info( $\u0022[ss2] {message}\u0022 ); }\n\n\treadonly Dictionary\u003Cint, SoundFile\u003E soundFiles = new();\n\treadonly List\u003CSoundKey\u003E sounds = new();\n\tdouble audioClock;\n\tfloat lastMaster = -1;\n\n\t/// \u003Csummary\u003EOne Flash sound instance. Playing state is tracked by time (a fresh SoundHandle reports\n\t/// IsPlaying = false until the mixer picks it up, which made loops restart and stack every frame).\u003C/summary\u003E\n\tsealed class SoundKey\n\t{\n\t\tpublic SoundDef Def;\n\t\tpublic SoundFile File;\n\t\tpublic SoundHandle Handle;\n\t\tpublic int LoopsLeft;\n\t\tpublic float Volume;\n\t\tpublic double Length, IterStart;\n\t\tpublic bool Stopped;\n\t}\n\n\tSoundFile LoadSoundFile( SoundDef sound )\n\t{\n\t\tif ( soundFiles.TryGetValue( sound.Id, out var sf ) ) return sf;\n\t\tvar mp3 = PackBytes( $\u0022a/{sound.Id}.mp3\u0022 );\n\t\tvar wav = mp3 == null ? PackBytes( $\u0022a/{sound.Id}.wav\u0022 ) : null;\n\t\tif ( mp3 != null ) sf = SoundFile.FromMp3( $\u0022ss2_{sound.Id}.mp3\u0022, mp3, new SoundFile.LoadOptions() );\n\t\telse if ( wav != null ) { sf = SoundFile.FromWav( $\u0022ss2_{sound.Id}.wav\u0022, wav, new SoundFile.LoadOptions() ); soundLengths[sound.Id] = WavSeconds( wav ); }\n\t\tsoundFiles[sound.Id] = sf;\n\t\treturn sf;\n\t}\n\n\treadonly Dictionary\u003Cint, double\u003E soundLengths = new();\n\n\t/// \u003Csummary\u003EExact length of a PCM wav (data bytes / byte rate).\u003C/summary\u003E\n\tstatic double WavSeconds( byte[] b )\n\t{\n\t\tif ( b.Length \u003C 44 ) return 0;\n\t\tint byteRate = BitConverter.ToInt32( b, 28 );\n\t\tint pos = 12;\n\t\twhile ( pos \u002B 8 \u003C= b.Length )\n\t\t{\n\t\t\tint size = BitConverter.ToInt32( b, pos \u002B 4 );\n\t\t\tif ( b[pos] == \u0027d\u0027 \u0026\u0026 b[pos \u002B 1] == \u0027a\u0027 \u0026\u0026 b[pos \u002B 2] == \u0027t\u0027 \u0026\u0026 b[pos \u002B 3] == \u0027a\u0027 ) return byteRate \u003E 0 ? Math.Min( size, b.Length - pos - 8 ) / (double)byteRate : 0;\n\t\t\tpos \u002B= 8 \u002B size \u002B (size \u0026 1);\n\t\t}\n\t\treturn 0;\n\t}\n\n\tfloat Vol( float v ) =\u003E Math.Clamp( v, 0, 1 ) * MasterVolume;\n\n\tpublic object PlaySound( SoundDef sound, double volume, double pan, int loops, double offsetSeconds )\n\t{\n\t\ttry\n\t\t{\n\t\t\tvar sf = LoadSoundFile( sound );\n\t\t\tif ( sf == null ) return null;\n\t\t\tvar k = new SoundKey { Def = sound, File = sf, LoopsLeft = Math.Max( 1, loops ), Volume = (float)volume, IterStart = audioClock };\n\t\t\tk.Length = soundLengths.TryGetValue( sound.Id, out var len ) \u0026\u0026 len \u003E 0.01 ? len : sound.Seconds \u003E 0.01 ? sound.Seconds : 1;\n\t\t\tk.Handle = Play2D( sf, Vol( k.Volume ) );\n\t\t\tsounds.Add( k );\n\t\t\tif ( LogSounds ) Log.Info( $\u0022[ss2 sound] play {sound.Id} {sound.ExportName} {k.Length:0.00}s x{k.LoopsLeft} vol {volume:0.##}\u0022 );\n\t\t\treturn k;\n\t\t}\n\t\tcatch ( Exception e )\n\t\t{\n\t\t\tLog.Warning( $\u0022FlashStage: sound {sound.Id} failed: {e.Message}\u0022 );\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003ECalled every frame: restart loops when an iteration ends, drop finished sounds.\u003C/summary\u003E\n\tvoid UpdateAudio( double dt )\n\t{\n\t\taudioClock \u002B= dt;\n\t\tbool masterChanged = lastMaster != MasterVolume;\n\t\tlastMaster = MasterVolume;\n\t\tfor ( int i = sounds.Count - 1; i \u003E= 0; i-- )\n\t\t{\n\t\t\tvar k = sounds[i];\n\t\t\tif ( k.Stopped ) { sounds.RemoveAt( i ); continue; }\n\t\t\tif ( masterChanged \u0026\u0026 k.Handle != null ) k.Handle.Volume = Vol( k.Volume );\n\t\t\tif ( audioClock - k.IterStart \u003C k.Length ) continue;\n\t\t\tk.LoopsLeft--;\n\t\t\tif ( k.LoopsLeft \u003C= 0 )\n\t\t\t{\n\t\t\t\t// finished: stop the handle explicitly so nothing can keep ringing\n\t\t\t\tk.Stopped = true;\n\t\t\t\tk.Handle?.Stop( 0.05f );\n\t\t\t\tk.Handle = null;\n\t\t\t\tsounds.RemoveAt( i );\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tk.IterStart \u002B= k.Length;\n\t\t\tif ( audioClock - k.IterStart \u003E k.Length ) k.IterStart = audioClock; // fell behind (game paused / hitch)\n\t\t\tk.Handle?.Stop( 0 );\n\t\t\tk.Handle = Play2D( k.File, Vol( k.Volume ) );\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003EFlash sounds are plain stereo: no 3D position, distance falloff, reverb, reflections or occlusion.\u003C/summary\u003E\n\tstatic SoundHandle Play2D( SoundFile file, float volume )\n\t{\n\t\tvar h = Sound.PlayFile( file, volume, 1f, 0f, 0f );\n\t\tif ( h == null ) return null;\n\t\th.ListenLocal = true;\n\t\th.SpacialBlend = 0f;\n\t\th.DistanceAttenuation = false;\n\t\th.ReverbEnabled = false;\n\t\th.Reflections = false;\n\t\th.OcclusionEnabled = false;\n\t\th.Occlusion = false;\n\t\th.AirAbsorption = false;\n\t\th.Transmission = false;\n\t\th.Position = Vector3.Zero;\n\t\treturn h;\n\t}\n\n\tvoid StopEverySound()\n\t{\n\t\tforeach ( var k in sounds ) { k.Handle?.Stop( 0 ); k.Stopped = true; }\n\t\tsounds.Clear();\n\t}\n\n\tpublic void StopSound( object handle )\n\t{\n\t\tif ( handle is not SoundKey k || k.Stopped ) return;\n\t\tif ( LogSounds ) Log.Info( $\u0022[ss2 sound] stop {k.Def.Id} {k.Def.ExportName}\u0022 );\n\t\tk.Stopped = true;\n\t\tk.Handle?.Stop( 0 );\n\t\tk.Handle = null;\n\t\tsounds.Remove( k );\n\t}\n\n\tpublic bool IsSoundPlaying( object handle ) =\u003E handle is SoundKey k \u0026\u0026 !k.Stopped;\n\n\tpublic double SoundPosition( object handle ) =\u003E handle is SoundKey k \u0026\u0026 !k.Stopped ? Math.Clamp( audioClock - k.IterStart, 0, k.Length ) : 0;\n\n\tpublic void SetSoundVolume( object handle, double volume, double pan )\n\t{\n\t\tif ( handle is not SoundKey k ) return;\n\t\tk.Volume = (float)volume;\n\t\tif ( k.Handle != null ) k.Handle.Volume = Vol( k.Volume );\n\t}\n\n\tpublic string LoadShared( string name )\n\t{\n\t\tvar p = $\u0022ss2/{name}.json\u0022;\n\t\treturn FileSystem.Data.FileExists( p ) ? FileSystem.Data.ReadAllText( p ) : null;\n\t}\n\n\tpublic void SaveShared( string name, string data )\n\t{\n\t\tFileSystem.Data.CreateDirectory( \u0022ss2\u0022 );\n\t\tFileSystem.Data.WriteAllText( $\u0022ss2/{name}.json\u0022, data );\n\t}\n\n\tpublic void GetUrl( string url, string window ) =\u003E Log.Info( $\u0022[ss2] getURL {url}\u0022 );\n\n\tpublic void SetFullScreen( bool on ) { }\n\n\t// ======================================================================= packed art\n\n\tint texLoaded, texFailures;\n\treadonly List\u003Cbyte[]\u003E packs = new();\n\treadonly Dictionary\u003Cstring, (int pack, int offset, int length)\u003E packIndex = new();\n\n\tvoid LoadPacks()\n\t{\n\t\tvar idx = $\u0022{DataPath}/pack.json\u0022;\n\t\tif ( !FileSystem.Mounted.FileExists( idx ) ) return;\n\t\tvar root = (Dictionary\u003Cstring, object\u003E)Flash.Json.Parse( FileSystem.Mounted.ReadAllText( idx ) );\n\t\tforeach ( var f in (List\u003Cobject\u003E)root[\u0022files\u0022] ) packs.Add( FileSystem.Mounted.ReadAllBytes( $\u0022{DataPath}/{f}\u0022 ).ToArray() );\n\t\tforeach ( var kv in (Dictionary\u003Cstring, object\u003E)root[\u0022entries\u0022] )\n\t\t{\n\t\t\tvar a = Flash.Json.Doubles( kv.Value );\n\t\t\tpackIndex[kv.Key] = ((int)a[0], (int)a[1], (int)a[2]);\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003EPacked images are \u0022RGBA\u0022 \u002B width \u002B height \u002B raw-deflated RGBA8 pixels.\u003C/summary\u003E\n\tstatic (int w, int h, byte[] px) DecodeRaw( byte[] data )\n\t{\n\t\tif ( data.Length \u003C 12 || data[0] != \u0027R\u0027 || data[1] != \u0027G\u0027 || data[2] != \u0027B\u0027 || data[3] != \u0027A\u0027 ) return (0, 0, null);\n\t\tint w = BitConverter.ToInt32( data, 4 ), h = BitConverter.ToInt32( data, 8 );\n\t\tvar raw = new byte[w * h * 4];\n\t\tusing ( var ms = new System.IO.MemoryStream( data, 12, data.Length - 12 ) )\n\t\tusing ( var ds = new System.IO.Compression.DeflateStream( ms, System.IO.Compression.CompressionMode.Decompress ) )\n\t\t{\n\t\t\tint read = 0;\n\t\t\twhile ( read \u003C raw.Length )\n\t\t\t{\n\t\t\t\tint n = ds.Read( raw, read, raw.Length - read );\n\t\t\t\tif ( n \u003C= 0 ) break;\n\t\t\t\tread \u002B= n;\n\t\t\t}\n\t\t}\n\t\treturn (w, h, raw);\n\t}\n\n\tstatic Texture MakeTexture( int w, int h, byte[] raw, string name ) =\u003E\n\t\tTexture.Create( w, h, ImageFormat.RGBA8888 ).WithData( raw ).WithMips().WithName( \u0022ss2_\u0022 \u002B name ).Finish();\n\n\tbyte[] PackBytes( string path )\n\t{\n\t\tif ( !packIndex.TryGetValue( path, out var e ) || e.pack \u003E= packs.Count ) return null;\n\t\tvar b = new byte[e.length];\n\t\tArray.Copy( packs[e.pack], e.offset, b, 0, e.length );\n\t\treturn b;\n\t}\n\n\t// ======================================================================= rendering\n\n\tTexture Tex( string path )\n\t{\n\t\tif ( textures.TryGetValue( path, out var t ) ) return t;\n\t\tvar bytes = PackBytes( path );\n\t\tif ( bytes != null )\n\t\t{\n\t\t\ttry\n\t\t\t{\n\t\t\t\tvar (w, h, px) = DecodeRaw( bytes );\n\t\t\t\tif ( px != null ) t = MakeTexture( w, h, px, path );\n\t\t\t\tif ( t != null \u0026\u0026 texLoaded\u002B\u002B \u003C 2 ) Log.Info( $\u0022FlashStage: texture {path} {t.Width}x{t.Height} valid={t.IsValid}\u0022 );\n\t\t\t}\n\t\t\tcatch ( Exception e ) { if ( texFailures\u002B\u002B \u003C 5 ) Log.Warning( $\u0022FlashStage: can\u0027t decode {path}: {e.Message}\u0022 ); }\n\t\t}\n\t\telse if ( FileSystem.Mounted.FileExists( $\u0022{DataPath}/{path}\u0022 ) )\n\t\t\tt = Texture.LoadFromFileSystem( $\u0022{DataPath}/{path}\u0022, FileSystem.Mounted, false );\n\t\ttextures[path] = t;\n\t\treturn t;\n\t}\n\n\t// ---- colour-transformed copies of images (Flash colour transforms / colour-matrix filters done on the CPU)\n\n\treadonly Dictionary\u003Cstring, (int w, int h, byte[] px)\u003E rawCache = new();\n\treadonly Dictionary\u003Cstring, Texture\u003E variants = new();\n\tlong rawBytes;\n\t/// \u003Csummary\u003ELast frame each texture was on screen; textures unused for a while are freed.\u003C/summary\u003E\n\treadonly Dictionary\u003CTexture, int\u003E texUsed = new();\n\n\tvoid EvictTextures()\n\t{\n\t\tif ( frameStamp % 120 != 0 ) return;\n\t\tList\u003Cstring\u003E drop = null;\n\t\tforeach ( var kv in variants )\n\t\t\tif ( kv.Value == null || !texUsed.TryGetValue( kv.Value, out var f ) || frameStamp - f \u003E 300 ) (drop ??= new()).Add( kv.Key );\n\t\tif ( drop != null ) foreach ( var k in drop ) { var t = variants[k]; variants.Remove( k ); Free( t ); }\n\t\tdrop = null;\n\t\tforeach ( var kv in textures )\n\t\t\tif ( kv.Value != null \u0026\u0026 texUsed.TryGetValue( kv.Value, out var f ) \u0026\u0026 frameStamp - f \u003E 1800 ) (drop ??= new()).Add( kv.Key );\n\t\tif ( drop != null ) foreach ( var k in drop ) { var t = textures[k]; textures.Remove( k ); Free( t ); }\n\t}\n\n\tvoid Free( Texture t )\n\t{\n\t\tif ( t == null ) return;\n\t\ttexUsed.Remove( t );\n\t\tforeach ( var n in nodes.Values ) if ( n.Cache.Tex == t ) { n.Cache.Tex = null; n.Cache.Image = null; }\n\t\tt.Dispose();\n\t}\n\n\t(int w, int h, byte[] px) Raw( string path )\n\t{\n\t\tif ( rawCache.TryGetValue( path, out var r ) ) return r;\n\t\tvar bytes = PackBytes( path );\n\t\tr = bytes != null ? DecodeRaw( bytes ) : (0, 0, null);\n\t\tif ( r.px != null )\n\t\t{\n\t\t\trawBytes \u002B= r.px.Length;\n\t\t\tif ( rawBytes \u003E 192_000_000 ) { rawCache.Clear(); rawBytes = r.px.Length; } // decoded pixels are only a cache\n\t\t}\n\t\trawCache[path] = r;\n\t\treturn r;\n\t}\n\n\tstatic int Q( double v, double step ) =\u003E (int)Math.Round( v / step );\n\n\t/// \u003Csummary\u003EThe image with colour transform cx (rgb part) and then any colour-matrix filters applied.\u003C/summary\u003E\n\t/// \u003Csummary\u003EA mask shape that cuts an image: its picture, bounds, and the map from the image\u0027s local space to the mask\u0027s.\u003C/summary\u003E\n\tstruct MaskRef { public string Image; public double X0, Y0, X1, Y1; public FMatrix R; }\n\n\tTexture Variant( string image, ColorTransform cx, List\u003Cfloat[]\u003E matrices, List\u003CMaskRef\u003E masks = null, double ix0 = 0, double iy0 = 0, double ix1 = 0, double iy1 = 0 )\n\t{\n\t\tbool cxIdentity = Math.Abs( cx.Rm - 1 ) \u003C 0.004 \u0026\u0026 Math.Abs( cx.Gm - 1 ) \u003C 0.004 \u0026\u0026 Math.Abs( cx.Bm - 1 ) \u003C 0.004\n\t\t\t\u0026\u0026 Math.Abs( cx.Ra ) \u003C 0.5 \u0026\u0026 Math.Abs( cx.Ga ) \u003C 0.5 \u0026\u0026 Math.Abs( cx.Ba ) \u003C 0.5 \u0026\u0026 Math.Abs( cx.Aa ) \u003C 0.5;\n\t\tif ( cxIdentity \u0026\u0026 (matrices == null || matrices.Count == 0) \u0026\u0026 masks == null ) return Tex( image );\n\t\t// quantise so animated tints reuse textures\n\t\tvar sb = new StringBuilder( image );\n\t\tsb.Append( \u0027|\u0027 ).Append( Q( cx.Rm, 1 / 128.0 ) ).Append( \u0027,\u0027 ).Append( Q( cx.Gm, 1 / 128.0 ) ).Append( \u0027,\u0027 ).Append( Q( cx.Bm, 1 / 128.0 ) )\n\t\t\t.Append( \u0027,\u0027 ).Append( Q( cx.Ra, 2 ) ).Append( \u0027,\u0027 ).Append( Q( cx.Ga, 2 ) ).Append( \u0027,\u0027 ).Append( Q( cx.Ba, 2 ) ).Append( \u0027,\u0027 ).Append( Q( cx.Aa, 2 ) );\n\t\tif ( matrices != null )\n\t\t\tforeach ( var m in matrices ) { sb.Append( \u0027|\u0027 ); foreach ( var v in m ) sb.Append( Q( v, 1 / 256.0 ) ).Append( \u0027,\u0027 ); }\n\t\tif ( masks != null )\n\t\t\tforeach ( var mr in masks )\n\t\t\t\tsb.Append( \u0022|m\u0022 ).Append( mr.Image ).Append( \u0027,\u0027 ).Append( Q( mr.R.A, 0.002 ) ).Append( \u0027,\u0027 ).Append( Q( mr.R.B, 0.002 ) ).Append( \u0027,\u0027 )\n\t\t\t\t\t.Append( Q( mr.R.C, 0.002 ) ).Append( \u0027,\u0027 ).Append( Q( mr.R.D, 0.002 ) ).Append( \u0027,\u0027 ).Append( Q( mr.R.Tx, 0.25 ) ).Append( \u0027,\u0027 ).Append( Q( mr.R.Ty, 0.25 ) );\n\t\tvar key = sb.ToString();\n\t\tif ( variants.TryGetValue( key, out var t ) ) return t;\n\t\tvar (w, h, src) = Raw( image );\n\t\tif ( src == null ) return Tex( image );\n\t\t// spread big recolours over frames (null = keep showing the previous texture this frame)\n\t\tif ( pixelBudget \u003C w * h \u0026\u0026 pixelBudget \u003C PixelBudgetPerFrame ) return null;\n\t\tpixelBudget -= w * h;\n\t\tvar px = new byte[src.Length];\n\t\tdouble rm = Q( cx.Rm, 1 / 128.0 ) / 128.0, gm = Q( cx.Gm, 1 / 128.0 ) / 128.0, bm = Q( cx.Bm, 1 / 128.0 ) / 128.0;\n\t\tdouble ra = Q( cx.Ra, 2 ) * 2.0, ga = Q( cx.Ga, 2 ) * 2.0, ba = Q( cx.Ba, 2 ) * 2.0, aa = Q( cx.Aa, 2 ) * 2.0;\n\t\tfor ( int i = 0; i \u003C src.Length; i \u002B= 4 )\n\t\t{\n\t\t\tdouble a = src[i \u002B 3];\n\t\t\tif ( a == 0 \u0026\u0026 aa \u003C= 0 ) continue;\n\t\t\tdouble r = src[i] * rm \u002B ra, g = src[i \u002B 1] * gm \u002B ga, b = src[i \u002B 2] * bm \u002B ba;\n\t\t\ta = Math.Clamp( a \u002B aa * (a \u003E 0 ? 1 : 0), 0, 255 );\n\t\t\tr = Math.Clamp( r, 0, 255 ); g = Math.Clamp( g, 0, 255 ); b = Math.Clamp( b, 0, 255 );\n\t\t\tif ( matrices != null )\n\t\t\t\tforeach ( var m in matrices )\n\t\t\t\t{\n\t\t\t\t\tdouble nr = m[0] * r \u002B m[1] * g \u002B m[2] * b \u002B m[3] * a \u002B m[4];\n\t\t\t\t\tdouble ng = m[5] * r \u002B m[6] * g \u002B m[7] * b \u002B m[8] * a \u002B m[9];\n\t\t\t\t\tdouble nb = m[10] * r \u002B m[11] * g \u002B m[12] * b \u002B m[13] * a \u002B m[14];\n\t\t\t\t\tdouble na = m[15] * r \u002B m[16] * g \u002B m[17] * b \u002B m[18] * a \u002B m[19];\n\t\t\t\t\tr = Math.Clamp( nr, 0, 255 ); g = Math.Clamp( ng, 0, 255 ); b = Math.Clamp( nb, 0, 255 ); a = Math.Clamp( na, 0, 255 );\n\t\t\t\t}\n\t\t\tpx[i] = (byte)r; px[i \u002B 1] = (byte)g; px[i \u002B 2] = (byte)b; px[i \u002B 3] = (byte)a;\n\t\t}\n\t\tif ( masks != null ) ApplyMasks( px, w, h, masks, ix0, iy0, ix1, iy1 );\n\t\tt = MakeTexture( w, h, px, key.GetHashCode().ToString( \u0022x\u0022 ) );\n\t\tvariants[key] = t;\n\t\treturn t;\n\t}\n\n\treadonly List\u003CDisplayObject\u003E deadNodes = new();\n\treadonly List\u003CPanel\u003E orderNow = new(), orderLast = new();\n\treadonly Dictionary\u003CPanel, int\u003E rank = new();\n\n\tvoid Render()\n\t{\n\t\tPlayer.CollectRender( items );\n\t\tframeStamp\u002B\u002B;\n\t\tpixelBudget = PixelBudgetPerFrame;\n\t\tEvictTextures();\n\t\tfor ( int i = 0; i \u003C items.Count; i\u002B\u002B )\n\t\t{\n\t\t\tvar it = items[i];\n\t\t\tif ( !nodes.TryGetValue( it.Obj, out var n ) ) { n = new Node(); nodes[it.Obj] = n; }\n\t\t\tn.Stamp = frameStamp;\n\t\t\ttry { Draw( it, n, i ); }\n\t\t\tcatch ( Exception e ) { Log.Warning( $\u0022FlashStage draw: {e.Message}\u0022 ); }\n\t\t}\n\t\t// keep the panels\u0027 real child order = Flash draw order (z-index alone isn\u0027t re-sorted when a new\n\t\t// panel appears, e.g. a button\u0027s hover state, which then covered the text above it)\n\t\torderNow.Clear();\n\t\tfor ( int i = 0; i \u003C items.Count; i\u002B\u002B ) if ( nodes.TryGetValue( items[i].Obj, out var on ) \u0026\u0026 on.Outer != null ) orderNow.Add( on.Outer );\n\t\tbool same = orderNow.Count == orderLast.Count;\n\t\tfor ( int i = 0; same \u0026\u0026 i \u003C orderNow.Count; i\u002B\u002B ) same = ReferenceEquals( orderNow[i], orderLast[i] );\n\t\tif ( !same )\n\t\t{\n\t\t\trank.Clear();\n\t\t\tfor ( int i = 0; i \u003C orderNow.Count; i\u002B\u002B ) rank[orderNow[i]] = i;\n\t\t\tstage.SortChildren( (Panel c) =\u003E rank.TryGetValue( c, out var r ) ? r : int.MaxValue );\n\t\t\torderLast.Clear();\n\t\t\torderLast.AddRange( orderNow );\n\t\t}\n\t\t// panels for anything not drawn this frame (removed, hidden, or now a lit sprite) go away\n\t\tdeadNodes.Clear();\n\t\tforeach ( var kv in nodes ) if ( kv.Value.Stamp != frameStamp ) deadNodes.Add( kv.Key );\n\t\tforeach ( var d in deadNodes ) { nodes[d].Outer?.Delete( true ); nodes.Remove( d ); }\n\t\tEditor?.Draw( items );\n\t}\n\n\tPanelTransform MakeTransform( FMatrix m )\n\t{\n\t\tvar t = new PanelTransform();\n\t\tfloat k = ScaleMatrixTranslation ? scale : 1f;\n\t\tm.Tx *= k; m.Ty *= k;\n\t\t// row-vector layout: x\u0027 = x*M11 \u002B y*M21 \u002B M41\n\t\tglobal::Matrix mat = TransposeMatrix\n\t\t\t? new global::Matrix( (float)m.A, (float)m.C, 0, 0, (float)m.B, (float)m.D, 0, 0, 0, 0, 1, 0, (float)m.Tx, (float)m.Ty, 0, 1 )\n\t\t\t: new global::Matrix( (float)m.A, (float)m.B, 0, 0, (float)m.C, (float)m.D, 0, 0, 0, 0, 1, 0, (float)m.Tx, (float)m.Ty, 0, 1 );\n\t\tt.AddMatrix3D( mat );\n\t\treturn t;\n\t}\n\n\tstatic FMatrix Translate( double x, double y ) =\u003E new( 1, 0, 0, 1, x, y );\n\n\tvoid EnsurePanels( Node n, bool clipped, bool entry )\n\t{\n\t\tif ( n.Panel != null \u0026\u0026 n.Clipped == clipped \u0026\u0026 (n.Entry != null) == entry ) return;\n\t\tn.Outer?.Delete( true );\n\t\tPanel p = entry ? new TextEntry() : (Panel)new Panel();\n\t\tif ( entry )\n\t\t{\n\t\t\tn.Entry = (TextEntry)p;\n\t\t\tp.Style.PointerEvents = PointerEvents.All;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tn.Entry = null;\n\t\t\tp.Style.PointerEvents = PointerEvents.None;\n\t\t}\n\t\tp.Style.Position = PositionMode.Absolute;\n\t\tp.Style.Left = 0;\n\t\tp.Style.Top = 0;\n\t\tp.Style.TransformOriginX = 0;\n\t\tp.Style.TransformOriginY = 0;\n\t\tif ( clipped )\n\t\t{\n\t\t\tvar c = new Panel { Parent = stage };\n\t\t\tc.Style.Position = PositionMode.Absolute;\n\t\t\tc.Style.Overflow = OverflowMode.Hidden;\n\t\t\tc.Style.PointerEvents = entry ? PointerEvents.All : PointerEvents.None;\n\t\t\tp.Parent = c;\n\t\t\tn.Outer = c;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tp.Parent = stage;\n\t\t\tn.Outer = p;\n\t\t}\n\t\tn.Panel = p;\n\t\tn.Clipped = clipped;\n\t\tn.Image = null; n.Mode = null; n.Tex = null; n.HasGeo = false; n.LastOpacity = -1; n.LastBorder = -2; n.LastBlur = -2; n.LastZ = int.MinValue; n.LC0 = double.NaN; n.LastStroke = -2; n.StyleKey = null; n.TextVersion = -1;\n\t}\n\n\tvoid Draw( RenderItem it, Node n, int index )\n\t{\n\t\tvar clip = it.Clip;\n\t\tbool clipped = clip != null;\n\t\tbool entry = it.Obj is EditText e0 \u0026\u0026 e0.Edit.Input;\n\t\tEnsurePanels( n, clipped, entry );\n\t\tvar p = n.Panel;\n\n\t\tFMatrix world = it.World;\n\t\tif ( clipped )\n\t\t{\n\t\t\tvar c = n.Outer;\n\t\t\tif ( n.LC0 != clip[0] || n.LC1 != clip[1] || n.LC2 != clip[2] || n.LC3 != clip[3] )\n\t\t\t{\n\t\t\t\tn.LC0 = clip[0]; n.LC1 = clip[1]; n.LC2 = clip[2]; n.LC3 = clip[3];\n\t\t\t\tc.Style.Left = (float)clip[0];\n\t\t\t\tc.Style.Top = (float)clip[1];\n\t\t\t\tc.Style.Width = (float)Math.Max( 0, clip[2] - clip[0] );\n\t\t\t\tc.Style.Height = (float)Math.Max( 0, clip[3] - clip[1] );\n\t\t\t}\n\t\t\tworld = Translate( -clip[0], -clip[1] ).Mul( world );\n\t\t}\n\t\tif ( n.LastZ != index ) { n.LastZ = index; n.Outer.Style.ZIndex = index; }\n\n\t\tswitch ( it.Obj )\n\t\t{\n\t\t\tcase ShapeObj s: DrawImage( n, s.Shape.Image, s.Shape.X0, s.Shape.Y0, s.Shape.X1, s.Shape.Y1, world, it ); break;\n\t\t\tcase StaticTextObj t: DrawImage( n, t.Text.Image, t.Text.X0, t.Text.Y0, t.Text.X1, t.Text.Y1, world, it ); break;\n\t\t\tcase MorphObj mo:\n\t\t\t\t{\n\t\t\t\t\tbool end = mo.T \u003E= 0.5;\n\t\t\t\t\tvar b = end ? mo.Morph.B1 : mo.Morph.B0;\n\t\t\t\t\tDrawImage( n, end ? mo.Morph.ImageEnd : mo.Morph.ImageStart, b[0], b[1], b[2], b[3], world, it );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase EditText et: DrawText( n, et, world, it ); break;\n\t\t}\n\t}\n\n\tvoid DrawImage( Node n, string image, double x0, double y0, double x1, double y1, FMatrix world, RenderItem it )\n\t{\n\t\tvar p = n.Panel;\n\t\tvar cx = it.Cx;\n\t\tFilter glow = null;\n\t\tfloat blur = 0;\n\t\tList\u003Cfloat[]\u003E matrices = null;\n\t\tbool hasMatrix = false;\n\t\tif ( it.Filters != null )\n\t\t{\n\t\t\tforeach ( var f in it.Filters )\n\t\t\t{\n\t\t\t\tswitch ( f.Kind )\n\t\t\t\t{\n\t\t\t\t\tcase \u0022cmatrix\u0022: hasMatrix = true; break;\n\t\t\t\t\tcase \u0022glow\u0022: if ( !f.Inner \u0026\u0026 !f.Knockout \u0026\u0026 glow == null ) glow = f; break;\n\t\t\t\t\tcase \u0022blur\u0022: blur = (float)Math.Max( f.BlurX, f.BlurY ) / 3f; break;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// colour matrices are only needed when the cached texture can\u0027t be reused\n\t\tif ( hasMatrix \u0026\u0026 !(n.Cache.Tex != null \u0026\u0026 n.Cache.Image == image \u0026\u0026 it.Masks == null \u0026\u0026 ReferenceEquals( n.Cache.LastFilters, it.Filters ) \u0026\u0026 SameCx( n.Cache.LastCx, cx )) )\n\t\t\tmatrices = ColorMatrices( it );\n\n\t\tvar tex = TextureFor( n.Cache, image, x0, y0, x1, y1, it, matrices );\n\t\tif ( tex != null ) texUsed[tex] = frameStamp;\n\t\tif ( n.Tex != tex )\n\t\t{\n\t\t\tn.Tex = tex;\n\t\t\tn.Image = image;\n\t\t\tp.Style.BackgroundImage = tex;\n\t\t\tp.Style.BackgroundSizeX = Length.Percent( 100 );\n\t\t\tp.Style.BackgroundSizeY = Length.Percent( 100 );\n\t\t\tp.Style.BackgroundRepeat = BackgroundRepeat.NoRepeat;\n\t\t}\n\t\tSetGeometry( n, p, world.Mul( Translate( x0, y0 ) ), x1 - x0, y1 - y0 );\n\t\tSetOpacity( n, p, (float)Math.Clamp( cx.Am, 0, 1 ) );\n\t\tfloat border = -1; Color borderColor = default;\n\t\tif ( glow != null \u0026\u0026 glow.Strength \u003E= 1.5 \u0026\u0026 Math.Max( glow.BlurX, glow.BlurY ) \u003C= 12 )\n\t\t{\n\t\t\t// a strong, tight glow is an outline\n\t\t\tvar scaleAvg = Math.Sqrt( Math.Abs( world.A * world.D - world.B * world.C ) );\n\t\t\tborder = (float)Math.Round( Math.Max( 0.5, Math.Max( glow.BlurX, glow.BlurY ) * 0.4 / Math.Max( 0.05, scaleAvg ) ), 2 );\n\t\t\tborderColor = new Color( (float)(glow.Color[0] / 255), (float)(glow.Color[1] / 255), (float)(glow.Color[2] / 255), (float)(glow.Color[3] / 255) );\n\t\t}\n\t\tif ( border != n.LastBorder || borderColor != n.LastBorderColor )\n\t\t{\n\t\t\tn.LastBorder = border; n.LastBorderColor = borderColor;\n\t\t\tif ( border \u003E 0 ) { p.Style.FilterBorderWidth = border; p.Style.FilterBorderColor = borderColor; }\n\t\t\telse { p.Style.FilterBorderWidth = null; p.Style.FilterBorderColor = null; }\n\t\t}\n\t\tif ( blur != n.LastBlur ) { n.LastBlur = blur; p.Style.FilterBlur = blur \u003E 0 ? blur : null; }\n\t}\n\n\t/// \u003Csummary\u003ESize \u002B transform, written only when they change (or the UI scale changes).\u003C/summary\u003E\n\tvoid SetGeometry( Node n, Panel p, FMatrix m, double w, double h )\n\t{\n\t\tif ( n.HasGeo \u0026\u0026 n.LastScale == scale \u0026\u0026 n.LastW == w \u0026\u0026 n.LastH == h \u0026\u0026\n\t\t\tn.LastM.A == m.A \u0026\u0026 n.LastM.B == m.B \u0026\u0026 n.LastM.C == m.C \u0026\u0026 n.LastM.D == m.D \u0026\u0026 n.LastM.Tx == m.Tx \u0026\u0026 n.LastM.Ty == m.Ty ) return;\n\t\tif ( !n.HasGeo || n.LastW != w || n.LastH != h ) { p.Style.Width = (float)w; p.Style.Height = (float)h; }\n\t\tn.HasGeo = true; n.LastScale = scale; n.LastW = w; n.LastH = h; n.LastM = m;\n\t\tp.Style.Transform = MakeTransform( m );\n\t}\n\n\tstatic void SetOpacity( Node n, Panel p, float o )\n\t{\n\t\tif ( n.LastOpacity == o ) return;\n\t\tn.LastOpacity = o;\n\t\tp.Style.Opacity = o;\n\t}\n\n\t/// \u003Csummary\u003EFlash masks: keep only the parts of the image covered by (any of) the mask shapes.\u003C/summary\u003E\n\tvoid ApplyMasks( byte[] px, int w, int h, List\u003CMaskRef\u003E masks, double ix0, double iy0, double ix1, double iy1 )\n\t{\n\t\tvar cover = new byte[w * h];\n\t\tdouble sx = (ix1 - ix0) / w, sy = (iy1 - iy0) / h;\n\t\tforeach ( var mr in masks )\n\t\t{\n\t\t\tvar (mw, mh, mpx) = Raw( mr.Image );\n\t\t\tif ( mpx == null ) continue;\n\t\t\tdouble kx = mw / Math.Max( 1e-6, mr.X1 - mr.X0 ), ky = mh / Math.Max( 1e-6, mr.Y1 - mr.Y0 );\n\t\t\tvar R = mr.R;\n\t\t\tfor ( int y = 0; y \u003C h; y\u002B\u002B )\n\t\t\t{\n\t\t\t\tdouble ly = iy0 \u002B (y \u002B 0.5) * sy;\n\t\t\t\tfor ( int x = 0; x \u003C w; x\u002B\u002B )\n\t\t\t\t{\n\t\t\t\t\tint i = y * w \u002B x;\n\t\t\t\t\tif ( cover[i] == 255 || px[i * 4 \u002B 3] == 0 ) continue;\n\t\t\t\t\tdouble lx = ix0 \u002B (x \u002B 0.5) * sx;\n\t\t\t\t\tdouble mx = R.A * lx \u002B R.C * ly \u002B R.Tx, my = R.B * lx \u002B R.D * ly \u002B R.Ty;\n\t\t\t\t\tint u = (int)((mx - mr.X0) * kx), v = (int)((my - mr.Y0) * ky);\n\t\t\t\t\tif ( u \u003C 0 || v \u003C 0 || u \u003E= mw || v \u003E= mh ) continue;\n\t\t\t\t\tbyte a = mpx[(v * mw \u002B u) * 4 \u002B 3];\n\t\t\t\t\tif ( a \u003E cover[i] ) cover[i] = a;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor ( int i = 0; i \u003C cover.Length; i\u002B\u002B )\n\t\t\tpx[i * 4 \u002B 3] = (byte)(px[i * 4 \u002B 3] * cover[i] / 255);\n\t}\n\n\tList\u003CMaskRef\u003E MaskRefs( RenderItem it )\n\t{\n\t\tif ( it.Masks == null || it.Masks.Count == 0 ) return null;\n\t\tvar list = new List\u003CMaskRef\u003E();\n\t\tforeach ( var m in it.Masks )\n\t\t{\n\t\t\tstring img; double[] b;\n\t\t\tswitch ( m.Obj )\n\t\t\t{\n\t\t\t\tcase ShapeObj s: img = s.Shape.Image; b = new[] { s.Shape.X0, s.Shape.Y0, s.Shape.X1, s.Shape.Y1 }; break;\n\t\t\t\tcase MorphObj mo:\n\t\t\t\t\t{\n\t\t\t\t\t\tbool end = mo.T \u003E= 0.5;\n\t\t\t\t\t\timg = end ? mo.Morph.ImageEnd : mo.Morph.ImageStart;\n\t\t\t\t\t\tb = end ? mo.Morph.B1 : mo.Morph.B0;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tdefault: return null; // can\u0027t express it: fall back to the rectangle clip\n\t\t\t}\n\t\t\tif ( img == null ) return null;\n\t\t\tlist.Add( new MaskRef { Image = img, X0 = b[0], Y0 = b[1], X1 = b[2], Y1 = b[3], R = m.World.Inverse().Mul( it.World ) } );\n\t\t}\n\t\treturn list;\n\t}\n\n\t/// \u003Csummary\u003ERemembers the last colour/mask variant an object used, so unchanged objects skip the lookup.\u003C/summary\u003E\n\tsealed class TexCache\n\t{\n\t\tpublic Texture Tex;\n\t\tpublic string Image;\n\t\tpublic ColorTransform LastCx;\n\t\tpublic List\u003CFilter\u003E LastFilters;\n\t\tpublic List\u003CMaskRef\u003E LastMasks;\n\t\tpublic int MaskChurn, ColorChurn;\n\t\tpublic bool NoMask;\n\t}\n\n\tconst int PixelBudgetPerFrame = 2_500_000;\n\tint pixelBudget = PixelBudgetPerFrame;\n\n\tTexture TextureFor( TexCache c, string image, double x0, double y0, double x1, double y1, RenderItem it, List\u003Cfloat[]\u003E matrices )\n\t{\n\t\tif ( c.Tex != null \u0026\u0026 c.Image == image \u0026\u0026 it.Masks == null \u0026\u0026 ReferenceEquals( c.LastFilters, it.Filters ) \u0026\u0026 SameCx( c.LastCx, it.Cx ) ) { c.ColorChurn = 0; return c.Tex; }\n\t\t// a mask that moves every frame (scrolling, animated wipes) would need a new texture each frame: use the rectangle clip for those\n\t\tvar masks = c.NoMask ? null : MaskRefs( it );\n\t\t// masked but nothing moved: same texture as last frame\n\t\tif ( masks != null \u0026\u0026 c.Tex != null \u0026\u0026 c.Image == image \u0026\u0026 ReferenceEquals( c.LastFilters, it.Filters ) \u0026\u0026 SameCx( c.LastCx, it.Cx ) \u0026\u0026 SameMasks( c.LastMasks, masks ) ) { c.MaskChurn = 0; return c.Tex; }\n\t\t// colour that animates every frame (flashes, fades to a tint): snap to coarse steps so a handful of textures get reused\n\t\tvar cx = it.Cx;\n\t\tif ( c.Image == image \u0026\u0026 !SameCx( c.LastCx, cx ) ) c.ColorChurn\u002B\u002B; else c.ColorChurn = 0;\n\t\tif ( c.ColorChurn \u003E 6 ) cx = Coarse( cx );\n\t\tvar tex = Variant( image, cx, matrices, masks, x0, y0, x1, y1 );\n\t\tif ( tex == null ) return c.Tex ?? Tex( image );\n\t\tif ( masks != null )\n\t\t{\n\t\t\tc.MaskChurn = tex != c.Tex \u0026\u0026 c.Tex != null ? c.MaskChurn \u002B 1 : 0;\n\t\t\tif ( c.MaskChurn \u003E 20 ) c.NoMask = true;\n\t\t}\n\t\tc.Tex = tex; c.Image = image; c.LastCx = it.Cx; c.LastFilters = it.Filters; c.LastMasks = masks;\n\t\treturn tex;\n\t}\n\n\tstatic ColorTransform Coarse( ColorTransform c )\n\t{\n\t\tstatic double M( double v ) =\u003E Math.Round( v * 8 ) / 8;\n\t\tstatic double A( double v ) =\u003E Math.Round( v / 32 ) * 32;\n\t\treturn new ColorTransform { Rm = M( c.Rm ), Gm = M( c.Gm ), Bm = M( c.Bm ), Am = c.Am, Ra = A( c.Ra ), Ga = A( c.Ga ), Ba = A( c.Ba ), Aa = A( c.Aa ) };\n\t}\n\n\tstatic List\u003Cfloat[]\u003E ColorMatrices( RenderItem it )\n\t{\n\t\tList\u003Cfloat[]\u003E matrices = null;\n\t\tif ( it.Filters != null )\n\t\t\tforeach ( var f in it.Filters )\n\t\t\t\tif ( f.Kind == \u0022cmatrix\u0022 \u0026\u0026 f.ColorMatrix != null \u0026\u0026 f.ColorMatrix.Length \u003E= 20 ) (matrices ??= new()).Add( ToFloats( f.ColorMatrix ) );\n\t\treturn matrices;\n\t}\n\n\tstatic bool SameMasks( List\u003CMaskRef\u003E a, List\u003CMaskRef\u003E b )\n\t{\n\t\tif ( a == null || b == null || a.Count != b.Count ) return false;\n\t\tfor ( int i = 0; i \u003C a.Count; i\u002B\u002B )\n\t\t{\n\t\t\tvar x = a[i]; var y = b[i];\n\t\t\tif ( x.Image != y.Image ) return false;\n\t\t\tif ( Math.Abs( x.R.A - y.R.A ) \u003E 0.001 || Math.Abs( x.R.B - y.R.B ) \u003E 0.001 || Math.Abs( x.R.C - y.R.C ) \u003E 0.001 || Math.Abs( x.R.D - y.R.D ) \u003E 0.001 ) return false;\n\t\t\tif ( Math.Abs( x.R.Tx - y.R.Tx ) \u003E 0.1 || Math.Abs( x.R.Ty - y.R.Ty ) \u003E 0.1 ) return false;\n\t\t}\n\t\treturn true;\n\t}\n\n\tstatic bool SameCx( ColorTransform a, ColorTransform b ) =\u003E\n\t\ta.Rm == b.Rm \u0026\u0026 a.Gm == b.Gm \u0026\u0026 a.Bm == b.Bm \u0026\u0026 a.Ra == b.Ra \u0026\u0026 a.Ga == b.Ga \u0026\u0026 a.Ba == b.Ba \u0026\u0026 a.Aa == b.Aa;\n\n\tstatic float[] ToFloats( double[] a )\n\t{\n\t\tvar r = new float[a.Length];\n\t\tfor ( int i = 0; i \u003C a.Length; i\u002B\u002B ) r[i] = (float)a[i];\n\t\treturn r;\n\t}\n\n\tvoid DrawText( Node n, EditText et, FMatrix world, RenderItem it )\n\t{\n\t\tvar d = et.Edit;\n\t\tvar p = n.Panel;\n\t\tconst double gutter = 2;\n\t\tdouble w = d.X1 - d.X0 - gutter * 2, h = d.Y1 - d.Y0 - gutter * 2;\n\n\t\tif ( n.Entry != null )\n\t\t{\n\t\t\tif ( n.TextVersion != et.Version \u0026\u0026 !n.Entry.HasFocus )\n\t\t\t{\n\t\t\t\tn.Entry.Text = et.Text;\n\t\t\t\tn.TextVersion = et.Version;\n\t\t\t}\n\t\t\tif ( n.Entry.OnTextEdited == null )\n\t\t\t\tn.Entry.OnTextEdited = s =\u003E { et.UserInput( s ); n.TextVersion = et.Version; };\n\t\t}\n\t\telse if ( n.TextVersion != et.Version || n.Text == null )\n\t\t{\n\t\t\t// a Label child holds the text\n\t\t\tif ( p.ChildrenCount == 0 ) new Label { Parent = p };\n\t\t\tvar lbl = p.GetChild( 0 ) as Label;\n\t\t\tif ( lbl != null ) lbl.Text = d.Password ? new string( \u0027*\u0027, et.Text.Length ) : et.Text;\n\t\t\tn.TextVersion = et.Version;\n\t\t\tn.Text = et.Text;\n\t\t}\n\n\t\tvar col = et.TextColor;\n\t\tvar cx = it.Cx;\n\t\tfloat r = (float)Math.Clamp( (col[0] * cx.Rm \u002B cx.Ra) / 255, 0, 1 ), g = (float)Math.Clamp( (col[1] * cx.Gm \u002B cx.Ga) / 255, 0, 1 ), b = (float)Math.Clamp( (col[2] * cx.Bm \u002B cx.Ba) / 255, 0, 1 );\n\t\tstring key = $\u0022{d.FontId}|{d.Size}|{r},{g},{b}|{d.Align}|{d.Multiline}{d.WordWrap}\u0022;\n\t\tif ( n.StyleKey != key )\n\t\t{\n\t\t\tn.StyleKey = key;\n\t\t\tvar family = Library.Fonts.TryGetValue( d.FontId, out var fd ) ? fd.Family : \u0022Arial\u0022;\n\t\t\tPanel target = (Panel)n.Entry ?? (p.ChildrenCount \u003E 0 ? p.GetChild( 0 ) : p);\n\t\t\tforeach ( var s in new[] { p.Style, target.Style } )\n\t\t\t{\n\t\t\t\ts.FontFamily = family;\n\t\t\t\ts.FontSize = (float)d.Size;\n\t\t\t\ts.FontColor = new Color( r, g, b, 1 );\n\t\t\t\ts.TextAlign = d.Align switch { 1 =\u003E TextAlign.Right, 2 =\u003E TextAlign.Center, _ =\u003E TextAlign.Left };\n\t\t\t\ts.WhiteSpace = d.Multiline || d.WordWrap ? WhiteSpace.PreLine : WhiteSpace.NoWrap;\n\t\t\t}\n\t\t\tif ( target != p )\n\t\t\t{\n\t\t\t\ttarget.Style.Width = Length.Percent( 100 );\n\t\t\t\ttarget.Style.Position = PositionMode.Absolute;\n\t\t\t\ttarget.Style.Left = 0; target.Style.Top = 0;\n\t\t\t}\n\t\t\tif ( n.Entry != null ) { n.Entry.Style.BackgroundColor = null; }\n\t\t}\n\t\tFilter glow = null;\n\t\tif ( it.Filters != null ) foreach ( var f in it.Filters ) if ( f.Kind == \u0022glow\u0022 \u0026\u0026 !f.Inner ) { glow = f; break; }\n\t\tPanel tp = (Panel)n.Entry ?? (p.ChildrenCount \u003E 0 ? p.GetChild( 0 ) : p);\n\t\tfloat stroke = -1; Color strokeColor = default;\n\t\tif ( glow != null )\n\t\t{\n\t\t\tstroke = (float)Math.Clamp( Math.Max( glow.BlurX, glow.BlurY ) * Math.Min( glow.Strength, 4 ) * 0.35, 1, 6 );\n\t\t\tstrokeColor = new Color( (float)(glow.Color[0] / 255), (float)(glow.Color[1] / 255), (float)(glow.Color[2] / 255), (float)(glow.Color[3] / 255) );\n\t\t}\n\t\tif ( stroke != n.LastStroke || strokeColor != n.LastStrokeColor )\n\t\t{\n\t\t\tn.LastStroke = stroke; n.LastStrokeColor = strokeColor;\n\t\t\tif ( stroke \u003E 0 ) { tp.Style.TextStrokeWidth = stroke; tp.Style.TextStrokeColor = strokeColor; }\n\t\t\telse { tp.Style.TextStrokeWidth = null; tp.Style.TextStrokeColor = null; }\n\t\t}\n\n\t\tSetGeometry( n, p, world.Mul( Translate( d.X0 \u002B gutter, d.Y0 \u002B gutter ) ), w, h );\n\t\tSetOpacity( n, p, (float)Math.Clamp( cx.Am \u002B cx.Aa / 255.0, 0, 1 ) );\n\t}\n}\n"},{"Ident":"farten.shoes_and_daggers","Path":"Game/Catalog.cs","FileName":"Catalog.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":443203,"IsPrivate":false,"Code":"using System;\n\npublic enum GearSlot { Helm, Chest, Shoulders, Gauntlets, Thighs, Greaves, Boots, Shield }\n\npublic enum Enchant { None, Bleed, Poison, Death, Freeze }\n\npublic enum Spell { Fireball, Lightning, Freeze, Heal, Drain, Tremor }\n\npublic enum Potion { Health, Energy, Magicka }\n\n/// \u003Csummary\u003ESrc = the S\u0026amp;S2 picture (resources/sad/ss/item/Src.png).\u003C/summary\u003E\npublic record WeaponDef( int Id, int Src, string Name, int Min, int Max, int Strength, int Price );\n\npublic record GearDef( GearSlot Slot, int Index, int Src, string Name, int Level, int Points, int Price );\n\npublic record RaceDef( string Name, string[] Skins );\n\npublic record SpellDef( Spell Id, string Name, int Cost, int MinMagicka, int Price, string Icon, string Help );\n\npublic static class Catalog\n{\n\tpublic const string Art = \u0022resources/sad/\u0022;\n\n\tpublic static WeaponDef[] Weapons =\u003E GearData.Weapons;\n\tpublic static GearDef[] Gear( GearSlot slot ) =\u003E GearData.Gear[(int)slot];\n\tpublic static GearDef GearItem( GearSlot slot, int index ) =\u003E index \u003E= 0 \u0026\u0026 index \u003C Gear( slot ).Length ? Gear( slot )[index] : null;\n\tpublic static readonly GearSlot[] Slots = Enum.GetValues\u003CGearSlot\u003E();\n\n\tpublic static string ItemArt( int src ) =\u003E $\u0022{Art}ss/item/{src}.png\u0022;\n\n\tpublic static string SlotName( GearSlot s ) =\u003E s switch\n\t{\n\t\tGearSlot.Helm =\u003E \u0022helmets\u0022,\n\t\tGearSlot.Chest =\u003E \u0022chest\u0022,\n\t\tGearSlot.Shoulders =\u003E \u0022shoulders\u0022,\n\t\tGearSlot.Gauntlets =\u003E \u0022gauntlets\u0022,\n\t\tGearSlot.Thighs =\u003E \u0022thighs\u0022,\n\t\tGearSlot.Greaves =\u003E \u0022greaves\u0022,\n\t\tGearSlot.Boots =\u003E \u0022boots\u0022,\n\t\t_ =\u003E \u0022shields\u0022\n\t};\n\n\tpublic static readonly RaceDef[] Races =\n\t{\n\t\tnew( \u0022Human\u0022, new[] { \u0022#f6c8a0\u0022, \u0022#e8b088\u0022, \u0022#c8925e\u0022, \u0022#9a6a44\u0022, \u0022#6a4630\u0022, \u0022#fde2cc\u0022 } ),\n\t\tnew( \u0022Elf\u0022, new[] { \u0022#fde8d6\u0022, \u0022#f4d6ba\u0022, \u0022#e8dcf0\u0022, \u0022#dcc8a8\u0022 } ),\n\t\tnew( \u0022Orc\u0022, new[] { \u0022#86ba52\u0022, \u0022#64944a\u0022, \u0022#98a868\u0022, \u0022#5a8a64\u0022 } ),\n\t\tnew( \u0022Devil\u0022, new[] { \u0022#e0403a\u0022, \u0022#b82a34\u0022, \u0022#ee6a54\u0022, \u0022#9a2a5a\u0022 } ),\n\t\tnew( \u0022Undead\u0022, new[] { \u0022#b4c4b4\u0022, \u0022#a8b4cc\u0022, \u0022#ccd4bc\u0022, \u0022#98a898\u0022 } ),\n\t};\n\n\tpublic static int HairCount =\u003E GearData.HairCount;\n\tpublic static int BeardCount =\u003E GearData.BeardCount;\n\tpublic static readonly string[] HairColours = { \u0022#2a2a2a\u0022, \u0022#4a2c14\u0022, \u0022#7a4a1e\u0022, \u0022#b07032\u0022, \u0022#d04a24\u0022, \u0022#f0d070\u0022, \u0022#f8f8f8\u0022, \u0022#9a9a9a\u0022, \u0022#4a7ae0\u0022, \u0022#36ac52\u0022, \u0022#b04ae0\u0022, \u0022#ec5a98\u0022 };\n\tpublic static readonly string[] ClothColours = { \u0022#e02a2a\u0022, \u0022#3a6ad8\u0022, \u0022#36a844\u0022, \u0022#f0c048\u0022, \u0022#8a46de\u0022, \u0022#2a2a2a\u0022, \u0022#f6f6f6\u0022, \u0022#ea8a34\u0022 };\n\n\t// ---------------- magic ----------------\n\n\tpublic static readonly SpellDef[] Spells =\n\t{\n\t\tnew( Spell.Fireball, \u0022Fireball\u0022, 15, 1, 150, \u002213\u0022, \u0022Hurls a ball of fire. Damage grows with magicka.\u0022 ),\n\t\tnew( Spell.Heal, \u0022Heal\u0022, 20, 1, 200, \u0022102\u0022, \u0022Heals your wounds (in a duel it mends your armour).\u0022 ),\n\t\tnew( Spell.Freeze, \u0022Freeze\u0022, 20, 3, 450, \u0022137\u0022, \u0022Freezes your opponent solid - they lose their next turn.\u0022 ),\n\t\tnew( Spell.Tremor, \u0022Tremor\u0022, 22, 4, 600, \u0022114\u0022, \u0022Shakes the ground and smashes armour to pieces.\u0022 ),\n\t\tnew( Spell.Drain, \u0022Drain Life\u0022, 25, 5, 900, \u0022145\u0022, \u0022Steals life from your opponent and gives it to you.\u0022 ),\n\t\tnew( Spell.Lightning, \u0022Lightning\u0022, 32, 8, 1500, \u002286\u0022, \u0022A huge bolt from the sky. Very hard to survive.\u0022 ),\n\t};\n\n\tpublic static SpellDef SpellInfo( Spell s ) =\u003E Spells.First( x =\u003E x.Id == s );\n\n\tpublic static int PotionPrice( Potion p ) =\u003E p switch { Potion.Health =\u003E 60, Potion.Energy =\u003E 40, _ =\u003E 50 };\n\tpublic static string PotionName( Potion p ) =\u003E p switch { Potion.Health =\u003E \u0022Health Potion\u0022, Potion.Energy =\u003E \u0022Energy Potion\u0022, _ =\u003E \u0022Magicka Potion\u0022 };\n\tpublic static string PotionArt( Potion p ) =\u003E p switch { Potion.Health =\u003E \u0022ss/item/65.png\u0022, Potion.Energy =\u003E \u0022ss/item/73.png\u0022, _ =\u003E \u0022ss/item/69.png\u0022 };\n\tpublic static string PotionHelp( Potion p ) =\u003E p switch\n\t{\n\t\tPotion.Health =\u003E \u0022Drink in a fight to get back 35% of your health (in a duel: mends 35% of your armour).\u0022,\n\t\tPotion.Energy =\u003E \u0022Drink in a fight to get back 40 energy.\u0022,\n\t\t_ =\u003E \u0022Drink in a fight to get back 30 magicka.\u0022\n\t};\n\n\tpublic static string EnchantColour( Enchant e ) =\u003E e switch\n\t{\n\t\tEnchant.Bleed =\u003E \u0022#ff4a4a\u0022,\n\t\tEnchant.Poison =\u003E \u0022#6aff4a\u0022,\n\t\tEnchant.Death =\u003E \u0022#d8d0ff\u0022,\n\t\tEnchant.Freeze =\u003E \u0022#7fd0ff\u0022,\n\t\t_ =\u003E \u0022#ffffff\u0022\n\t};\n\n\tpublic static (int Min, int Max) EnchantRange( int tier ) =\u003E tier switch\n\t{\n\t\t1 =\u003E (4, 8),\n\t\t2 =\u003E (9, 13),\n\t\t3 =\u003E (14, 18),\n\t\t_ =\u003E (0, 0)\n\t};\n\n\tpublic static int EnchantPrice( int tier ) =\u003E tier switch { 1 =\u003E 300, 2 =\u003E 900, 3 =\u003E 2000, _ =\u003E 0 };\n\tpublic static string EnchantTierName( int tier ) =\u003E tier switch { 1 =\u003E \u0022Weak\u0022, 2 =\u003E \u0022Strong\u0022, 3 =\u003E \u0022Mighty\u0022, _ =\u003E \u0022\u0022 };\n\tpublic static string EnchantIcon( Enchant e ) =\u003E e switch { Enchant.Bleed =\u003E \u00221121\u0022, Enchant.Poison =\u003E \u0022145\u0022, Enchant.Death =\u003E \u0022141\u0022, _ =\u003E \u0022137\u0022 };\n\n\t// ---------------- stats ----------------\n\n\tpublic static int MaxHealth( int vitality ) =\u003E 50 \u002B vitality * 10;\n\tpublic static int MaxEnergy( int stamina ) =\u003E 40 \u002B stamina * 10;\n\tpublic static int MaxMagicka( int magicka ) =\u003E 10 \u002B magicka * 10;\n\tpublic static int Discount( int price, int charisma ) =\u003E (int)(price * (1f - Math.Min( 0.2f, charisma * 0.01f )));\n\n\t/// \u003Csummary\u003EDuels to win as a slave before the real arena opens.\u003C/summary\u003E\n\tpublic const int DuelsToFreedom = 3;\n\n\tpublic static readonly string[] EnemyNames =\n\t{\n\t\t\u0022Gorvak the Grim\u0022, \u0022Lucius Ironhand\u0022, \u0022Varro the Bold\u0022, \u0022Titus Crane\u0022, \u0022Mordo Skullsplitter\u0022, \u0022Cassius Vane\u0022, \u0022Brakka\u0022, \u0022Old Septimus\u0022,\n\t\t\u0022Hrothgar Bonecrusher\u0022, \u0022Silent Decimus\u0022, \u0022Ulfar Redbeard\u0022, \u0022Marcus the Mad\u0022, \u0022Grimtusk\u0022, \u0022Aulus Vex\u0022, \u0022Kragg\u0022, \u0022Nero Ashborn\u0022,\n\t\t\u0022Belisar\u0022, \u0022The Pale Twins\u0022, \u0022Draven Hollowsong\u0022, \u0022Severus Thorn\u0022, \u0022Zug Zug\u0022, \u0022Fausta the Fierce\u0022, \u0022Malakor\u0022, \u0022Rufus Blackwater\u0022\n\t};\n\n\tpublic static readonly string[] PrisonerNames =\n\t{\n\t\t\u0022Smelly Bob\u0022, \u0022Grubb\u0022, \u0022Toothless Tam\u0022, \u0022Wretched Ollo\u0022, \u0022Pip the Thief\u0022, \u0022Mad Mungo\u0022, \u0022Little Bran\u0022, \u0022Old Fennick\u0022,\n\t\t\u0022Slop\u0022, \u0022Nervous Ned\u0022, \u0022Crooked Kel\u0022, \u0022Wheezy Will\u0022, \u0022Gristle\u0022, \u0022Dirty Dunstan\u0022, \u0022Rat-Eye Rolo\u0022, \u0022Bald Benedict\u0022\n\t};\n\n\tpublic static readonly string[] PrisonerTitles = { \u0022Fearful prisoner\u0022, \u0022Starving prisoner\u0022, \u0022Desperate prisoner\u0022, \u0022Angry prisoner\u0022, \u0022Crazed prisoner\u0022 };\n}\n"},{"Ident":"farten.shoes_and_daggers","Path":"Game/FitDefaults.cs","FileName":"FitDefaults.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":443203,"IsPrivate":false,"Code":"// Generated (tools/build.py): where every S\u0026S2 piece sits on its bone, before the Fitting Room changes it.\n// Key -\u003E (X, Y, Scale, Turn, Flip). X/Y = centre of the image from the bone\u0027s joint, in rig pixels,\n// for the screen-left side; the right side is mirrored. Turn in degrees clockwise.\npublic static class FitDefaults\n{\n\tpublic static readonly Dictionary\u003Cstring, FitEntry\u003E Values = new()\n\t{\n\t\t[\u0022beard.1\u0022] = new( 0.0f, -13.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022beard.10\u0022] = new( 0.0f, -15.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022beard.11\u0022] = new( 0.0f, -9.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022beard.12\u0022] = new( 0.0f, -10.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022beard.13\u0022] = new( 0.0f, -20.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022beard.14\u0022] = new( 0.0f, -7.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022beard.15\u0022] = new( 0.0f, -34.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022beard.16\u0022] = new( 0.0f, -14.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022beard.17\u0022] = new( 0.0f, -30.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022beard.18\u0022] = new( 0.0f, -12.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022beard.19\u0022] = new( 0.0f, -30.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022beard.2\u0022] = new( 0.0f, -18.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022beard.20\u0022] = new( 0.0f, -18.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022beard.21\u0022] = new( 0.0f, -15.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022beard.22\u0022] = new( 0.0f, -34.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022beard.3\u0022] = new( 0.0f, -14.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022beard.4\u0022] = new( 0.0f, -7.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022beard.5\u0022] = new( 0.0f, -23.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022beard.6\u0022] = new( 0.0f, -34.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022beard.7\u0022] = new( 0.0f, -16.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022beard.8\u0022] = new( 0.0f, -34.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022beard.9\u0022] = new( 0.0f, -36.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022body.farm\u0022] = new( -3.5f, 42.4f, 1.0f, 0.0f, false ),\n\t\t[\u0022body.foot\u0022] = new( 0.0f, 7.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022body.head\u0022] = new( 0.0f, -49.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022body.shin\u0022] = new( -7.5f, 43.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022body.thigh\u0022] = new( -1.4f, 47.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022body.torso\u0022] = new( 0.0f, -50.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022body.uarm\u0022] = new( 1.7f, 45.7f, 1.0f, -13.9f, false ),\n\t\t[\u0022boot.1\u0022] = new( 0.0f, 9.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022boot.10\u0022] = new( 0.0f, 3.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022boot.11\u0022] = new( 0.0f, -2.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022boot.12\u0022] = new( 0.0f, -3.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022boot.13\u0022] = new( 0.0f, -2.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022boot.14\u0022] = new( 0.0f, -6.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022boot.15\u0022] = new( 0.0f, -4.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022boot.16\u0022] = new( 0.0f, -4.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022boot.17\u0022] = new( 0.0f, -2.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022boot.18\u0022] = new( 0.0f, -3.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022boot.19\u0022] = new( 0.0f, -5.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022boot.2\u0022] = new( 0.0f, 8.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022boot.3\u0022] = new( 0.0f, 8.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022boot.4\u0022] = new( 0.0f, 6.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022boot.5\u0022] = new( 0.0f, 8.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022boot.6\u0022] = new( 0.0f, 7.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022boot.7\u0022] = new( 0.0f, 4.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022boot.8\u0022] = new( 0.0f, 1.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022boot.9\u0022] = new( 0.0f, 0.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022chest.1\u0022] = new( 0.0f, -65.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022chest.10\u0022] = new( 0.0f, -46.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022chest.11\u0022] = new( 0.0f, -59.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022chest.12\u0022] = new( 0.0f, -59.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022chest.13\u0022] = new( 0.0f, -43.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022chest.14\u0022] = new( 0.0f, -59.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022chest.15\u0022] = new( 0.0f, -46.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022chest.16\u0022] = new( 0.0f, -64.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022chest.17\u0022] = new( 0.0f, -56.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022chest.18\u0022] = new( 0.0f, -64.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022chest.19\u0022] = new( 0.0f, -46.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022chest.2\u0022] = new( 0.0f, -61.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022chest.20\u0022] = new( 0.0f, -48.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022chest.21\u0022] = new( 0.0f, -59.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022chest.22\u0022] = new( 0.0f, -46.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022chest.23\u0022] = new( 0.0f, -42.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022chest.24\u0022] = new( 0.0f, -49.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022chest.3\u0022] = new( 0.0f, -65.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022chest.4\u0022] = new( 0.0f, -62.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022chest.5\u0022] = new( 0.0f, -60.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022chest.6\u0022] = new( 0.0f, -65.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022chest.7\u0022] = new( 0.0f, -55.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022chest.8\u0022] = new( 0.0f, -65.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022chest.9\u0022] = new( 0.0f, -46.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022face\u0022] = new( 0.0f, -49.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022gauntlet.1\u0022] = new( -3.5f, 46.9f, 1.0f, 0.0f, false ),\n\t\t[\u0022gauntlet.10\u0022] = new( -3.5f, 51.9f, 1.0f, 0.0f, false ),\n\t\t[\u0022gauntlet.11\u0022] = new( -3.5f, 43.9f, 1.0f, 0.0f, false ),\n\t\t[\u0022gauntlet.12\u0022] = new( -3.5f, 34.4f, 1.0f, 0.0f, false ),\n\t\t[\u0022gauntlet.13\u0022] = new( -3.5f, 43.4f, 1.0f, 0.0f, false ),\n\t\t[\u0022gauntlet.14\u0022] = new( -3.5f, 41.4f, 1.0f, 0.0f, false ),\n\t\t[\u0022gauntlet.15\u0022] = new( -3.5f, 35.9f, 1.0f, 0.0f, false ),\n\t\t[\u0022gauntlet.16\u0022] = new( -3.5f, 41.4f, 1.0f, 0.0f, false ),\n\t\t[\u0022gauntlet.17\u0022] = new( -3.5f, 34.9f, 1.0f, 0.0f, false ),\n\t\t[\u0022gauntlet.18\u0022] = new( -3.5f, 41.4f, 1.0f, 0.0f, false ),\n\t\t[\u0022gauntlet.19\u0022] = new( -3.5f, 38.9f, 1.0f, 0.0f, false ),\n\t\t[\u0022gauntlet.2\u0022] = new( -3.5f, 46.9f, 1.0f, 0.0f, false ),\n\t\t[\u0022gauntlet.20\u0022] = new( -3.5f, 41.4f, 1.0f, 0.0f, false ),\n\t\t[\u0022gauntlet.21\u0022] = new( -3.5f, 34.9f, 1.0f, 0.0f, false ),\n\t\t[\u0022gauntlet.22\u0022] = new( -3.5f, 38.9f, 1.0f, 0.0f, false ),\n\t\t[\u0022gauntlet.23\u0022] = new( -3.5f, 39.4f, 1.0f, 0.0f, false ),\n\t\t[\u0022gauntlet.24\u0022] = new( -3.5f, 28.4f, 1.0f, 0.0f, false ),\n\t\t[\u0022gauntlet.3\u0022] = new( -3.5f, 43.4f, 1.0f, 0.0f, false ),\n\t\t[\u0022gauntlet.4\u0022] = new( -3.5f, 41.4f, 1.0f, 0.0f, false ),\n\t\t[\u0022gauntlet.5\u0022] = new( -3.5f, 51.9f, 1.0f, 0.0f, false ),\n\t\t[\u0022gauntlet.6\u0022] = new( -3.5f, 35.9f, 1.0f, 0.0f, false ),\n\t\t[\u0022gauntlet.7\u0022] = new( -3.5f, 42.4f, 1.0f, 0.0f, false ),\n\t\t[\u0022gauntlet.8\u0022] = new( -3.5f, 39.4f, 1.0f, 0.0f, false ),\n\t\t[\u0022gauntlet.9\u0022] = new( -3.5f, 44.4f, 1.0f, 0.0f, false ),\n\t\t[\u0022greave.1\u0022] = new( -7.5f, 34.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022greave.10\u0022] = new( -7.5f, 34.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022greave.11\u0022] = new( -7.5f, 45.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022greave.12\u0022] = new( -7.5f, 38.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022greave.13\u0022] = new( -7.5f, 47.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022greave.14\u0022] = new( -7.5f, 46.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022greave.15\u0022] = new( -7.5f, 47.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022greave.16\u0022] = new( -7.5f, 46.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022greave.17\u0022] = new( -7.5f, 49.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022greave.2\u0022] = new( -7.5f, 30.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022greave.3\u0022] = new( -7.5f, 34.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022greave.4\u0022] = new( -7.5f, 34.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022greave.5\u0022] = new( -7.5f, 33.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022greave.6\u0022] = new( -7.5f, 38.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022greave.7\u0022] = new( -7.5f, 35.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022greave.8\u0022] = new( -7.5f, 38.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022greave.9\u0022] = new( -7.5f, 44.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.1\u0022] = new( 0.0f, -81.6f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.10\u0022] = new( 0.0f, -89.2f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.11\u0022] = new( 0.0f, -90.3f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.12\u0022] = new( 0.0f, -80.4f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.13\u0022] = new( 0.0f, -48.9f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.14\u0022] = new( 0.0f, -90.7f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.15\u0022] = new( 0.0f, -32.2f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.16\u0022] = new( 0.0f, -92.8f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.17\u0022] = new( 0.0f, -58.4f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.18\u0022] = new( 0.0f, -87.3f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.19\u0022] = new( 0.0f, -56.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.2\u0022] = new( 0.0f, -53.1f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.20\u0022] = new( 0.0f, -63.3f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.21\u0022] = new( 0.0f, -54.6f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.22\u0022] = new( 0.0f, -76.9f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.23\u0022] = new( 0.0f, -63.6f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.24\u0022] = new( 0.0f, -56.8f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.25\u0022] = new( 0.0f, -50.4f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.26\u0022] = new( 0.0f, -61.4f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.27\u0022] = new( 0.0f, -84.8f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.28\u0022] = new( 0.0f, -85.4f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.29\u0022] = new( 0.0f, -53.8f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.3\u0022] = new( 0.0f, -96.6f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.30\u0022] = new( 0.0f, -83.1f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.31\u0022] = new( 0.0f, -74.7f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.32\u0022] = new( 0.0f, -45.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.33\u0022] = new( 0.0f, -71.7f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.34\u0022] = new( 0.0f, -64.9f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.35\u0022] = new( 0.0f, -80.4f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.36\u0022] = new( 0.0f, -59.9f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.37\u0022] = new( 0.0f, -89.9f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.4\u0022] = new( 0.0f, -63.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.5\u0022] = new( 0.0f, -59.2f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.6\u0022] = new( 0.0f, -50.8f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.7\u0022] = new( 0.0f, -57.6f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.8\u0022] = new( 0.0f, -87.8f, 1.0f, 0.0f, false ),\n\t\t[\u0022hair.9\u0022] = new( 0.0f, -83.1f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.1\u0022] = new( 0.0f, -80.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.10\u0022] = new( 0.0f, -58.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.11\u0022] = new( 0.0f, -66.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.12\u0022] = new( 0.0f, -58.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.13\u0022] = new( 0.0f, -88.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.14\u0022] = new( 0.0f, -66.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.15\u0022] = new( 0.0f, -59.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.16\u0022] = new( 0.0f, -67.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.17\u0022] = new( 0.0f, -84.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.18\u0022] = new( 0.0f, -56.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.19\u0022] = new( 0.0f, -66.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.2\u0022] = new( 0.0f, -78.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.20\u0022] = new( 0.0f, -95.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.21\u0022] = new( 0.0f, -66.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.22\u0022] = new( 0.0f, -59.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.23\u0022] = new( 0.0f, -80.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.24\u0022] = new( 0.0f, -73.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.25\u0022] = new( 0.0f, -69.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.26\u0022] = new( 0.0f, -85.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.27\u0022] = new( 0.0f, -85.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.28\u0022] = new( 0.0f, -78.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.29\u0022] = new( 0.0f, -81.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.3\u0022] = new( 0.0f, -68.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.30\u0022] = new( 0.0f, -77.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.31\u0022] = new( 0.0f, -85.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.32\u0022] = new( 0.0f, -75.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.33\u0022] = new( 0.0f, -72.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.34\u0022] = new( 0.0f, -73.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.35\u0022] = new( 0.0f, -75.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.36\u0022] = new( 0.0f, -104.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.37\u0022] = new( 0.0f, -80.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.38\u0022] = new( 0.0f, -92.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.4\u0022] = new( 0.0f, -89.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.5\u0022] = new( 0.0f, -69.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.6\u0022] = new( 0.0f, -54.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.7\u0022] = new( 0.0f, -64.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.8\u0022] = new( 0.0f, -50.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022helm.9\u0022] = new( 0.0f, -50.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022joint.ankle\u0022] = new( -11.0f, 82.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022joint.elbow\u0022] = new( -0.8f, 87.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022joint.hand\u0022] = new( 0.3f, 76.4f, 1.0f, 0.0f, false ),\n\t\t[\u0022joint.hip\u0022] = new( -26.0f, 4.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022joint.knee\u0022] = new( -1.9f, 88.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022joint.neck\u0022] = new( 0.0f, -136.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022joint.root\u0022] = new( 0.0f, -196.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022joint.shield\u0022] = new( 0.3f, 66.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022joint.shoulder\u0022] = new( -56.0f, -124.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022race.devil\u0022] = new( 0.0f, -55.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022race.elf\u0022] = new( 0.0f, -51.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022race.orc\u0022] = new( 0.0f, -38.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022race.undead\u0022] = new( 0.0f, -63.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shield.1\u0022] = new( 0.0f, 0.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shield.10\u0022] = new( 0.0f, 0.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shield.11\u0022] = new( 0.0f, 0.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shield.12\u0022] = new( 0.0f, 0.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shield.13\u0022] = new( 0.0f, 0.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shield.14\u0022] = new( 0.0f, 0.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shield.15\u0022] = new( 0.0f, 0.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shield.16\u0022] = new( 0.0f, 0.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shield.17\u0022] = new( 0.0f, 0.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shield.18\u0022] = new( 0.0f, 0.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shield.19\u0022] = new( 0.0f, 0.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shield.2\u0022] = new( 0.0f, 0.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shield.20\u0022] = new( 0.0f, 0.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shield.21\u0022] = new( 0.0f, 0.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shield.22\u0022] = new( 0.0f, 0.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shield.23\u0022] = new( 0.0f, 0.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shield.3\u0022] = new( 0.0f, 0.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shield.4\u0022] = new( 0.0f, 0.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shield.5\u0022] = new( 0.0f, 0.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shield.6\u0022] = new( 0.0f, 0.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shield.7\u0022] = new( 0.0f, 0.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shield.8\u0022] = new( 0.0f, 0.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shield.9\u0022] = new( 0.0f, 0.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.1\u0022] = new( 0.0f, 27.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.10\u0022] = new( 0.0f, 39.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.11\u0022] = new( 0.0f, 31.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.12\u0022] = new( 0.0f, 29.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.13\u0022] = new( 0.0f, 27.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.14\u0022] = new( 0.0f, 27.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.15\u0022] = new( 0.0f, 49.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.16\u0022] = new( 0.0f, 27.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.17\u0022] = new( 0.0f, 42.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.18\u0022] = new( 0.0f, 33.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.19\u0022] = new( 0.0f, 33.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.2\u0022] = new( 0.0f, 22.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.20\u0022] = new( 0.0f, 42.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.21\u0022] = new( 0.0f, 40.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.22\u0022] = new( 0.0f, 40.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.23\u0022] = new( 0.0f, 72.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.24\u0022] = new( 0.0f, 61.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.25\u0022] = new( 0.0f, 65.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.26\u0022] = new( 0.0f, 56.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.27\u0022] = new( 0.0f, 59.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.3\u0022] = new( 0.0f, 23.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.4\u0022] = new( 0.0f, 22.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.5\u0022] = new( 0.0f, 25.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.6\u0022] = new( 0.0f, 28.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.7\u0022] = new( 0.0f, 22.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.8\u0022] = new( 0.0f, 22.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022shoulder.9\u0022] = new( 0.0f, 28.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022thigh.1\u0022] = new( -1.4f, 35.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022thigh.10\u0022] = new( -1.4f, 45.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022thigh.2\u0022] = new( -1.4f, 39.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022thigh.3\u0022] = new( -1.4f, 35.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022thigh.4\u0022] = new( -1.4f, 41.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022thigh.5\u0022] = new( -1.4f, 35.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022thigh.6\u0022] = new( -1.4f, 35.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022thigh.7\u0022] = new( -1.4f, 45.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022thigh.8\u0022] = new( -1.4f, 41.0f, 1.0f, 0.0f, false ),\n\t\t[\u0022thigh.9\u0022] = new( -1.4f, 35.5f, 1.0f, 0.0f, false ),\n\t\t[\u0022weapon.1\u0022] = new( 0.0f, 82.7f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.10\u0022] = new( 0.0f, 71.0f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.11\u0022] = new( 0.0f, 99.5f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.12\u0022] = new( 0.0f, 78.8f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.13\u0022] = new( 0.0f, 71.0f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.14\u0022] = new( 0.0f, 92.8f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.15\u0022] = new( 0.0f, 122.8f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.16\u0022] = new( 0.0f, 104.1f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.17\u0022] = new( 0.0f, 99.1f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.18\u0022] = new( 0.0f, 103.3f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.19\u0022] = new( 0.0f, 113.5f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.2\u0022] = new( 0.0f, 56.9f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.20\u0022] = new( 0.0f, 101.8f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.21\u0022] = new( 0.0f, 117.4f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.22\u0022] = new( 0.0f, 148.2f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.23\u0022] = new( 0.0f, 129.1f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.24\u0022] = new( 0.0f, 128.7f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.25\u0022] = new( 0.0f, 76.4f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.26\u0022] = new( 0.0f, 98.7f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.27\u0022] = new( 0.0f, 103.7f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.28\u0022] = new( 0.0f, 132.6f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.29\u0022] = new( 0.0f, 149.8f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.3\u0022] = new( 0.0f, 48.5f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.30\u0022] = new( 0.0f, 148.2f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.31\u0022] = new( 0.0f, 104.5f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.32\u0022] = new( 0.0f, 137.7f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.33\u0022] = new( 0.0f, 110.4f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.34\u0022] = new( 0.0f, 156.0f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.35\u0022] = new( 0.0f, 148.2f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.36\u0022] = new( 0.0f, 113.5f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.37\u0022] = new( 0.0f, 87.4f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.38\u0022] = new( 0.0f, 128.3f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.39\u0022] = new( 0.0f, 139.2f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.4\u0022] = new( 0.0f, 62.0f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.40\u0022] = new( 0.0f, 110.0f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.41\u0022] = new( 0.0f, 101.4f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.42\u0022] = new( 0.0f, 113.5f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.43\u0022] = new( 0.0f, 113.1f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.44\u0022] = new( 0.0f, 139.6f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.45\u0022] = new( 0.0f, 119.0f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.46\u0022] = new( 0.0f, 134.9f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.47\u0022] = new( 0.0f, 168.5f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.48\u0022] = new( 0.0f, 149.8f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.49\u0022] = new( 0.0f, 174.0f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.5\u0022] = new( 0.0f, 53.0f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.50\u0022] = new( 0.0f, 165.0f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.51\u0022] = new( 0.0f, 165.0f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.52\u0022] = new( 0.0f, 221.0f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.53\u0022] = new( 0.0f, 148.2f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.54\u0022] = new( 0.0f, 174.0f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.55\u0022] = new( 0.0f, 214.5f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.56\u0022] = new( 0.0f, 257.5f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.57\u0022] = new( 0.0f, 269.0f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.58\u0022] = new( 0.0f, 199.0f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.59\u0022] = new( 0.0f, 279.0f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.6\u0022] = new( 0.0f, 94.4f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.60\u0022] = new( 0.0f, 245.0f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.61\u0022] = new( 0.0f, 189.5f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.62\u0022] = new( 0.0f, 220.0f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.63\u0022] = new( 0.0f, 136.1f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.7\u0022] = new( 0.0f, 99.1f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.8\u0022] = new( 0.0f, 52.5f, 1.0f, 180.0f, false ),\n\t\t[\u0022weapon.9\u0022] = new( 0.0f, 64.3f, 1.0f, 180.0f, false ),\n\t};\n}\n"},{"Ident":"farten.shoes_and_daggers","Path":"Game/Profile.cs","FileName":"Profile.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":443203,"IsPrivate":false,"Code":"using System;\n\n/// \u003Csummary\u003E\n/// Everything the puppet needs to draw a gladiator. Gear values are item indexes (-1 = nothing worn).\n/// \u003C/summary\u003E\npublic struct Look\n{\n\tpublic int Race;\n\tpublic int Skin;\n\tpublic int Hair;        // 0 = bald, 1.. = S\u0026S2 hairstyles\n\tpublic int Beard;       // 0 = clean shaven\n\tpublic int HairColour;\n\tpublic int Cloth;\n\tpublic int Helm, Chest, Shoulders, Gauntlets, Thighs, Greaves, Boots, Shield;\n\tpublic int Weapon;      // 0 = fists\n\n\tpublic static Look Default =\u003E new()\n\t{\n\t\tHelm = -1, Chest = -1, Shoulders = -1, Gauntlets = -1, Thighs = -1, Greaves = -1, Boots = -1, Shield = -1,\n\t\tHairColour = 1\n\t};\n\n\tpublic string SkinHex\n\t{\n\t\tget\n\t\t{\n\t\t\tvar skins = Catalog.Races[Math.Clamp( Race, 0, Catalog.Races.Length - 1 )].Skins;\n\t\t\treturn skins[Math.Clamp( Skin, 0, skins.Length - 1 )];\n\t\t}\n\t}\n\tpublic string HairHex =\u003E Catalog.HairColours[Math.Clamp( HairColour, 0, Catalog.HairColours.Length - 1 )];\n\tpublic string ClothHex =\u003E Catalog.ClothColours[Math.Clamp( Cloth, 0, Catalog.ClothColours.Length - 1 )];\n\tpublic string RaceName =\u003E Catalog.Races[Math.Clamp( Race, 0, Catalog.Races.Length - 1 )].Name;\n\n\tpublic int Get( GearSlot s ) =\u003E s switch\n\t{\n\t\tGearSlot.Helm =\u003E Helm,\n\t\tGearSlot.Chest =\u003E Chest,\n\t\tGearSlot.Shoulders =\u003E Shoulders,\n\t\tGearSlot.Gauntlets =\u003E Gauntlets,\n\t\tGearSlot.Thighs =\u003E Thighs,\n\t\tGearSlot.Greaves =\u003E Greaves,\n\t\tGearSlot.Boots =\u003E Boots,\n\t\t_ =\u003E Shield\n\t};\n\n\tpublic void Set( GearSlot s, int item )\n\t{\n\t\tswitch ( s )\n\t\t{\n\t\t\tcase GearSlot.Helm: Helm = item; break;\n\t\t\tcase GearSlot.Chest: Chest = item; break;\n\t\t\tcase GearSlot.Shoulders: Shoulders = item; break;\n\t\t\tcase GearSlot.Gauntlets: Gauntlets = item; break;\n\t\t\tcase GearSlot.Thighs: Thighs = item; break;\n\t\t\tcase GearSlot.Greaves: Greaves = item; break;\n\t\t\tcase GearSlot.Boots: Boots = item; break;\n\t\t\tdefault: Shield = item; break;\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003EArmour from everything worn.\u003C/summary\u003E\n\tpublic int ArmourPoints()\n\t{\n\t\tint total = 0;\n\t\tforeach ( var s in Catalog.Slots )\n\t\t{\n\t\t\tvar g = Catalog.GearItem( s, Get( s ) );\n\t\t\tif ( g is not null ) total \u002B= g.Points;\n\t\t}\n\t\treturn total;\n\t}\n}\n\npublic class WeaponEnchant\n{\n\tpublic int Weapon { get; set; }\n\tpublic Enchant Type { get; set; }\n\tpublic int Tier { get; set; }\n}\n\npublic class Profile\n{\n\tpublic const string SaveFile = \u0022gladiator.json\u0022;\n\tpublic const int CurrentVersion = 2;\n\n\tpublic int SaveVersion { get; set; } = CurrentVersion;\n\tpublic string Name { get; set; } = \u0022Gladiator\u0022;\n\tpublic int Level { get; set; } = 1;\n\tpublic int Gold { get; set; } = 60;\n\n\t/// \u003Csummary\u003EEvery stat starts at this, with \u003Csee cref=\u0022StartPoints\u0022/\u003E to spend on top (like the Flash game).\u003C/summary\u003E\n\tpublic const int StartStat = 1;\n\tpublic const int StartPoints = 9;\n\tpublic const int StatCount = 8;\n\n\tpublic int StatPoints { get; set; } = StartPoints;\n\t/// \u003Csummary\u003EEveryone starts as a slave: dungeon duels until \u003Csee cref=\u0022Catalog.DuelsToFreedom\u0022/\u003E are won.\u003C/summary\u003E\n\tpublic bool Enslaved { get; set; } = true;\n\tpublic int DuelWins { get; set; }\n\tpublic int Wins { get; set; }\n\tpublic int Losses { get; set; }\n\n\tpublic int Strength { get; set; } = StartStat;\n\tpublic int Attack { get; set; } = StartStat;\n\tpublic int Defence { get; set; } = StartStat;\n\tpublic int Vitality { get; set; } = StartStat;\n\tpublic int Agility { get; set; } = StartStat;\n\tpublic int Stamina { get; set; } = StartStat;\n\tpublic int Charisma { get; set; } = StartStat;\n\tpublic int Magicka { get; set; } = StartStat;\n\n\tpublic int Race { get; set; }\n\tpublic int Skin { get; set; }\n\tpublic int Hair { get; set; }\n\tpublic int Beard { get; set; }\n\tpublic int HairColour { get; set; } = 1;\n\tpublic int Cloth { get; set; }\n\n\tpublic int Weapon { get; set; } = 1;\n\tpublic List\u003Cint\u003E OwnedWeapons { get; set; } = new() { 0, 1 };\n\t/// \u003Csummary\u003EWorn item per slot (index into the slot\u0027s list), -1 = nothing.\u003C/summary\u003E\n\tpublic int[] Gear { get; set; } = NewGear();\n\t/// \u003Csummary\u003E\u0022slot:index\u0022 of everything bought.\u003C/summary\u003E\n\tpublic List\u003Cstring\u003E OwnedGear { get; set; } = new() { \u00221:0\u0022 };\n\tpublic List\u003CWeaponEnchant\u003E Enchants { get; set; } = new();\n\tpublic List\u003Cint\u003E Spells { get; set; } = new();\n\tpublic int[] Potions { get; set; } = new int[3];\n\n\tpublic static Profile Current { get; set; }\n\n\t/// \u003Csummary\u003EA slave starts in rags (the first chest piece) with a wooden club.\u003C/summary\u003E\n\tstatic int[] NewGear()\n\t{\n\t\tvar g = new int[Catalog.Slots.Length];\n\t\tfor ( int i = 0; i \u003C g.Length; i\u002B\u002B ) g[i] = -1;\n\t\tg[(int)GearSlot.Chest] = 0;\n\t\treturn g;\n\t}\n\n\tpublic Look Look\n\t{\n\t\tget\n\t\t{\n\t\t\tvar l = new Look { Race = Race, Skin = Skin, Hair = Hair, Beard = Beard, HairColour = HairColour, Cloth = Cloth, Weapon = Weapon };\n\t\t\tforeach ( var s in Catalog.Slots ) l.Set( s, Gear[(int)s] );\n\t\t\treturn l;\n\t\t}\n\t}\n\n\tpublic void ApplyLook( Look l )\n\t{\n\t\tRace = l.Race; Skin = l.Skin; Hair = l.Hair; Beard = l.Beard; HairColour = l.HairColour; Cloth = l.Cloth;\n\t}\n\n\tpublic int GetStat( int i ) =\u003E i switch\n\t{\n\t\t0 =\u003E Strength, 1 =\u003E Attack, 2 =\u003E Defence, 3 =\u003E Vitality, 4 =\u003E Agility, 5 =\u003E Stamina, 6 =\u003E Charisma, _ =\u003E Magicka\n\t};\n\n\tpublic void AddStat( int i, int amount )\n\t{\n\t\tswitch ( i )\n\t\t{\n\t\t\tcase 0: Strength \u002B= amount; break;\n\t\t\tcase 1: Attack \u002B= amount; break;\n\t\t\tcase 2: Defence \u002B= amount; break;\n\t\t\tcase 3: Vitality \u002B= amount; break;\n\t\t\tcase 4: Agility \u002B= amount; break;\n\t\t\tcase 5: Stamina \u002B= amount; break;\n\t\t\tcase 6: Charisma \u002B= amount; break;\n\t\t\tdefault: Magicka \u002B= amount; break;\n\t\t}\n\t}\n\n\tpublic static readonly string[] StatNames = { \u0022Strength\u0022, \u0022Attack\u0022, \u0022Defence\u0022, \u0022Vitality\u0022, \u0022Agility\u0022, \u0022Stamina\u0022, \u0022Charisma\u0022, \u0022Magicka\u0022 };\n\n\t/// \u003Csummary\u003EThe order the Flash game lists them in.\u003C/summary\u003E\n\tpublic static readonly int[] StatOrder = { 0, 4, 1, 2, 3, 6, 5, 7 };\n\n\tpublic static readonly string[] StatHelp =\n\t{\n\t\t\u0022Strength: \u002B2 damage per point. Unlocks heavier weapons.\u0022,\n\t\t\u0022Attack: better chance to hit.\u0022,\n\t\t\u0022Defence: reduces the damage you take.\u0022,\n\t\t\u0022Vitality: \u002B10 max health per point.\u0022,\n\t\t\u0022Agility: move further, dodge more, act first.\u0022,\n\t\t\u0022Stamina: \u002B10 max energy per point. Resting recovers more.\u0022,\n\t\t\u0022Charisma: stronger taunts, happier crowds, cheaper shops.\u0022,\n\t\t\u0022Magicka: \u002B10 magic per point, stronger spells, and lets you learn better ones.\u0022\n\t};\n\n\tpublic WeaponEnchant EnchantFor( int weapon ) =\u003E Enchants.FirstOrDefault( x =\u003E x.Weapon == weapon );\n\n\tpublic bool Owns( GearSlot slot, int index ) =\u003E OwnedGear.Contains( $\u0022{(int)slot}:{index}\u0022 );\n\n\tpublic int ArmourPoints() =\u003E Look.ArmourPoints();\n\n\tpublic static bool HasSave =\u003E FileSystem.Data.FileExists( SaveFile );\n\n\tpublic void Save() =\u003E FileSystem.Data.WriteJson( SaveFile, this );\n\n\tpublic static Profile Load()\n\t{\n\t\ttry\n\t\t{\n\t\t\tvar p = FileSystem.Data.ReadJson\u003CProfile\u003E( SaveFile );\n\t\t\tif ( p is null ) return null;\n\n\t\t\t// saves from before the S\u0026S2 art: keep the career, reset looks and gear\n\t\t\tif ( p.SaveVersion \u003C CurrentVersion )\n\t\t\t{\n\t\t\t\tp.Hair = 0; p.Beard = 0; p.Race = 0; p.Skin = 0; p.HairColour = 1; p.Cloth = 0;\n\t\t\t\tp.Gear = NewGear();\n\t\t\t\tp.OwnedGear = new() { \u00221:0\u0022 };\n\t\t\t\tp.Weapon = 1;\n\t\t\t\tp.OwnedWeapons = new() { 0, 1 };\n\t\t\t\tp.Enchants = new();\n\t\t\t\tif ( p.Magicka \u003C StartStat ) p.Magicka = StartStat;\n\t\t\t\tp.SaveVersion = CurrentVersion;\n\t\t\t}\n\n\t\t\tif ( p.Gear is null || p.Gear.Length != Catalog.Slots.Length ) p.Gear = NewGear();\n\t\t\tp.OwnedWeapons ??= new() { 0, 1 };\n\t\t\tp.OwnedGear ??= new();\n\t\t\tp.Enchants ??= new();\n\t\t\tp.Spells ??= new();\n\t\t\tif ( p.Potions is null || p.Potions.Length != 3 ) p.Potions = new int[3];\n\t\t\tp.Weapon = Math.Clamp( p.Weapon, 0, Catalog.Weapons.Length - 1 );\n\t\t\tp.Hair = Math.Clamp( p.Hair, 0, Catalog.HairCount );\n\t\t\tp.Beard = Math.Clamp( p.Beard, 0, Catalog.BeardCount );\n\t\t\treturn p;\n\t\t}\n\t\tcatch ( Exception e )\n\t\t{\n\t\t\tLog.Warning( $\u0022Couldn\u0027t load save: {e.Message}\u0022 );\n\t\t\treturn null;\n\t\t}\n\t}\n}\n"},{"Ident":"farten.shoes_and_daggers","Path":"UI/StatsScreen.razor","FileName":"StatsScreen.razor","PackageType":"game","CodeKind":"Game","AssetVersionId":443203,"IsPrivate":false,"Code":"@using System;\n@using Sandbox;\n@using Sandbox.UI;\n\n@* Level up / stats, laid out like the Flash game: heading, stats with \u002B, skill points, tick.\n   Your gladiator stands in the 3D hall. *@\n\u003Croot class=\u0022screen\u0022\u003E\n\t\u003Cdiv class=\u0022head\u0022\u003E\n\t\t\u003Cdiv class=\u0022title outlined\u0022\u003E@(Gm.Heading ?? $\u0022{P.Name}, level {P.Level} {(P.Enslaved ? \u0022slave\u0022 : \u0022gladiator\u0022)}\u0022)\u003C/div\u003E\n\t\t\u003Cdiv class=\u0022sub outlined\u0022\u003E@(Gm.Notice ?? Subtitle)\u003C/div\u003E\n\t\u003C/div\u003E\n\n\t\u003Cdiv class=\u0022sas-panel stats-panel\u0022\u003E\n\t\t\u003CStatsList\u003E\u003C/StatsList\u003E\n\t\u003C/div\u003E\n\n\t\u003Cdiv class=\u0022sas-panel points-panel\u0022\u003E\n\t\t\u003Cdiv class=\u0022sas-label\u0022\u003Eskill points\u003C/div\u003E\n\t\t\u003Cdiv class=\u0022points-number\u0022\u003E@P.StatPoints\u003C/div\u003E\n\t\u003C/div\u003E\n\n\t\u003Cdiv class=\u0022sas-panel derived-panel\u0022\u003E\n\t\t\u003Cdiv\u003E@($\u0022health {Catalog.MaxHealth( P.Vitality )}\u0022)\u003C/div\u003E\n\t\t\u003Cdiv\u003E@($\u0022energy {Catalog.MaxEnergy( P.Stamina )}\u0022)\u003C/div\u003E\n\t\t\u003Cdiv\u003E@($\u0022magicka {Catalog.MaxMagicka( P.Magicka )}\u0022)\u003C/div\u003E\n\t\t\u003Cdiv\u003E@($\u0022armour {P.ArmourPoints()}\u0022)\u003C/div\u003E\n\t\t\u003Cdiv\u003E@($\u0022{Catalog.Weapons[P.Weapon].Name}: {Catalog.Weapons[P.Weapon].Min \u002B P.Strength * 2}-{Catalog.Weapons[P.Weapon].Max \u002B P.Strength * 2}\u0022)\u003C/div\u003E\n\t\u003C/div\u003E\n\n\t\u003Cdiv class=\u0022gold-bar\u0022\u003E\u003Cdiv class=\u0022coin\u0022\u003E\u003C/div\u003E@P.Gold\u003C/div\u003E\n\t\u003Cdiv class=\u0022sas-round tick done\u0022 onclick=@(() =\u003E Gm.Go( Gm.StatsReturn ))\u003E\u003Cdiv class=\u0022mark\u0022\u003E\u003C/div\u003E\u003C/div\u003E\n\u003C/root\u003E\n\n@code\n{\n\tGameManager Gm =\u003E GameManager.Instance;\n\tProfile P =\u003E Profile.Current;\n\n\tstatic readonly string[] Sayings =\n\t{\n\t\t\u0022A journey of a thousand miles begins with a single step.\u0022,\n\t\t\u0022The crowd remembers the bold.\u0022,\n\t\t\u0022Steel sharpens steel.\u0022,\n\t\t\u0022Fortune favours the brave.\u0022\n\t};\n\n\tstring Subtitle =\u003E Sayings[Math.Abs( P.Level ) % Sayings.Length];\n\n\tprotected override int BuildHash() =\u003E HashCode.Combine( P?.StatPoints, P?.Vitality, P?.Stamina, P?.Magicka, P?.Strength, Gm?.Notice, Gm?.Heading );\n}\n"},{"Ident":"farten.shoes_and_daggers","Path":"Flash/Core/AsObject.cs","FileName":"AsObject.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":443203,"IsPrivate":false,"Code":"using System;\nusing System.Collections.Generic;\n\nnamespace Flash;\n\n[Flags]\npublic enum PropFlags : byte { None = 0, DontEnum = 1, DontDelete = 2, ReadOnly = 4 }\n\npublic sealed class Property\n{\n\tpublic Value Value;\n\tpublic PropFlags Flags;\n\tpublic AsFunction Getter, Setter;\n\tpublic bool IsVirtual =\u003E Getter != null || Setter != null;\n}\n\n/// \u003Csummary\u003EA plain ActionScript object (with prototype chain).\u003C/summary\u003E\npublic class AsObject\n{\n\treadonly Dictionary\u003Cstring, Property\u003E props = new();\n\treadonly List\u003Cstring\u003E order = new();\n\tpublic AsObject Proto;\n\t/// \u003Csummary\u003ENative payload for built-in classes (Date ticks, Sound, Color target...).\u003C/summary\u003E\n\tpublic object Native;\n\n\tpublic AsObject() { }\n\tpublic AsObject( AsObject proto ) { Proto = proto; }\n\n\tpublic virtual string ClassName =\u003E \u0022Object\u0022;\n\n\t// --------------------------------------------------------------- own property storage\n\n\tpublic Property GetOwnProperty( string name ) =\u003E props.TryGetValue( name, out var p ) ? p : null;\n\n\tpublic bool HasOwnProperty( string name ) =\u003E props.ContainsKey( name ) || HasNativeProperty( name );\n\n\tpublic void SetOwn( string name, Value v, PropFlags flags = PropFlags.None )\n\t{\n\t\tif ( props.TryGetValue( name, out var p ) ) { p.Value = v; p.Getter = null; p.Setter = null; p.Flags = flags; return; }\n\t\tprops[name] = new Property { Value = v, Flags = flags };\n\t\torder.Add( name );\n\t}\n\n\tpublic void DefineHidden( string name, Value v ) =\u003E SetOwn( name, v, PropFlags.DontEnum );\n\n\tpublic void AddVirtual( string name, AsFunction getter, AsFunction setter, PropFlags flags = PropFlags.None )\n\t{\n\t\tif ( !props.TryGetValue( name, out var p ) ) { p = new Property(); props[name] = p; order.Add( name ); }\n\t\tp.Getter = getter; p.Setter = setter; p.Flags = flags; p.Value = Value.Undefined;\n\t}\n\n\tpublic virtual bool Delete( string name )\n\t{\n\t\tif ( !props.TryGetValue( name, out var p ) ) return false;\n\t\tif ( (p.Flags \u0026 PropFlags.DontDelete) != 0 ) return false;\n\t\tprops.Remove( name );\n\t\torder.Remove( name );\n\t\treturn true;\n\t}\n\n\tpublic void SetFlags( string name, PropFlags set, PropFlags clear )\n\t{\n\t\tif ( props.TryGetValue( name, out var p ) ) p.Flags = (p.Flags | set) \u0026 ~clear;\n\t}\n\n\tpublic IEnumerable\u003Cstring\u003E OwnKeys =\u003E order;\n\n\tpublic int OwnCount =\u003E order.Count;\n\n\t// --------------------------------------------------------------- hooks for special objects\n\n\t/// \u003Csummary\u003EBuilt-in (non-dictionary) properties, e.g. a MovieClip\u0027s _x or an array\u0027s indices.\u003C/summary\u003E\n\tprotected virtual bool TryGetNative( string name, Avm1 vm, out Value v ) { v = default; return false; }\n\tprotected virtual bool TrySetNative( string name, Value v, Avm1 vm ) =\u003E false;\n\tprotected virtual bool HasNativeProperty( string name ) =\u003E false;\n\t/// \u003Csummary\u003ENames to enumerate besides the dictionary (array indices, child clips...).\u003C/summary\u003E\n\tpublic virtual IEnumerable\u003Cstring\u003E NativeKeys =\u003E Array.Empty\u003Cstring\u003E();\n\n\t// --------------------------------------------------------------- lookup\n\n\tpublic Value Get( string name, Avm1 vm ) =\u003E GetWithThis( name, vm, this );\n\n\tpublic Value GetWithThis( string name, Avm1 vm, AsObject thisObj )\n\t{\n\t\tif ( name == \u0022__proto__\u0022 ) return Proto is null ? Value.Undefined : Value.From( Proto );\n\t\tvar o = this;\n\t\tint guard = 0;\n\t\twhile ( o != null \u0026\u0026 guard\u002B\u002B \u003C 256 )\n\t\t{\n\t\t\tif ( o.props.TryGetValue( name, out var p ) )\n\t\t\t{\n\t\t\t\tif ( p.Getter != null ) return vm.CallFunction( p.Getter, Value.From( thisObj ), Array.Empty\u003CValue\u003E() );\n\t\t\t\tif ( p.Setter != null ) return Value.Undefined;\n\t\t\t\treturn p.Value;\n\t\t\t}\n\t\t\tif ( o.TryGetNative( name, vm, out var nv ) ) return nv;\n\t\t\to = o.Proto;\n\t\t}\n\t\treturn Value.Undefined;\n\t}\n\n\tpublic bool Has( string name )\n\t{\n\t\tif ( name == \u0022__proto__\u0022 ) return Proto != null;\n\t\tvar o = this; int guard = 0;\n\t\twhile ( o != null \u0026\u0026 guard\u002B\u002B \u003C 256 )\n\t\t{\n\t\t\tif ( o.props.ContainsKey( name ) || o.HasNativeProperty( name ) ) return true;\n\t\t\to = o.Proto;\n\t\t}\n\t\treturn false;\n\t}\n\n\tpublic virtual void Set( string name, Value v, Avm1 vm )\n\t{\n\t\tif ( name == \u0022__proto__\u0022 ) { Proto = v.AsObj; return; }\n\t\tif ( props.TryGetValue( name, out var p ) )\n\t\t{\n\t\t\tif ( p.Setter != null ) { vm.CallFunction( p.Setter, Value.From( this ), new[] { v } ); return; }\n\t\t\tif ( p.Getter != null ) return;\n\t\t\tif ( (p.Flags \u0026 PropFlags.ReadOnly) != 0 ) return;\n\t\t\tp.Value = v;\n\t\t\treturn;\n\t\t}\n\t\tif ( TrySetNative( name, v, vm ) ) return;\n\t\t// inherited virtual property?\n\t\tvar o = Proto; int guard = 0;\n\t\twhile ( o != null \u0026\u0026 guard\u002B\u002B \u003C 256 )\n\t\t{\n\t\t\tif ( o.props.TryGetValue( name, out var ip ) )\n\t\t\t{\n\t\t\t\tif ( ip.Setter != null ) { vm.CallFunction( ip.Setter, Value.From( this ), new[] { v } ); return; }\n\t\t\t\tif ( ip.Getter != null ) return;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\to = o.Proto;\n\t\t}\n\t\tprops[name] = new Property { Value = v };\n\t\torder.Add( name );\n\t}\n\n\t/// \u003Csummary\u003EAll enumerable names, own first then inherited (for..in).\u003C/summary\u003E\n\tpublic List\u003Cstring\u003E EnumerableKeys()\n\t{\n\t\tvar seen = new HashSet\u003Cstring\u003E();\n\t\tvar list = new List\u003Cstring\u003E();\n\t\tvar o = this; int guard = 0;\n\t\twhile ( o != null \u0026\u0026 guard\u002B\u002B \u003C 256 )\n\t\t{\n\t\t\tforeach ( var k in o.NativeKeys )\n\t\t\t\tif ( seen.Add( k ) ) list.Add( k );\n\t\t\tfor ( int i = o.order.Count - 1; i \u003E= 0; i-- )\n\t\t\t{\n\t\t\t\tvar k = o.order[i];\n\t\t\t\tif ( !seen.Add( k ) ) continue;\n\t\t\t\tif ( (o.props[k].Flags \u0026 PropFlags.DontEnum) == 0 ) list.Add( k );\n\t\t\t}\n\t\t\to = o.Proto;\n\t\t}\n\t\treturn list;\n\t}\n\n\tpublic virtual Value ToPrimitive( Avm1 vm, bool hintString )\n\t{\n\t\tvar first = hintString ? \u0022toString\u0022 : \u0022valueOf\u0022;\n\t\tvar second = hintString ? \u0022valueOf\u0022 : \u0022toString\u0022;\n\t\tvar f = Get( first, vm ).AsObj as AsFunction;\n\t\tif ( f != null )\n\t\t{\n\t\t\tvar r = vm.CallFunction( f, Value.From( this ), Array.Empty\u003CValue\u003E() );\n\t\t\tif ( r.Kind != ValueKind.Object ) return r;\n\t\t}\n\t\tf = Get( second, vm ).AsObj as AsFunction;\n\t\tif ( f != null )\n\t\t{\n\t\t\tvar r = vm.CallFunction( f, Value.From( this ), Array.Empty\u003CValue\u003E() );\n\t\t\tif ( r.Kind != ValueKind.Object ) return r;\n\t\t}\n\t\treturn Value.From( this );\n\t}\n\n\tpublic bool IsInstanceOf( AsObject ctor, Avm1 vm )\n\t{\n\t\tvar proto = ctor.Get( \u0022prototype\u0022, vm ).AsObj;\n\t\tif ( proto is null ) return false;\n\t\tvar o = Proto; int guard = 0;\n\t\twhile ( o != null \u0026\u0026 guard\u002B\u002B \u003C 256 )\n\t\t{\n\t\t\tif ( ReferenceEquals( o, proto ) ) return true;\n\t\t\t// AS2 \u0022implements\u0022: interfaces listed in __implements__\n\t\t\to = o.Proto;\n\t\t}\n\t\treturn false;\n\t}\n}\n\n/// \u003Csummary\u003EAn ActionScript Array.\u003C/summary\u003E\npublic sealed class AsArray : AsObject\n{\n\tpublic readonly List\u003CValue\u003E Items = new();\n\tpublic AsArray( AsObject proto ) : base( proto ) { }\n\tpublic override string ClassName =\u003E \u0022Array\u0022;\n\n\tpublic static bool TryIndex( string name, out int index )\n\t{\n\t\tindex = 0;\n\t\tif ( string.IsNullOrEmpty( name ) || name.Length \u003E 9 ) return false;\n\t\tfor ( int i = 0; i \u003C name.Length; i\u002B\u002B )\n\t\t{\n\t\t\tchar c = name[i];\n\t\t\tif ( c \u003C \u00270\u0027 || c \u003E \u00279\u0027 ) return false;\n\t\t\tif ( i == 0 \u0026\u0026 c == \u00270\u0027 \u0026\u0026 name.Length \u003E 1 ) return false;\n\t\t\tindex = index * 10 \u002B (c - \u00270\u0027);\n\t\t}\n\t\treturn true;\n\t}\n\n\tprotected override bool TryGetNative( string name, Avm1 vm, out Value v )\n\t{\n\t\tif ( name == \u0022length\u0022 ) { v = Items.Count; return true; }\n\t\tif ( TryIndex( name, out int i ) ) { v = i \u003C Items.Count ? Items[i] : Value.Undefined; return true; }\n\t\tv = default; return false;\n\t}\n\n\tprotected override bool TrySetNative( string name, Value v, Avm1 vm )\n\t{\n\t\tif ( name == \u0022length\u0022 )\n\t\t{\n\t\t\tint n = Math.Max( 0, Value.ToInt32( v.ToNumber( vm ) ) );\n\t\t\tif ( n \u003C Items.Count ) Items.RemoveRange( n, Items.Count - n );\n\t\t\telse while ( Items.Count \u003C n ) Items.Add( Value.Undefined );\n\t\t\treturn true;\n\t\t}\n\t\tif ( TryIndex( name, out int i ) )\n\t\t{\n\t\t\twhile ( Items.Count \u003C= i ) Items.Add( Value.Undefined );\n\t\t\tItems[i] = v;\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tprotected override bool HasNativeProperty( string name ) =\u003E name == \u0022length\u0022 || (TryIndex( name, out int i ) \u0026\u0026 i \u003C Items.Count);\n\n\tpublic override bool Delete( string name )\n\t{\n\t\tif ( TryIndex( name, out int i ) \u0026\u0026 i \u003C Items.Count ) { Items[i] = Value.Undefined; return true; }\n\t\treturn base.Delete( name );\n\t}\n\n\tpublic override IEnumerable\u003Cstring\u003E NativeKeys\n\t{\n\t\tget\n\t\t{\n\t\t\tfor ( int i = Items.Count - 1; i \u003E= 0; i-- )\n\t\t\t\tif ( !Items[i].IsUndefined ) yield return i.ToString();\n\t\t}\n\t}\n}\n\n/// \u003Csummary\u003EAnything callable.\u003C/summary\u003E\npublic abstract class AsFunction : AsObject\n{\n\tpublic string Name = \u0022\u0022;\n\tprotected AsFunction( AsObject proto ) : base( proto ) { }\n\tpublic override string ClassName =\u003E \u0022Function\u0022;\n\tpublic abstract Value Call( Avm1 vm, Value thisV, Value[] args, AsObject superCtorOwner = null );\n\n\t/// \u003Csummary\u003Enew F(args)\u003C/summary\u003E\n\tpublic virtual Value Construct( Avm1 vm, Value[] args )\n\t{\n\t\tvar protoV = Get( \u0022prototype\u0022, vm );\n\t\tvar obj = vm.NewObject( protoV.AsObj ?? vm.ObjectProto );\n\t\tobj.DefineHidden( \u0022__constructor__\u0022, Value.From( this ) );\n\t\tif ( vm.SwfVersion \u003C 7 ) obj.DefineHidden( \u0022constructor\u0022, Value.From( this ) );\n\t\tvar r = Call( vm, Value.From( obj ), args, null );\n\t\tif ( r.IsObject ) return r;\n\t\treturn Value.From( obj );\n\t}\n}\n\npublic delegate Value NativeCall( Avm1 vm, Value thisV, Value[] args );\n\npublic sealed class NativeFunction : AsFunction\n{\n\treadonly NativeCall call;\n\t/// \u003Csummary\u003EOptional: when called with new, builds the instance itself (Array, Date...).\u003C/summary\u003E\n\tpublic NativeCall Constructor;\n\n\tpublic NativeFunction( AsObject proto, string name, NativeCall call ) : base( proto ) { Name = name; this.call = call; }\n\n\tpublic override Value Call( Avm1 vm, Value thisV, Value[] args, AsObject superCtorOwner = null ) =\u003E call( vm, thisV, args );\n\n\tpublic override Value Construct( Avm1 vm, Value[] args )\n\t{\n\t\tif ( Constructor != null )\n\t\t{\n\t\t\tvar r = Constructor( vm, Value.Undefined, args );\n\t\t\tif ( r.IsObject \u0026\u0026 !r.Obj.HasOwnProperty( \u0022__constructor__\u0022 ) ) r.Obj.DefineHidden( \u0022__constructor__\u0022, Value.From( this ) );\n\t\t\treturn r;\n\t\t}\n\t\treturn base.Construct( vm, args );\n\t}\n}\n\n/// \u003Csummary\u003EA function defined in bytecode (DefineFunction / DefineFunction2).\u003C/summary\u003E\npublic sealed class BytecodeFunction : AsFunction\n{\n\tpublic byte[] Code;\n\tpublic int Start, Length;\n\tpublic string[] ParamNames;\n\tpublic byte[] ParamRegisters; // DefineFunction2 only (0 = no register)\n\tpublic bool IsFunction2;\n\tpublic int RegisterCount;\n\tpublic ushort Flags2;\n\tpublic string[] ConstantPool;\n\tpublic Scope Scope; // captured scope chain\n\tpublic DisplayObject BaseClip; // timeline the function was defined on\n\tpublic int SwfVersion;\n\n\tpublic BytecodeFunction( AsObject proto ) : base( proto ) { }\n\n\tpublic override Value Call( Avm1 vm, Value thisV, Value[] args, AsObject superCtorOwner = null )\n\t\t=\u003E vm.InvokeBytecode( this, thisV, args, superCtorOwner );\n}\n\n/// \u003Csummary\u003EPrimitive wrapper objects (new String(\u0022x\u0022), (5).toString(), ...).\u003C/summary\u003E\npublic sealed class AsBoxed : AsObject\n{\n\tpublic Value Primitive;\n\tpublic AsBoxed( AsObject proto, Value v ) : base( proto ) { Primitive = v; }\n\tpublic override string ClassName =\u003E Primitive.Kind switch { ValueKind.String =\u003E \u0022String\u0022, ValueKind.Number =\u003E \u0022Number\u0022, _ =\u003E \u0022Boolean\u0022 };\n\n\tprotected override bool TryGetNative( string name, Avm1 vm, out Value v )\n\t{\n\t\tif ( Primitive.IsString \u0026\u0026 name == \u0022length\u0022 ) { v = Primitive.Str.Length; return true; }\n\t\tv = default; return false;\n\t}\n\n\tpublic override Value ToPrimitive( Avm1 vm, bool hintString ) =\u003E Primitive;\n}\n"},{"Ident":"farten.shoes_and_daggers","Path":"Flash/Core/Avm1.cs","FileName":"Avm1.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":443203,"IsPrivate":false,"Code":"using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace Flash;\n\n/// \u003Csummary\u003EA value thrown by ActionScript (throw x;).\u003C/summary\u003E\npublic sealed class AsThrow : Exception\n{\n\tpublic readonly Value Thrown;\n\tpublic AsThrow( Value v ) : base( \u0022ActionScript exception\u0022 ) { Thrown = v; }\n}\n\npublic enum ScopeClass : byte { Global, Target, Local, With }\n\npublic sealed class Scope\n{\n\tpublic readonly AsObject Values;\n\tpublic readonly ScopeClass Class;\n\tpublic readonly Scope Parent;\n\tpublic Scope( AsObject values, ScopeClass cls, Scope parent ) { Values = values; Class = cls; Parent = parent; }\n}\n\n/// \u003Csummary\u003EThe ActionScript 1/2 virtual machine (AVM1).\u003C/summary\u003E\npublic sealed partial class Avm1\n{\n\tpublic readonly Player Player;\n\tpublic int SwfVersion = 11;\n\tpublic AsObject Global;\n\tpublic AsObject ObjectProto, FunctionProto, ArrayProto, StringProto, NumberProto, BooleanProto, DateProto;\n\tpublic AsObject MovieClipProto, TextFieldProto, ButtonProto, SoundProto, ColorProto, SharedObjectProto;\n\tpublic NativeFunction ObjectCtor, FunctionCtor, ArrayCtor;\n\tpublic Random Rng = new();\n\tpublic readonly List\u003Cstring\u003E Log = new();\n\tint callDepth;\n\tconst int MaxCallDepth = 256;\n\tconst long MaxActionsPerCall = 2_000_000;\n\tlong actionBudget;\n\n\tpublic Avm1( Player player )\n\t{\n\t\tPlayer = player;\n\t\tBuiltins.Install( this );\n\t}\n\n\tpublic void Trace( string s )\n\t{\n\t\tLog.Add( s );\n\t\tif ( Log.Count \u003E 500 ) Log.RemoveAt( 0 );\n\t\tPlayer.Host?.Trace( s );\n\t}\n\n\t// ======================================================================= object helpers\n\n\tpublic AsObject NewObject( AsObject proto = null ) =\u003E new AsObject( proto ?? ObjectProto );\n\n\tpublic AsArray NewArray( IEnumerable\u003CValue\u003E items = null )\n\t{\n\t\tvar a = new AsArray( ArrayProto );\n\t\tif ( items != null ) a.Items.AddRange( items );\n\t\treturn a;\n\t}\n\n\tpublic NativeFunction Native( string name, NativeCall call ) =\u003E new NativeFunction( FunctionProto, name, call );\n\n\tpublic void Method( AsObject target, string name, NativeCall call ) =\u003E target.DefineHidden( name, Value.From( Native( name, call ) ) );\n\n\t/// \u003Csummary\u003ETurns a primitive into an object for property access / method calls.\u003C/summary\u003E\n\tpublic AsObject ToObject( Value v )\n\t{\n\t\tswitch ( v.Kind )\n\t\t{\n\t\t\tcase ValueKind.Object: return v.Obj;\n\t\t\tcase ValueKind.String: return new AsBoxed( StringProto, v );\n\t\t\tcase ValueKind.Number: return new AsBoxed( NumberProto, v );\n\t\t\tcase ValueKind.Bool: return new AsBoxed( BooleanProto, v );\n\t\t\tdefault: return null;\n\t\t}\n\t}\n\n\tpublic Value GetMember( Value objV, string name )\n\t{\n\t\tif ( objV.IsNullish ) return Value.Undefined;\n\t\tif ( objV.IsString \u0026\u0026 name == \u0022length\u0022 ) return objV.Str.Length;\n\t\tif ( objV.IsObject \u0026\u0026 objV.Obj is DisplayObject dd \u0026\u0026 dd.Removed )\n\t\t{\n\t\t\tvar live = Live( dd );\n\t\t\tif ( live == null ) return Value.Undefined;\n\t\t\tobjV = Value.From( live );\n\t\t}\n\t\tvar o = objV.IsObject ? objV.Obj : ToObject( objV );\n\t\tif ( o is null ) return Value.Undefined;\n\t\treturn o.GetWithThis( name, this, objV.IsObject ? objV.Obj : o );\n\t}\n\n\tpublic void SetMember( Value objV, string name, Value v )\n\t{\n\t\tif ( !objV.IsObject ) return;\n\t\tvar o = objV.Obj;\n\t\tif ( o is DisplayObject dd \u0026\u0026 dd.Removed ) { o = Live( dd ); if ( o == null ) return; }\n\t\to.Set( name, v, this );\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// Flash keeps clip references as paths: once a clip is removed, a variable pointing at it resolves to whatever\n\t/// now lives at that path (often nothing \u2192 undefined), never to the dead clip\u0027s old values.\n\t/// \u003C/summary\u003E\n\tpublic DisplayObject Live( DisplayObject d )\n\t{\n\t\tif ( d == null || !d.Removed ) return d;\n\t\tvar path = d.DeadPath;\n\t\tif ( string.IsNullOrEmpty( path ) || path == \u0022_level0\u0022 ) return null;\n\t\tvar r = ResolveTargetPath( Player.Root, path );\n\t\treturn r != null \u0026\u0026 !r.Removed \u0026\u0026 !ReferenceEquals( r, Player.Root ) ? r : null;\n\t}\n\n\t// ======================================================================= calling\n\n\tpublic Value CallFunction( AsFunction f, Value thisV, Value[] args )\n\t{\n\t\tif ( f is null ) return Value.Undefined;\n\t\treturn f.Call( this, thisV, args ?? Array.Empty\u003CValue\u003E() );\n\t}\n\n\tpublic Value CallMethod( Value objV, string name, params Value[] args )\n\t{\n\t\tvar f = GetMember( objV, name ).AsObj as AsFunction;\n\t\tif ( f is null ) return Value.Undefined;\n\t\treturn CallFunction( f, objV, args );\n\t}\n\n\tpublic Value Construct( AsFunction ctor, Value[] args )\n\t{\n\t\tif ( ctor is null ) return Value.Undefined;\n\t\treturn ctor.Construct( this, args );\n\t}\n\n\t/// \u003Csummary\u003ERuns a frame script / clip event / button action on a timeline.\u003C/summary\u003E\n\tpublic void RunScript( DisplayObject clip, byte[] code, int start = 0, int length = -1, Value? thisOverride = null )\n\t{\n\t\tif ( code is null || code.Length == 0 ) return;\n\t\tvar scope = new Scope( clip, ScopeClass.Target, new Scope( Global, ScopeClass.Global, null ) );\n\t\tvar ctx = new Ctx\n\t\t{\n\t\t\tCode = code, Scope = scope, This = thisOverride ?? Value.From( clip ), Target = clip, BaseClip = clip,\n\t\t\tRegs = new Value[4], Pool = Array.Empty\u003Cstring\u003E(), Version = SwfVersion,\n\t\t};\n\t\tactionBudget = MaxActionsPerCall;\n\t\ttry\n\t\t{\n\t\t\tcallDepth\u002B\u002B;\n\t\t\tRunRange( ctx, start, length \u003C 0 ? code.Length : start \u002B length );\n\t\t}\n\t\tcatch ( AsThrow t )\n\t\t{\n\t\t\tTrace( \u0022Uncaught exception: \u0022 \u002B t.Thrown.ToStr( this ) );\n\t\t}\n\t\tcatch ( ScriptAbort a )\n\t\t{\n\t\t\tTrace( \u0022Script aborted: \u0022 \u002B a.Message );\n\t\t}\n\t\tfinally { callDepth--; }\n\t}\n\n\tpublic Value InvokeBytecode( BytecodeFunction f, Value thisV, Value[] args, AsObject superOwner )\n\t{\n\t\tif ( callDepth \u003E= MaxCallDepth ) throw new ScriptAbort( \u0022stack overflow in \u0022 \u002B f.Name );\n\t\tvar activation = new AsObject( null );\n\t\tvar scope = new Scope( activation, ScopeClass.Local, f.Scope );\n\t\tvar ctx = new Ctx\n\t\t{\n\t\t\tCode = f.Code, Scope = scope, This = thisV, BaseClip = f.BaseClip, Target = f.BaseClip, Pool = f.ConstantPool,\n\t\t\tVersion = f.SwfVersion, Func = f, Args = args,\n\t\t\tRegs = new Value[f.IsFunction2 ? Math.Max( f.RegisterCount, 1 ) : 4],\n\t\t};\n\t\tif ( ctx.Target != null \u0026\u0026 ctx.Target.Removed \u0026\u0026 thisV.Obj is DisplayObject td \u0026\u0026 !td.Removed ) { ctx.Target = td; ctx.BaseClip = td; }\n\n\t\tvar argsObj = NewArray( args );\n\t\targsObj.DefineHidden( \u0022callee\u0022, Value.From( f ) );\n\t\targsObj.DefineHidden( \u0022caller\u0022, Value.Null );\n\n\t\tAsObject superOwnerProto = null;\n\t\tif ( thisV.IsObject ) superOwnerProto = superOwner ?? thisV.Obj.Proto;\n\t\tSuperObject superObj = superOwnerProto != null ? new SuperObject( this, thisV, superOwnerProto ) : null;\n\n\t\tif ( f.IsFunction2 )\n\t\t{\n\t\t\tushort fl = f.Flags2;\n\t\t\tint r = 1;\n\t\t\t// flag bits: 0 preloadThis,1 suppressThis,2 preloadArgs,3 suppressArgs,4 preloadSuper,5 suppressSuper,6 preloadRoot,7 preloadParent,8 preloadGlobal\n\t\t\tif ( (fl \u0026 0x01) != 0 ) ctx.Regs[r\u002B\u002B] = thisV;\n\t\t\tif ( (fl \u0026 0x04) != 0 ) ctx.Regs[r\u002B\u002B] = Value.From( argsObj );\n\t\t\tif ( (fl \u0026 0x10) != 0 ) ctx.Regs[r\u002B\u002B] = superObj != null ? Value.From( superObj ) : Value.Undefined;\n\t\t\tif ( (fl \u0026 0x40) != 0 ) ctx.Regs[r\u002B\u002B] = Value.From( Player.Root );\n\t\t\tif ( (fl \u0026 0x80) != 0 ) ctx.Regs[r\u002B\u002B] = f.BaseClip?.Parent != null ? Value.From( f.BaseClip.Parent ) : Value.Undefined;\n\t\t\tif ( (fl \u0026 0x100) != 0 ) ctx.Regs[r\u002B\u002B] = Value.From( Global );\n\t\t\tif ( (fl \u0026 0x02) == 0 ) activation.SetOwn( \u0022this\u0022, thisV );\n\t\t\tif ( (fl \u0026 0x08) == 0 ) activation.SetOwn( \u0022arguments\u0022, Value.From( argsObj ) );\n\t\t\tif ( (fl \u0026 0x20) == 0 \u0026\u0026 superObj != null ) activation.SetOwn( \u0022super\u0022, Value.From( superObj ) );\n\t\t\tfor ( int i = 0; i \u003C f.ParamNames.Length; i\u002B\u002B )\n\t\t\t{\n\t\t\t\tvar v = i \u003C args.Length ? args[i] : Value.Undefined;\n\t\t\t\tint reg = f.ParamRegisters[i];\n\t\t\t\tif ( reg != 0 \u0026\u0026 reg \u003C ctx.Regs.Length ) ctx.Regs[reg] = v;\n\t\t\t\telse activation.SetOwn( f.ParamNames[i], v );\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tactivation.SetOwn( \u0022this\u0022, thisV );\n\t\t\tactivation.SetOwn( \u0022arguments\u0022, Value.From( argsObj ) );\n\t\t\tif ( superObj != null ) activation.SetOwn( \u0022super\u0022, Value.From( superObj ) );\n\t\t\tfor ( int i = 0; i \u003C f.ParamNames.Length; i\u002B\u002B )\n\t\t\t\tactivation.SetOwn( f.ParamNames[i], i \u003C args.Length ? args[i] : Value.Undefined );\n\t\t}\n\n\t\tcallDepth\u002B\u002B;\n\t\ttry\n\t\t{\n\t\t\tvar c = RunRange( ctx, f.Start, f.Start \u002B f.Length );\n\t\t\treturn c.Kind == Completion.Return ? c.Value : Value.Undefined;\n\t\t}\n\t\tfinally { callDepth--; }\n\t}\n\n\t// ======================================================================= interpreter\n\n\tsealed class Ctx\n\t{\n\t\tpublic byte[] Code;\n\t\tpublic Scope Scope;\n\t\tpublic Value This;\n\t\tpublic DisplayObject Target, BaseClip;\n\t\tpublic string[] Pool;\n\t\tpublic Value[] Regs;\n\t\tpublic int Version;\n\t\tpublic BytecodeFunction Func;\n\t\tpublic Value[] Args;\n\t\tpublic readonly List\u003CValue\u003E Stack = new( 16 );\n\t}\n\n\tenum Completion { Normal, Return, Jump }\n\n\treadonly struct Result\n\t{\n\t\tpublic readonly Completion Kind; public readonly Value Value; public readonly int JumpTo;\n\t\tpublic Result( Completion k, Value v = default, int jump = 0 ) { Kind = k; Value = v; JumpTo = jump; }\n\t}\n\n\tstatic Value Pop( Ctx c )\n\t{\n\t\tvar s = c.Stack;\n\t\tif ( s.Count == 0 ) return Value.Undefined;\n\t\tvar v = s[s.Count - 1];\n\t\ts.RemoveAt( s.Count - 1 );\n\t\treturn v;\n\t}\n\n\tstatic Value Peek( Ctx c ) =\u003E c.Stack.Count == 0 ? Value.Undefined : c.Stack[c.Stack.Count - 1];\n\n\tstatic void Push( Ctx c, Value v ) =\u003E c.Stack.Add( v );\n\n\tstatic ushort U16( byte[] b, int i ) =\u003E (ushort)(b[i] | (b[i \u002B 1] \u003C\u003C 8));\n\tstatic short S16( byte[] b, int i ) =\u003E (short)(b[i] | (b[i \u002B 1] \u003C\u003C 8));\n\tstatic int I32( byte[] b, int i ) =\u003E b[i] | (b[i \u002B 1] \u003C\u003C 8) | (b[i \u002B 2] \u003C\u003C 16) | (b[i \u002B 3] \u003C\u003C 24);\n\n\tstatic string CStr( byte[] b, ref int i, int end )\n\t{\n\t\tint s = i;\n\t\twhile ( i \u003C end \u0026\u0026 b[i] != 0 ) i\u002B\u002B;\n\t\tvar str = Encoding.UTF8.GetString( b, s, i - s );\n\t\ti\u002B\u002B; // skip NUL\n\t\treturn str;\n\t}\n\n\tValue[] PopArgs( Ctx c, int n )\n\t{\n\t\tn = Math.Clamp( n, 0, c.Stack.Count );\n\t\tvar a = new Value[n];\n\t\tfor ( int i = 0; i \u003C n; i\u002B\u002B ) a[i] = Pop( c );\n\t\treturn a;\n\t}\n\n\tResult RunRange( Ctx c, int start, int end )\n\t{\n\t\tvar code = c.Code;\n\t\tint pc = start;\n\t\twhile ( pc \u003C end \u0026\u0026 pc \u003C code.Length )\n\t\t{\n\t\t\tif ( --actionBudget \u003C 0 ) throw new ScriptAbort( \u0022too many actions (infinite loop?)\u0022 );\n\t\t\tint op = code[pc];\n\t\t\tint len = 0;\n\t\t\tint body = pc \u002B 1;\n\t\t\tif ( op \u003E= 0x80 )\n\t\t\t{\n\t\t\t\tlen = U16( code, pc \u002B 1 );\n\t\t\t\tbody = pc \u002B 3;\n\t\t\t}\n\t\t\tint next = body \u002B len;\n\t\t\tif ( op == 0 ) return new Result( Completion.Normal );\n\n\t\t\tswitch ( op )\n\t\t\t{\n\t\t\t\t// -------------------------------------------------- timeline control (SWF 3)\n\t\t\t\tcase 0x04: c.Target?.AsClip?.NextFrame(); break;\n\t\t\t\tcase 0x05: c.Target?.AsClip?.PrevFrame(); break;\n\t\t\t\tcase 0x06: c.Target?.AsClip?.Play(); break;\n\t\t\t\tcase 0x07: c.Target?.AsClip?.Stop(); break;\n\t\t\t\tcase 0x08: break; // toggle quality\n\t\t\t\tcase 0x09: Player.StopAllSounds(); break;\n\t\t\t\tcase 0x81: c.Target?.AsClip?.GotoFrame( U16( code, body ) \u002B 1, stop: true ); break;\n\t\t\t\tcase 0x8C: { int i = body; var label = CStr( code, ref i, next ); c.Target?.AsClip?.GotoLabel( label, stop: true ); break; }\n\t\t\t\tcase 0x8A: break; // WaitForFrame: everything is loaded\n\t\t\t\tcase 0x8D: Pop( c ); break; // WaitForFrame2\n\t\t\t\tcase 0x8B:\n\t\t\t\t\t{\n\t\t\t\t\t\tint i = body; var t = CStr( code, ref i, next );\n\t\t\t\t\t\tc.Target = t.Length == 0 ? c.BaseClip : ResolveTargetPath( c, t ) ?? c.Target;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x20:\n\t\t\t\t\t{\n\t\t\t\t\t\tvar tv = Pop( c );\n\t\t\t\t\t\tif ( tv.IsObject \u0026\u0026 tv.Obj is DisplayObject d ) c.Target = d;\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvar t = tv.ToStr( this );\n\t\t\t\t\t\t\tc.Target = t.Length == 0 ? c.BaseClip : ResolveTargetPath( c, t ) ?? c.BaseClip;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x83:\n\t\t\t\t\t{\n\t\t\t\t\t\tint i = body; var url = CStr( code, ref i, next ); var win = CStr( code, ref i, next );\n\t\t\t\t\t\tPlayer.GetUrl( url, win );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x9A:\n\t\t\t\t\t{\n\t\t\t\t\t\tvar win = Pop( c ); var url = Pop( c );\n\t\t\t\t\t\tint flags = code[body];\n\t\t\t\t\t\tif ( (flags \u0026 0x40) != 0 )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// loadMovie / loadVariables into a target\n\t\t\t\t\t\t\tPlayer.GetUrl( url.ToStr( this ), win.ToStr( this ) );\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse Player.GetUrl( url.ToStr( this ), win.ToStr( this ) );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x9F:\n\t\t\t\t\t{\n\t\t\t\t\t\tint flags = code[body];\n\t\t\t\t\t\tint bias = (flags \u0026 0x02) != 0 ? U16( code, body \u002B 1 ) : 0;\n\t\t\t\t\t\tbool play = (flags \u0026 0x01) != 0;\n\t\t\t\t\t\tvar fv = Pop( c );\n\t\t\t\t\t\tGotoFrameValue( c, fv, bias, !play );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x9E: // Call (frame actions)\n\t\t\t\t\t{\n\t\t\t\t\t\tvar fv = Pop( c ).ToStr( this );\n\t\t\t\t\t\tCallFrame( c, fv );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t// -------------------------------------------------- stack / arithmetic\n\t\t\t\tcase 0x96: ActionPush( c, body, next ); break;\n\t\t\t\tcase 0x17: Pop( c ); break;\n\t\t\t\tcase 0x4C: Push( c, Peek( c ) ); break;\n\t\t\t\tcase 0x4D: { var a = Pop( c ); var b = Pop( c ); Push( c, a ); Push( c, b ); break; }\n\t\t\t\tcase 0x0A: { var b = Pop( c ).ToNumber( this ); var a = Pop( c ).ToNumber( this ); Push( c, a \u002B b ); break; }\n\t\t\t\tcase 0x0B: { var b = Pop( c ).ToNumber( this ); var a = Pop( c ).ToNumber( this ); Push( c, a - b ); break; }\n\t\t\t\tcase 0x0C: { var b = Pop( c ).ToNumber( this ); var a = Pop( c ).ToNumber( this ); Push( c, a * b ); break; }\n\t\t\t\tcase 0x0D:\n\t\t\t\t\t{\n\t\t\t\t\t\tvar b = Pop( c ).ToNumber( this ); var a = Pop( c ).ToNumber( this );\n\t\t\t\t\t\tif ( b == 0 \u0026\u0026 c.Version \u003C 5 ) Push( c, \u0022#ERROR#\u0022 ); else Push( c, a / b );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x3F: { var b = Pop( c ).ToNumber( this ); var a = Pop( c ).ToNumber( this ); Push( c, a % b ); break; }\n\t\t\t\tcase 0x0E: { var b = Pop( c ).ToNumber( this ); var a = Pop( c ).ToNumber( this ); Push( c, a == b ); break; }\n\t\t\t\tcase 0x0F: { var b = Pop( c ).ToNumber( this ); var a = Pop( c ).ToNumber( this ); Push( c, a \u003C b ); break; }\n\t\t\t\tcase 0x10: { var b = Pop( c ).ToBool( c.Version ); var a = Pop( c ).ToBool( c.Version ); Push( c, a \u0026\u0026 b ); break; }\n\t\t\t\tcase 0x11: { var b = Pop( c ).ToBool( c.Version ); var a = Pop( c ).ToBool( c.Version ); Push( c, a || b ); break; }\n\t\t\t\tcase 0x12: { var a = Pop( c ).ToBool( c.Version ); Push( c, !a ); break; }\n\t\t\t\tcase 0x13: { var b = Pop( c ).ToStr( this ); var a = Pop( c ).ToStr( this ); Push( c, a == b ); break; }\n\t\t\t\tcase 0x14: Push( c, Pop( c ).ToStr( this ).Length ); break;\n\t\t\t\tcase 0x31: Push( c, Pop( c ).ToStr( this ).Length ); break;\n\t\t\t\tcase 0x15:\n\t\t\t\tcase 0x35:\n\t\t\t\t\t{\n\t\t\t\t\t\tint count = Pop( c ).ToInt32( this ); int index = Pop( c ).ToInt32( this ) - 1; var s = Pop( c ).ToStr( this );\n\t\t\t\t\t\tif ( index \u003C 0 ) index = 0;\n\t\t\t\t\t\tif ( index \u003E= s.Length ) { Push( c, \u0022\u0022 ); break; }\n\t\t\t\t\t\tif ( count \u003C 0 || index \u002B count \u003E s.Length ) count = s.Length - index;\n\t\t\t\t\t\tPush( c, s.Substring( index, count ) );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x21: { var b = Pop( c ).ToStr( this ); var a = Pop( c ).ToStr( this ); Push( c, a \u002B b ); break; }\n\t\t\t\tcase 0x29: { var b = Pop( c ).ToStr( this ); var a = Pop( c ).ToStr( this ); Push( c, string.CompareOrdinal( a, b ) \u003C 0 ); break; }\n\t\t\t\tcase 0x68: { var b = Pop( c ).ToStr( this ); var a = Pop( c ).ToStr( this ); Push( c, string.CompareOrdinal( a, b ) \u003E 0 ); break; }\n\t\t\t\tcase 0x18: Push( c, (double)Value.ToInt32( Math.Truncate( Pop( c ).ToNumber( this ) ) ) ); break;\n\t\t\t\tcase 0x32: case 0x36: { var s = Pop( c ).ToStr( this ); Push( c, s.Length \u003E 0 ? s[0] : 0 ); break; }\n\t\t\t\tcase 0x33: case 0x37: { int n = Pop( c ).ToInt32( this ); Push( c, n == 0 ? \u0022\u0022 : ((char)n).ToString() ); break; }\n\t\t\t\tcase 0x30: { int max = Pop( c ).ToInt32( this ); Push( c, max \u003C= 0 ? 0 : Rng.Next( max ) ); break; }\n\t\t\t\tcase 0x34: Push( c, Player.TimeMs ); break;\n\t\t\t\tcase 0x26: Trace( Pop( c ).ToStr( this ) ); break;\n\t\t\t\tcase 0x47:\n\t\t\t\t\t{\n\t\t\t\t\t\tvar b = Pop( c ); var a = Pop( c );\n\t\t\t\t\t\tvar pa = a.IsObject ? a.Obj.ToPrimitive( this, false ) : a;\n\t\t\t\t\t\tvar pb = b.IsObject ? b.Obj.ToPrimitive( this, false ) : b;\n\t\t\t\t\t\tif ( a.IsObject \u0026\u0026 a.Obj is DisplayObject ) pa = a.ToStr( this );\n\t\t\t\t\t\tif ( b.IsObject \u0026\u0026 b.Obj is DisplayObject ) pb = b.ToStr( this );\n\t\t\t\t\t\tif ( pa.IsString || pb.IsString ) Push( c, pa.ToStr( this ) \u002B pb.ToStr( this ) );\n\t\t\t\t\t\telse Push( c, pa.ToNumber( this ) \u002B pb.ToNumber( this ) );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x48: { var b = Pop( c ); var a = Pop( c ); Push( c, LessThan( a, b ) ); break; }\n\t\t\t\tcase 0x67: { var b = Pop( c ); var a = Pop( c ); Push( c, LessThan( b, a ) ); break; }\n\t\t\t\tcase 0x49: { var b = Pop( c ); var a = Pop( c ); Push( c, AbstractEquals( a, b ) ); break; }\n\t\t\t\tcase 0x66: { var b = Pop( c ); var a = Pop( c ); Push( c, StrictEquals( a, b ) ); break; }\n\t\t\t\tcase 0x4A: Push( c, Pop( c ).ToNumber( this ) ); break;\n\t\t\t\tcase 0x4B: Push( c, Pop( c ).ToStr( this ) ); break;\n\t\t\t\tcase 0x50: Push( c, Pop( c ).ToNumber( this ) \u002B 1 ); break;\n\t\t\t\tcase 0x51: Push( c, Pop( c ).ToNumber( this ) - 1 ); break;\n\t\t\t\tcase 0x60: { var b = Pop( c ).ToInt32( this ); var a = Pop( c ).ToInt32( this ); Push( c, a \u0026 b ); break; }\n\t\t\t\tcase 0x61: { var b = Pop( c ).ToInt32( this ); var a = Pop( c ).ToInt32( this ); Push( c, a | b ); break; }\n\t\t\t\tcase 0x62: { var b = Pop( c ).ToInt32( this ); var a = Pop( c ).ToInt32( this ); Push( c, a ^ b ); break; }\n\t\t\t\tcase 0x63: { var b = Pop( c ).ToInt32( this ) \u0026 31; var a = Pop( c ).ToInt32( this ); Push( c, a \u003C\u003C b ); break; }\n\t\t\t\tcase 0x64: { var b = Pop( c ).ToInt32( this ) \u0026 31; var a = Pop( c ).ToInt32( this ); Push( c, a \u003E\u003E b ); break; }\n\t\t\t\tcase 0x65: { var b = Pop( c ).ToInt32( this ) \u0026 31; var a = (uint)Pop( c ).ToInt32( this ); Push( c, (double)(a \u003E\u003E b) ); break; }\n\t\t\t\tcase 0x44: Push( c, Pop( c ).TypeOf() ); break;\n\t\t\t\tcase 0x45:\n\t\t\t\t\t{\n\t\t\t\t\t\tvar v = Pop( c );\n\t\t\t\t\t\tPush( c, v.IsObject \u0026\u0026 v.Obj is DisplayObject d ? (Value)d.TargetPath : Value.Undefined );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t// -------------------------------------------------- variables\n\t\t\t\tcase 0x1C:\n\t\t\t\t\t{\n\t\t\t\t\t\tvar name = Pop( c ).ToStr( this );\n\t\t\t\t\t\tPush( c, GetVariable( c, name ) );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x1D:\n\t\t\t\t\t{\n\t\t\t\t\t\tvar v = Pop( c ); var name = Pop( c ).ToStr( this );\n\t\t\t\t\t\tSetVariable( c, name, v );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x3C:\n\t\t\t\t\t{\n\t\t\t\t\t\tvar v = Pop( c ); var name = Pop( c ).ToStr( this );\n\t\t\t\t\t\tDefineLocal( c, name, v );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x41:\n\t\t\t\t\t{\n\t\t\t\t\t\tvar name = Pop( c ).ToStr( this );\n\t\t\t\t\t\tvar local = LocalScope( c );\n\t\t\t\t\t\tif ( !local.Values.HasOwnProperty( name ) ) local.Values.SetOwn( name, Value.Undefined );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x3A:\n\t\t\t\t\t{\n\t\t\t\t\t\tvar name = Pop( c ).ToStr( this ); var obj = Pop( c );\n\t\t\t\t\t\tPush( c, obj.IsObject \u0026\u0026 obj.Obj.Delete( name ) );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x3B:\n\t\t\t\t\t{\n\t\t\t\t\t\tvar name = Pop( c ).ToStr( this );\n\t\t\t\t\t\tbool ok = false;\n\t\t\t\t\t\tfor ( var s = c.Scope; s != null; s = s.Parent )\n\t\t\t\t\t\t\tif ( s.Values.HasOwnProperty( name ) ) { ok = s.Values.Delete( name ); break; }\n\t\t\t\t\t\tPush( c, ok );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x4E:\n\t\t\t\t\t{\n\t\t\t\t\t\tvar nameV = Pop( c ); var obj = Pop( c );\n\t\t\t\t\t\tPush( c, GetMember( obj, nameV.ToStr( this ) ) );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x4F:\n\t\t\t\t\t{\n\t\t\t\t\t\tvar v = Pop( c ); var nameV = Pop( c ); var obj = Pop( c );\n\t\t\t\t\t\tSetMember( obj, nameV.ToStr( this ), v );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x22:\n\t\t\t\t\t{\n\t\t\t\t\t\tint idx = Pop( c ).ToInt32( this ); var tv = Pop( c );\n\t\t\t\t\t\tvar d = TargetFromValue( c, tv );\n\t\t\t\t\t\tPush( c, d != null ? d.GetIndexedProperty( idx, this ) : Value.Undefined );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x23:\n\t\t\t\t\t{\n\t\t\t\t\t\tvar v = Pop( c ); int idx = Pop( c ).ToInt32( this ); var tv = Pop( c );\n\t\t\t\t\t\tvar d = TargetFromValue( c, tv );\n\t\t\t\t\t\td?.SetIndexedProperty( idx, v, this );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x87:\n\t\t\t\t\t{\n\t\t\t\t\t\tint r = code[body];\n\t\t\t\t\t\tif ( r \u003C c.Regs.Length ) c.Regs[r] = Peek( c );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x88:\n\t\t\t\t\t{\n\t\t\t\t\t\tint n = U16( code, body ); int i = body \u002B 2;\n\t\t\t\t\t\tvar pool = new string[n];\n\t\t\t\t\t\tfor ( int k = 0; k \u003C n; k\u002B\u002B ) pool[k] = CStr( code, ref i, next );\n\t\t\t\t\t\tc.Pool = pool;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t// -------------------------------------------------- objects\n\t\t\t\tcase 0x40:\n\t\t\t\t\t{\n\t\t\t\t\t\tvar name = Pop( c ).ToStr( this ); int n = Pop( c ).ToInt32( this ); var args = PopArgs( c, n );\n\t\t\t\t\t\tvar ctor = GetVariable( c, name ).AsObj as AsFunction;\n\t\t\t\t\t\tPush( c, ctor != null ? Construct( ctor, args ) : Value.Undefined );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x53:\n\t\t\t\t\t{\n\t\t\t\t\t\tvar nameV = Pop( c ); var obj = Pop( c ); int n = Pop( c ).ToInt32( this ); var args = PopArgs( c, n );\n\t\t\t\t\t\tAsFunction ctor;\n\t\t\t\t\t\tif ( nameV.IsUndefined || (nameV.IsString \u0026\u0026 nameV.Str.Length == 0) ) ctor = obj.AsObj as AsFunction;\n\t\t\t\t\t\telse ctor = GetMember( obj, nameV.ToStr( this ) ).AsObj as AsFunction;\n\t\t\t\t\t\tPush( c, ctor != null ? Construct( ctor, args ) : Value.Undefined );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x42:\n\t\t\t\t\t{\n\t\t\t\t\t\tint n = Pop( c ).ToInt32( this );\n\t\t\t\t\t\tvar arr = NewArray();\n\t\t\t\t\t\tfor ( int i = 0; i \u003C n; i\u002B\u002B ) arr.Items.Add( Pop( c ) );\n\t\t\t\t\t\tPush( c, arr );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x43:\n\t\t\t\t\t{\n\t\t\t\t\t\tint n = Pop( c ).ToInt32( this );\n\t\t\t\t\t\tvar obj = NewObject();\n\t\t\t\t\t\tfor ( int i = 0; i \u003C n; i\u002B\u002B ) { var v = Pop( c ); var k = Pop( c ).ToStr( this ); obj.Set( k, v, this ); }\n\t\t\t\t\t\tPush( c, obj );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x3D:\n\t\t\t\t\t{\n\t\t\t\t\t\tvar name = Pop( c ).ToStr( this ); int n = Pop( c ).ToInt32( this ); var args = PopArgs( c, n );\n\t\t\t\t\t\tvar (holder, fv) = ResolveCallable( c, name );\n\t\t\t\t\t\tif ( fv.AsObj is AsFunction f )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvar thisV = holder != null ? Value.From( holder ) : (c.Target != null ? Value.From( c.Target ) : Value.Undefined);\n\t\t\t\t\t\t\tif ( holder != null \u0026\u0026 LocalScopeHolds( c, holder ) ) thisV = c.Target != null ? Value.From( c.Target ) : Value.Undefined;\n\t\t\t\t\t\t\tPush( c, CallFunction( f, thisV, args ) );\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse Push( c, Value.Undefined );\n\t\t\t\t\t\tif ( c.BaseClip != null \u0026\u0026 c.BaseClip.Removed \u0026\u0026 c.Func == null ) return new Result( Completion.Normal );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x52:\n\t\t\t\t\t{\n\t\t\t\t\t\tvar nameV = Pop( c ); var obj = Pop( c ); int n = Pop( c ).ToInt32( this ); var args = PopArgs( c, n );\n\t\t\t\t\t\tif ( obj.IsNullish ) { Push( c, Value.Undefined ); break; }\n\t\t\t\t\t\tif ( (nameV.IsUndefined || (nameV.IsString \u0026\u0026 nameV.Str.Length == 0)) \u0026\u0026 obj.Obj is SuperObject sc )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tPush( c, sc.CallConstructor( args ) );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( nameV.IsUndefined || (nameV.IsString \u0026\u0026 nameV.Str.Length == 0) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// obj itself is the function\n\t\t\t\t\t\t\tif ( obj.AsObj is AsFunction f0 ) Push( c, CallFunction( f0, Value.Undefined, args ) );\n\t\t\t\t\t\t\telse Push( c, Value.Undefined );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvar name = nameV.ToStr( this );\n\t\t\t\t\t\tif ( obj.IsObject \u0026\u0026 obj.Obj is DisplayObject deadClip \u0026\u0026 deadClip.Removed )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvar live = Live( deadClip );\n\t\t\t\t\t\t\tif ( live == null ) { Push( c, Value.Undefined ); break; }\n\t\t\t\t\t\t\tobj = Value.From( live );\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( obj.IsObject \u0026\u0026 obj.Obj is SuperObject sup )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tPush( c, sup.CallMethod( name, args ) );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvar fv = GetMember( obj, name );\n\t\t\t\t\t\tif ( fv.AsObj is AsFunction f )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvar thisV = obj.IsObject ? obj : Value.From( ToObject( obj ) );\n\t\t\t\t\t\t\t// for prototype methods, super inside should start above the prototype that owns the method\n\t\t\t\t\t\t\tPush( c, f.Call( this, thisV, args, FindOwner( obj, name ) ) );\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse Push( c, Value.Undefined );\n\t\t\t\t\t\tif ( c.BaseClip != null \u0026\u0026 c.BaseClip.Removed \u0026\u0026 c.Func == null ) return new Result( Completion.Normal );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x3E:\n\t\t\t\t\treturn new Result( Completion.Return, Pop( c ) );\n\t\t\t\tcase 0x54:\n\t\t\t\t\t{\n\t\t\t\t\t\tvar ctor = Pop( c ); var obj = Pop( c );\n\t\t\t\t\t\tPush( c, obj.IsObject \u0026\u0026 ctor.AsObj is AsFunction \u0026\u0026 InstanceOf( obj.Obj, ctor.Obj ) );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x2B:\n\t\t\t\t\t{\n\t\t\t\t\t\tvar obj = Pop( c ); var ctor = Pop( c );\n\t\t\t\t\t\tPush( c, obj.IsObject \u0026\u0026 ctor.AsObj is AsFunction \u0026\u0026 InstanceOf( obj.Obj, ctor.Obj ) ? obj : Value.Null );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x2C:\n\t\t\t\t\t{\n\t\t\t\t\t\tvar ctor = Pop( c ); int n = Pop( c ).ToInt32( this ); var ifaces = PopArgs( c, n );\n\t\t\t\t\t\tvar proto = ctor.AsObj?.Get( \u0022prototype\u0022, this ).AsObj;\n\t\t\t\t\t\tif ( proto != null ) proto.Native = ifaces;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x69:\n\t\t\t\t\t{\n\t\t\t\t\t\tvar superCtor = Pop( c ).AsObj; var sub = Pop( c ).AsObj;\n\t\t\t\t\t\tif ( sub != null \u0026\u0026 superCtor != null )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvar proto = NewObject( superCtor.Get( \u0022prototype\u0022, this ).AsObj );\n\t\t\t\t\t\t\tproto.DefineHidden( \u0022__constructor__\u0022, Value.From( superCtor ) );\n\t\t\t\t\t\t\tif ( SwfVersion \u003E= 7 ) proto.DefineHidden( \u0022constructor\u0022, Value.From( sub ) );\n\t\t\t\t\t\t\tsub.SetOwn( \u0022prototype\u0022, Value.From( proto ), PropFlags.DontEnum );\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x46:\n\t\t\t\t\t{\n\t\t\t\t\t\tvar name = Pop( c ).ToStr( this );\n\t\t\t\t\t\tvar obj = GetVariable( c, name );\n\t\t\t\t\t\tPush( c, Value.Undefined );\n\t\t\t\t\t\tif ( obj.IsObject ) foreach ( var k in obj.Obj.EnumerableKeys() ) Push( c, k );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x55:\n\t\t\t\t\t{\n\t\t\t\t\t\tvar obj = Pop( c );\n\t\t\t\t\t\tPush( c, Value.Undefined );\n\t\t\t\t\t\tif ( obj.IsObject ) foreach ( var k in obj.Obj.EnumerableKeys() ) Push( c, k );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x2A:\n\t\t\t\t\tthrow new AsThrow( Pop( c ) );\n\n\t\t\t\t// -------------------------------------------------- functions \u0026 blocks\n\t\t\t\tcase 0x9B:\n\t\t\t\t\t{\n\t\t\t\t\t\tint i = body;\n\t\t\t\t\t\tvar name = CStr( code, ref i, next );\n\t\t\t\t\t\tint np = U16( code, i ); i \u002B= 2;\n\t\t\t\t\t\tvar ps = new string[np];\n\t\t\t\t\t\tfor ( int k = 0; k \u003C np; k\u002B\u002B ) ps[k] = CStr( code, ref i, next );\n\t\t\t\t\t\tint size = U16( code, i );\n\t\t\t\t\t\tvar f = MakeFunction( c, name, ps, null, false, 0, 0, next, size );\n\t\t\t\t\t\tnext \u002B= size;\n\t\t\t\t\t\tif ( name.Length \u003E 0 ) SetVariable( c, name, f );\n\t\t\t\t\t\telse Push( c, f );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x8E:\n\t\t\t\t\t{\n\t\t\t\t\t\tint i = body;\n\t\t\t\t\t\tvar name = CStr( code, ref i, next );\n\t\t\t\t\t\tint np = U16( code, i ); i \u002B= 2;\n\t\t\t\t\t\tint regCount = code[i\u002B\u002B];\n\t\t\t\t\t\tushort flags = (ushort)(code[i] | (code[i \u002B 1] \u003C\u003C 8)); i \u002B= 2;\n\t\t\t\t\t\tvar ps = new string[np]; var pr = new byte[np];\n\t\t\t\t\t\tfor ( int k = 0; k \u003C np; k\u002B\u002B ) { pr[k] = code[i\u002B\u002B]; ps[k] = CStr( code, ref i, next ); }\n\t\t\t\t\t\tint size = U16( code, i );\n\t\t\t\t\t\tvar f = MakeFunction( c, name, ps, pr, true, regCount, flags, next, size );\n\t\t\t\t\t\tnext \u002B= size;\n\t\t\t\t\t\tif ( name.Length \u003E 0 ) SetVariable( c, name, f );\n\t\t\t\t\t\telse Push( c, f );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x94:\n\t\t\t\t\t{\n\t\t\t\t\t\tint size = U16( code, body );\n\t\t\t\t\t\tvar obj = Pop( c );\n\t\t\t\t\t\tint blockStart = next;\n\t\t\t\t\t\tint blockEnd = next \u002B size;\n\t\t\t\t\t\tvar withObj = obj.IsObject ? obj.Obj : ToObject( obj );\n\t\t\t\t\t\tvar saved = c.Scope;\n\t\t\t\t\t\tif ( withObj != null ) c.Scope = new Scope( withObj, ScopeClass.With, c.Scope );\n\t\t\t\t\t\tResult r;\n\t\t\t\t\t\ttry { r = RunRange( c, blockStart, blockEnd ); }\n\t\t\t\t\t\tfinally { c.Scope = saved; }\n\t\t\t\t\t\tif ( r.Kind == Completion.Return ) return r;\n\t\t\t\t\t\tif ( r.Kind == Completion.Jump )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif ( r.JumpTo \u003E= start \u0026\u0026 r.JumpTo \u003C end ) { pc = r.JumpTo; continue; }\n\t\t\t\t\t\t\tif ( r.JumpTo == end ) return new Result( Completion.Normal );\n\t\t\t\t\t\t\treturn r;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tnext = blockEnd;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x8F:\n\t\t\t\t\t{\n\t\t\t\t\t\tint i = body;\n\t\t\t\t\t\tint flags = code[i\u002B\u002B];\n\t\t\t\t\t\tint trySize = U16( code, i ); i \u002B= 2;\n\t\t\t\t\t\tint catchSize = U16( code, i ); i \u002B= 2;\n\t\t\t\t\t\tint finallySize = U16( code, i ); i \u002B= 2;\n\t\t\t\t\t\tbool catchInReg = (flags \u0026 4) != 0;\n\t\t\t\t\t\tstring catchName = null; int catchReg = 0;\n\t\t\t\t\t\tif ( catchInReg ) catchReg = code[i\u002B\u002B]; else catchName = CStr( code, ref i, next );\n\t\t\t\t\t\tint tryStart = next, catchStart = tryStart \u002B trySize, finStart = catchStart \u002B catchSize, after = finStart \u002B finallySize;\n\t\t\t\t\t\tResult r = new( Completion.Normal );\n\t\t\t\t\t\ttry\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tr = RunRange( c, tryStart, catchStart );\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcatch ( AsThrow t ) when ( (flags \u0026 1) != 0 )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif ( catchInReg ) { if ( catchReg \u003C c.Regs.Length ) c.Regs[catchReg] = t.Thrown; }\n\t\t\t\t\t\t\telse LocalScope( c ).Values.SetOwn( catchName, t.Thrown );\n\t\t\t\t\t\t\tr = RunRange( c, catchStart, finStart );\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfinally\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif ( (flags \u0026 2) != 0 \u0026\u0026 finallySize \u003E 0 )\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tvar fr = RunRange( c, finStart, after );\n\t\t\t\t\t\t\t\tif ( fr.Kind == Completion.Return ) r = fr;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( r.Kind == Completion.Return ) return r;\n\t\t\t\t\t\tif ( r.Kind == Completion.Jump )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif ( r.JumpTo \u003E= start \u0026\u0026 r.JumpTo \u003C end ) { pc = r.JumpTo; continue; }\n\t\t\t\t\t\t\tif ( r.JumpTo == end ) return new Result( Completion.Normal );\n\t\t\t\t\t\t\treturn r;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tnext = after;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x99:\n\t\t\t\t\t{\n\t\t\t\t\t\tint target = next \u002B S16( code, body );\n\t\t\t\t\t\tif ( target == end ) return new Result( Completion.Normal );\n\t\t\t\t\t\tif ( target \u003C start || target \u003E end ) return new Result( Completion.Jump, default, target );\n\t\t\t\t\t\tpc = target;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\tcase 0x9D:\n\t\t\t\t\t{\n\t\t\t\t\t\tint target = next \u002B S16( code, body );\n\t\t\t\t\t\tif ( Pop( c ).ToBool( c.Version ) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif ( target == end ) return new Result( Completion.Normal );\n\t\t\t\t\t\t\tif ( target \u003C start || target \u003E end ) return new Result( Completion.Jump, default, target );\n\t\t\t\t\t\t\tpc = target;\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t// -------------------------------------------------- movie clips\n\t\t\t\tcase 0x24:\n\t\t\t\t\t{\n\t\t\t\t\t\tint depth = Pop( c ).ToInt32( this ); var tname = Pop( c ).ToStr( this ); var src = Pop( c );\n\t\t\t\t\t\tvar srcClip = TargetFromValue( c, src );\n\t\t\t\t\t\tsrcClip?.Duplicate( tname, depth \u002B 0 );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x25:\n\t\t\t\t\t{\n\t\t\t\t\t\tvar tv = Pop( c );\n\t\t\t\t\t\tvar d = TargetFromValue( c, tv );\n\t\t\t\t\t\td?.RemoveFromScript();\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x27:\n\t\t\t\t\t{\n\t\t\t\t\t\tvar target = Pop( c ); bool lockCenter = Pop( c ).ToBool( c.Version ); bool constrain = Pop( c ).ToBool( c.Version );\n\t\t\t\t\t\tdouble x1 = 0, y1 = 0, x2 = 0, y2 = 0;\n\t\t\t\t\t\tif ( constrain ) { y2 = Pop( c ).ToNumber( this ); x2 = Pop( c ).ToNumber( this ); y1 = Pop( c ).ToNumber( this ); x1 = Pop( c ).ToNumber( this ); }\n\t\t\t\t\t\tvar d = TargetFromValue( c, target );\n\t\t\t\t\t\tif ( d != null ) Player.StartDrag( d, lockCenter, constrain ? new double[] { x1, y1, x2, y2 } : null );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 0x28: Player.StopDrag(); break;\n\n\t\t\t\tdefault:\n\t\t\t\t\t// unknown / unsupported action: skip\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tpc = next;\n\t\t}\n\t\treturn new Result( Completion.Normal );\n\t}\n\n\tvoid ActionPush( Ctx c, int i, int end )\n\t{\n\t\tvar code = c.Code;\n\t\twhile ( i \u003C end )\n\t\t{\n\t\t\tint type = code[i\u002B\u002B];\n\t\t\tswitch ( type )\n\t\t\t{\n\t\t\t\tcase 0: Push( c, CStr( code, ref i, end ) ); break;\n\t\t\t\tcase 1: Push( c, (double)BitConverter.Int32BitsToSingle( I32( code, i ) ) ); i \u002B= 4; break;\n\t\t\t\tcase 2: Push( c, Value.Null ); break;\n\t\t\t\tcase 3: Push( c, Value.Undefined ); break;\n\t\t\t\tcase 4: { int r = code[i\u002B\u002B]; Push( c, r \u003C c.Regs.Length ? c.Regs[r] : Value.Undefined ); break; }\n\t\t\t\tcase 5: Push( c, code[i\u002B\u002B] != 0 ); break;\n\t\t\t\tcase 6:\n\t\t\t\t\t{\n\t\t\t\t\t\tlong hi = (uint)I32( code, i ); long lo = (uint)I32( code, i \u002B 4 );\n\t\t\t\t\t\tPush( c, BitConverter.Int64BitsToDouble( (hi \u003C\u003C 32) | lo ) );\n\t\t\t\t\t\ti \u002B= 8;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\tcase 7: Push( c, (double)I32( code, i ) ); i \u002B= 4; break;\n\t\t\t\tcase 8: { int k = code[i\u002B\u002B]; Push( c, k \u003C c.Pool.Length ? (Value)c.Pool[k] : Value.Undefined ); break; }\n\t\t\t\tcase 9: { int k = U16( code, i ); i \u002B= 2; Push( c, k \u003C c.Pool.Length ? (Value)c.Pool[k] : Value.Undefined ); break; }\n\t\t\t\tdefault: return;\n\t\t\t}\n\t\t}\n\t}\n\n\tBytecodeFunction MakeFunction( Ctx c, string name, string[] ps, byte[] regs, bool f2, int regCount, ushort flags, int start, int size )\n\t{\n\t\tvar f = new BytecodeFunction( FunctionProto )\n\t\t{\n\t\t\tName = name, Code = c.Code, Start = start, Length = size, ParamNames = ps, ParamRegisters = regs ?? new byte[ps.Length],\n\t\t\tIsFunction2 = f2, RegisterCount = regCount, Flags2 = flags, ConstantPool = c.Pool, Scope = c.Scope,\n\t\t\tBaseClip = c.BaseClip ?? Player.Root, SwfVersion = c.Version,\n\t\t};\n\t\tvar proto = NewObject();\n\t\tproto.DefineHidden( \u0022constructor\u0022, Value.From( f ) );\n\t\tf.SetOwn( \u0022prototype\u0022, Value.From( proto ), PropFlags.DontEnum );\n\t\treturn f;\n\t}\n\n\t// ======================================================================= variable resolution\n\n\tScope LocalScope( Ctx c )\n\t{\n\t\tfor ( var s = c.Scope; s != null; s = s.Parent )\n\t\t\tif ( s.Class == ScopeClass.Local || s.Class == ScopeClass.Target ) return s;\n\t\treturn c.Scope;\n\t}\n\n\tbool LocalScopeHolds( Ctx c, AsObject holder )\n\t{\n\t\tfor ( var s = c.Scope; s != null; s = s.Parent )\n\t\t\tif ( s.Class == ScopeClass.Local \u0026\u0026 ReferenceEquals( s.Values, holder ) ) return true;\n\t\treturn false;\n\t}\n\n\tstatic bool IsPath( string name )\n\t{\n\t\tforeach ( var ch in name ) if ( ch == \u0027/\u0027 || ch == \u0027:\u0027 || ch == \u0027.\u0027 ) return true;\n\t\treturn false;\n\t}\n\n\tValue GetVariable( Ctx c, string name )\n\t{\n\t\tif ( name.Length \u003E 0 \u0026\u0026 IsPath( name ) \u0026\u0026 !char.IsDigit( name[0] ) )\n\t\t{\n\t\t\tif ( TryResolvePathVariable( c, name, out var pv ) ) return pv;\n\t\t}\n\t\tvar (holder, v) = ResolveCallable( c, name );\n\t\treturn v;\n\t}\n\n\t(AsObject holder, Value value) ResolveCallable( Ctx c, string name )\n\t{\n\t\tswitch ( name )\n\t\t{\n\t\t\tcase \u0022this\u0022: return (null, c.This);\n\t\t\tcase \u0022_root\u0022: return (null, Value.From( Player.Root ));\n\t\t\tcase \u0022_global\u0022: return (null, Value.From( Global ));\n\t\t\tcase \u0022_parent\u0022:\n\t\t\t\t{\n\t\t\t\t\t// _parent resolves on the current target unless shadowed by a local\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t}\n\t\tfor ( var s = c.Scope; s != null; s = s.Parent )\n\t\t{\n\t\t\tvar o = s.Values;\n\t\t\tif ( o is DisplayObject d \u0026\u0026 d.Removed ) o = c.Target ?? Player.Root;\n\t\t\tif ( o.Has( name ) ) return (o, o.Get( name, this ));\n\t\t}\n\t\tif ( name == \u0022_parent\u0022 ) return (null, c.Target?.Parent != null ? Value.From( c.Target.Parent ) : Value.Undefined);\n\t\tif ( name.StartsWith( \u0022_level\u0022 ) ) return (null, Value.From( Player.Root ));\n\t\treturn (null, Value.Undefined);\n\t}\n\n\tvoid SetVariable( Ctx c, string name, Value v )\n\t{\n\t\tif ( name.Length \u003E 0 \u0026\u0026 IsPath( name ) \u0026\u0026 !char.IsDigit( name[0] ) )\n\t\t{\n\t\t\tif ( TrySetPathVariable( c, name, v ) ) return;\n\t\t}\n\t\tfor ( var s = c.Scope; s != null; s = s.Parent )\n\t\t{\n\t\t\tvar o = s.Values;\n\t\t\tif ( o is DisplayObject d \u0026\u0026 d.Removed ) continue;\n\t\t\tif ( s.Class == ScopeClass.Target || o.Has( name ) || s.Parent == null )\n\t\t\t{\n\t\t\t\to.Set( name, v, this );\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid DefineLocal( Ctx c, string name, Value v )\n\t{\n\t\t// in a with block, an existing property on the with target wins\n\t\tfor ( var s = c.Scope; s != null; s = s.Parent )\n\t\t{\n\t\t\tif ( s.Class == ScopeClass.With )\n\t\t\t{\n\t\t\t\tif ( s.Values.Has( name ) ) { s.Values.Set( name, v, this ); return; }\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ts.Values.SetOwn( name, v );\n\t\t\treturn;\n\t\t}\n\t}\n\n\tAsObject FindOwner( Value obj, string name )\n\t{\n\t\tif ( !obj.IsObject ) return null;\n\t\tvar o = obj.Obj; int g = 0;\n\t\twhile ( o != null \u0026\u0026 g\u002B\u002B \u003C 64 )\n\t\t{\n\t\t\tif ( o.GetOwnProperty( name ) != null ) return ReferenceEquals( o, obj.Obj ) ? null : o;\n\t\t\to = o.Proto;\n\t\t}\n\t\treturn null;\n\t}\n\n\tpublic bool InstanceOf( AsObject obj, AsObject ctor )\n\t{\n\t\tvar proto = ctor.Get( \u0022prototype\u0022, this ).AsObj;\n\t\tif ( proto is null ) return false;\n\t\tvar o = obj.Proto; int g = 0;\n\t\twhile ( o != null \u0026\u0026 g\u002B\u002B \u003C 128 )\n\t\t{\n\t\t\tif ( ReferenceEquals( o, proto ) ) return true;\n\t\t\tif ( o.Native is Value[] ifaces )\n\t\t\t\tforeach ( var i in ifaces ) if ( i.AsObj != null \u0026\u0026 ReferenceEquals( i.Obj, ctor ) ) return true;\n\t\t\to = o.Proto;\n\t\t}\n\t\treturn false;\n\t}\n\n\t// ======================================================================= target paths\n\n\tDisplayObject TargetFromValue( Ctx c, Value v )\n\t{\n\t\tif ( v.IsObject \u0026\u0026 v.Obj is DisplayObject d ) return d;\n\t\tvar s = v.ToStr( this );\n\t\tif ( s.Length == 0 ) return c.Target;\n\t\treturn ResolveTargetPath( c, s );\n\t}\n\n\tpublic DisplayObject ResolveTargetPath( DisplayObject start, string path ) =\u003E ResolveTargetPath( new Ctx { Target = start, BaseClip = start, This = Value.From( start ) }, path );\n\n\tDisplayObject ResolveTargetPath( Ctx c, string path )\n\t{\n\t\tif ( string.IsNullOrEmpty( path ) ) return c.Target;\n\t\tDisplayObject cur = c.Target ?? Player.Root;\n\t\tint i = 0;\n\t\tif ( path[0] == \u0027/\u0027 ) { cur = Player.Root; i = 1; }\n\t\tvar parts = path.Substring( i ).Split( \u0027/\u0027, \u0027.\u0027 );\n\t\tbool first = true;\n\t\tforeach ( var raw in parts )\n\t\t{\n\t\t\tvar p = raw;\n\t\t\tif ( p.Length == 0 ) continue;\n\t\t\tif ( p == \u0022..\u0022 || p == \u0022_parent\u0022 ) { cur = cur?.Parent; }\n\t\t\telse if ( p == \u0022_root\u0022 || p.StartsWith( \u0022_level\u0022 ) ) cur = Player.Root;\n\t\t\telse if ( p == \u0022this\u0022 ) { cur = c.This.Obj as DisplayObject ?? cur; }\n\t\t\telse if ( first \u0026\u0026 p != null \u0026\u0026 c.Scope != null \u0026\u0026 !(cur is MovieClip mc0 \u0026\u0026 mc0.ChildByName( p ) != null) )\n\t\t\t{\n\t\t\t\t// first segment can be a variable holding a clip\n\t\t\t\tvar v = GetVariableNoPath( c, p );\n\t\t\t\tif ( v.IsObject \u0026\u0026 v.Obj is DisplayObject vd ) cur = vd;\n\t\t\t\telse cur = (cur as MovieClip)?.ChildByName( p );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tvar mc = cur as MovieClip;\n\t\t\t\tif ( mc is null ) return null;\n\t\t\t\tvar child = mc.ChildByName( p );\n\t\t\t\tif ( child == null )\n\t\t\t\t{\n\t\t\t\t\tvar v = mc.Get( p, this );\n\t\t\t\t\tchild = v.Obj as DisplayObject;\n\t\t\t\t}\n\t\t\t\tcur = child;\n\t\t\t}\n\t\t\tfirst = false;\n\t\t\tif ( cur is null ) return null;\n\t\t}\n\t\treturn cur;\n\t}\n\n\tValue GetVariableNoPath( Ctx c, string name ) =\u003E ResolveCallable( c, name ).value;\n\n\tbool TryResolvePathVariable( Ctx c, string name, out Value v )\n\t{\n\t\tv = Value.Undefined;\n\t\tint colon = name.LastIndexOf( \u0027:\u0027 );\n\t\tstring targetPath, varName;\n\t\tif ( colon \u003E= 0 ) { targetPath = name.Substring( 0, colon ); varName = name.Substring( colon \u002B 1 ); }\n\t\telse\n\t\t{\n\t\t\tint dot = Math.Max( name.LastIndexOf( \u0027.\u0027 ), name.LastIndexOf( \u0027/\u0027 ) );\n\t\t\tif ( dot \u003C 0 ) return false;\n\t\t\ttargetPath = name.Substring( 0, dot ); varName = name.Substring( dot \u002B 1 );\n\t\t\tif ( name.IndexOf( \u0027/\u0027 ) \u003E= 0 \u0026\u0026 name.IndexOf( \u0027.\u0027 ) \u003C 0 \u0026\u0026 name.IndexOf( \u0027:\u0027 ) \u003C 0 )\n\t\t\t{\n\t\t\t\t// \u0022/a/b\u0022 is a clip path\n\t\t\t\tvar d = ResolveTargetPath( c, name );\n\t\t\t\tif ( d != null ) { v = Value.From( d ); return true; }\n\t\t\t}\n\t\t}\n\t\tvar start = targetPath.Length == 0 ? c.Target : ResolveTargetPath( c, targetPath );\n\t\tif ( start == null )\n\t\t{\n\t\t\t// maybe an object path like a.b.c on plain objects\n\t\t\tvar parts = name.Split( \u0027.\u0027 );\n\t\t\tvar cur = GetVariableNoPath( c, parts[0] );\n\t\t\tfor ( int k = 1; k \u003C parts.Length; k\u002B\u002B ) cur = GetMember( cur, parts[k] );\n\t\t\tv = cur;\n\t\t\treturn true;\n\t\t}\n\t\tv = start.Get( varName, this );\n\t\treturn true;\n\t}\n\n\tbool TrySetPathVariable( Ctx c, string name, Value v )\n\t{\n\t\tint colon = name.LastIndexOf( \u0027:\u0027 );\n\t\tstring targetPath, varName;\n\t\tif ( colon \u003E= 0 ) { targetPath = name.Substring( 0, colon ); varName = name.Substring( colon \u002B 1 ); }\n\t\telse\n\t\t{\n\t\t\tint dot = Math.Max( name.LastIndexOf( \u0027.\u0027 ), name.LastIndexOf( \u0027/\u0027 ) );\n\t\t\tif ( dot \u003C 0 ) return false;\n\t\t\ttargetPath = name.Substring( 0, dot ); varName = name.Substring( dot \u002B 1 );\n\t\t}\n\t\tvar start = targetPath.Length == 0 ? c.Target : ResolveTargetPath( c, targetPath );\n\t\tif ( start == null )\n\t\t{\n\t\t\tvar parts = name.Split( \u0027.\u0027 );\n\t\t\tvar cur = GetVariableNoPath( c, parts[0] );\n\t\t\tfor ( int k = 1; k \u003C parts.Length - 1; k\u002B\u002B ) cur = GetMember( cur, parts[k] );\n\t\t\tif ( cur.IsObject ) { cur.Obj.Set( parts[^1], v, this ); return true; }\n\t\t\treturn false;\n\t\t}\n\t\tstart.Set( varName, v, this );\n\t\treturn true;\n\t}\n\n\t// ======================================================================= frames\n\n\tvoid GotoFrameValue( Ctx c, Value fv, int bias, bool stop )\n\t{\n\t\tvar target = c.Target?.AsClip;\n\t\tif ( fv.IsString )\n\t\t{\n\t\t\tvar s = fv.Str;\n\t\t\tint colon = s.LastIndexOf( \u0027:\u0027 );\n\t\t\tif ( colon \u003E= 0 )\n\t\t\t{\n\t\t\t\ttarget = ResolveTargetPath( c, s.Substring( 0, colon ) )?.AsClip;\n\t\t\t\ts = s.Substring( colon \u002B 1 );\n\t\t\t}\n\t\t\tif ( target == null ) return;\n\t\t\tvar n = Value.StringToNumber( s, SwfVersion );\n\t\t\tif ( !double.IsNaN( n ) ) target.GotoFrame( (int)n \u002B bias, stop );\n\t\t\telse target.GotoLabel( s, stop );\n\t\t\treturn;\n\t\t}\n\t\tif ( target == null ) return;\n\t\ttarget.GotoFrame( Value.ToInt32( fv.ToNumber( this ) ) \u002B bias, stop );\n\t}\n\n\tvoid CallFrame( Ctx c, string fv )\n\t{\n\t\tvar target = c.Target?.AsClip;\n\t\tvar s = fv;\n\t\tint colon = s.LastIndexOf( \u0027:\u0027 );\n\t\tif ( colon \u003E= 0 ) { target = ResolveTargetPath( c, s.Substring( 0, colon ) )?.AsClip; s = s.Substring( colon \u002B 1 ); }\n\t\tif ( target == null ) return;\n\t\tvar n = Value.StringToNumber( s, SwfVersion );\n\t\tint frame = !double.IsNaN( n ) ? (int)n : target.FrameOfLabel( s );\n\t\tif ( frame \u003C= 0 ) return;\n\t\tforeach ( var code in target.FrameScripts( frame ) ) RunScript( target, code );\n\t}\n\n\t// ======================================================================= comparison\n\n\tpublic Value LessThan( Value a, Value b )\n\t{\n\t\tvar pa = a.IsObject ? a.Obj.ToPrimitive( this, false ) : a;\n\t\tvar pb = b.IsObject ? b.Obj.ToPrimitive( this, false ) : b;\n\t\tif ( pa.IsString \u0026\u0026 pb.IsString ) return string.CompareOrdinal( pa.Str, pb.Str ) \u003C 0;\n\t\tdouble x = pa.ToNumber( this ), y = pb.ToNumber( this );\n\t\tif ( double.IsNaN( x ) || double.IsNaN( y ) ) return Value.Undefined;\n\t\treturn x \u003C y;\n\t}\n\n\tpublic bool StrictEquals( Value a, Value b )\n\t{\n\t\tif ( a.Kind != b.Kind ) return false;\n\t\tswitch ( a.Kind )\n\t\t{\n\t\t\tcase ValueKind.Undefined: case ValueKind.Null: return true;\n\t\t\tcase ValueKind.Number: return a.Num == b.Num;\n\t\t\tcase ValueKind.Bool: return a.Num == b.Num;\n\t\t\tcase ValueKind.String: return a.Str == b.Str;\n\t\t\tdefault: return ReferenceEquals( a.Obj, b.Obj );\n\t\t}\n\t}\n\n\tpublic bool AbstractEquals( Value a, Value b )\n\t{\n\t\tif ( a.Kind == b.Kind ) return StrictEquals( a, b );\n\t\tif ( a.IsNullish \u0026\u0026 b.IsNullish ) return true;\n\t\tif ( a.IsNullish || b.IsNullish ) return false;\n\t\tif ( a.IsNumber \u0026\u0026 b.IsString ) return a.Num == b.ToNumber( this );\n\t\tif ( a.IsString \u0026\u0026 b.IsNumber ) return a.ToNumber( this ) == b.Num;\n\t\tif ( a.IsBool ) return AbstractEquals( a.Num, b );\n\t\tif ( b.IsBool ) return AbstractEquals( a, b.Num );\n\t\tif ( (a.IsNumber || a.IsString) \u0026\u0026 b.IsObject ) return AbstractEquals( a, b.Obj.ToPrimitive( this, false ) );\n\t\tif ( a.IsObject \u0026\u0026 (b.IsNumber || b.IsString) ) return AbstractEquals( a.Obj.ToPrimitive( this, false ), b );\n\t\treturn false;\n\t}\n}\n\n/// \u003Csummary\u003EStops a runaway script.\u003C/summary\u003E\npublic sealed class ScriptAbort : Exception\n{\n\tpublic ScriptAbort( string m ) : base( m ) { }\n}\n\n/// \u003Csummary\u003EThe value of \u0060super\u0060 inside a method or constructor.\u003C/summary\u003E\npublic sealed class SuperObject : AsObject\n{\n\treadonly Avm1 vm;\n\treadonly Value thisV;\n\treadonly AsObject owner; // prototype that owns the running method (its __constructor__ is the parent class)\n\n\tpublic SuperObject( Avm1 vm, Value thisV, AsObject owner ) : base( owner?.Proto ) { this.vm = vm; this.thisV = thisV; this.owner = owner; }\n\n\tpublic Value CallMethod( string name, Value[] args )\n\t{\n\t\tvar start = owner?.Proto;\n\t\tif ( start is null ) return Value.Undefined;\n\t\tvar f = start.Get( name, vm ).AsObj as AsFunction;\n\t\tif ( f is null ) return Value.Undefined;\n\t\tAsObject found = start; int g = 0;\n\t\twhile ( found != null \u0026\u0026 found.GetOwnProperty( name ) == null \u0026\u0026 g\u002B\u002B \u003C 64 ) found = found.Proto;\n\t\treturn f.Call( vm, thisV, args, found );\n\t}\n\n\t/// \u003Csummary\u003Esuper(...) \u2014 runs the parent class constructor on this.\u003C/summary\u003E\n\tpublic Value CallConstructor( Value[] args )\n\t{\n\t\tvar ctor = owner?.Get( \u0022__constructor__\u0022, vm ).AsObj as AsFunction;\n\t\tif ( ctor is null ) return Value.Undefined;\n\t\treturn ctor.Call( vm, thisV, args, owner.Proto );\n\t}\n}\n"},{"Ident":"farten.shoes_and_daggers","Path":"Flash/Core/Builtins.cs","FileName":"Builtins.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":443203,"IsPrivate":false,"Code":"using System;\nusing System.Collections.Generic;\nusing System.Globalization;\nusing System.Text;\n\nnamespace Flash;\n\n/// \u003Csummary\u003EThe ActionScript 2 standard library (the parts SS2 uses, plus the obvious neighbours).\u003C/summary\u003E\npublic static class Builtins\n{\n\tstatic Value Arg( Value[] a, int i ) =\u003E i \u003C a.Length ? a[i] : Value.Undefined;\n\n\tpublic static void Install( Avm1 vm )\n\t{\n\t\tvar objectProto = new AsObject( null );\n\t\tvm.ObjectProto = objectProto;\n\t\tvar functionProto = new AsObject( objectProto );\n\t\tvm.FunctionProto = functionProto;\n\t\tvar global = new AsObject( objectProto );\n\t\tvm.Global = global;\n\n\t\tInstallObject( vm );\n\t\tInstallFunction( vm );\n\t\tInstallArray( vm );\n\t\tInstallString( vm );\n\t\tInstallNumberBoolean( vm );\n\t\tInstallMath( vm );\n\t\tInstallDate( vm );\n\t\tInstallGlobals( vm );\n\t\tInstallMovieClip( vm );\n\t\tInstallTextFieldButton( vm );\n\t\tInstallSound( vm );\n\t\tInstallColor( vm );\n\t\tInstallKeyMouseStage( vm );\n\t\tInstallSharedObject( vm );\n\t\tInstallBroadcaster( vm );\n\t}\n\n\tstatic NativeFunction Ctor( Avm1 vm, string name, AsObject proto, NativeCall call, NativeCall construct = null )\n\t{\n\t\tvar f = vm.Native( name, call );\n\t\tf.Constructor = construct;\n\t\tf.SetOwn( \u0022prototype\u0022, Value.From( proto ), PropFlags.DontEnum | PropFlags.DontDelete );\n\t\tproto.DefineHidden( \u0022constructor\u0022, Value.From( f ) );\n\t\tvm.Global.DefineHidden( name, Value.From( f ) );\n\t\treturn f;\n\t}\n\n\t// ========================================================================= Object\n\n\tstatic void InstallObject( Avm1 vm )\n\t{\n\t\tvar p = vm.ObjectProto;\n\t\tvm.ObjectCtor = Ctor( vm, \u0022Object\u0022, p, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar v = Arg( a, 0 );\n\t\t\tif ( v.IsObject ) return v;\n\t\t\tif ( !v.IsNullish ) return Value.From( vm.ToObject( v ) );\n\t\t\treturn t.IsObject \u0026\u0026 !(t.Obj is DisplayObject) ? t : Value.From( vm.NewObject() );\n\t\t}, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar v = Arg( a, 0 );\n\t\t\tif ( v.IsObject ) return v;\n\t\t\tif ( !v.IsNullish ) return Value.From( vm.ToObject( v ) );\n\t\t\treturn Value.From( vm.NewObject() );\n\t\t} );\n\t\tvm.Method( p, \u0022hasOwnProperty\u0022, ( vm, t, a ) =\u003E t.IsObject \u0026\u0026 t.Obj.HasOwnProperty( Arg( a, 0 ).ToStr( vm ) ) );\n\t\tvm.Method( p, \u0022toString\u0022, ( vm, t, a ) =\u003E t.IsObject \u0026\u0026 t.Obj is AsFunction ? \u0022[type Function]\u0022 : \u0022[object Object]\u0022 );\n\t\tvm.Method( p, \u0022toLocaleString\u0022, ( vm, t, a ) =\u003E \u0022[object Object]\u0022 );\n\t\tvm.Method( p, \u0022valueOf\u0022, ( vm, t, a ) =\u003E t );\n\t\tvm.Method( p, \u0022isPropertyEnumerable\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tif ( !t.IsObject ) return false;\n\t\t\tvar pr = t.Obj.GetOwnProperty( Arg( a, 0 ).ToStr( vm ) );\n\t\t\treturn pr != null \u0026\u0026 (pr.Flags \u0026 PropFlags.DontEnum) == 0;\n\t\t} );\n\t\tvm.Method( p, \u0022isPrototypeOf\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar o = Arg( a, 0 ).AsObj;\n\t\t\tif ( !t.IsObject || o == null ) return false;\n\t\t\tfor ( var x = o.Proto; x != null; x = x.Proto ) if ( ReferenceEquals( x, t.Obj ) ) return true;\n\t\t\treturn false;\n\t\t} );\n\t\tvm.Method( p, \u0022addProperty\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tif ( !t.IsObject ) return false;\n\t\t\tvar name = Arg( a, 0 ).ToStr( vm );\n\t\t\tvar g = Arg( a, 1 ).AsObj as AsFunction;\n\t\t\tvar s = Arg( a, 2 ).AsObj as AsFunction;\n\t\t\tif ( name.Length == 0 || g == null ) return false;\n\t\t\tt.Obj.AddVirtual( name, g, s );\n\t\t\treturn true;\n\t\t} );\n\t\tvm.Method( p, \u0022watch\u0022, ( vm, t, a ) =\u003E false );\n\t\tvm.Method( p, \u0022unwatch\u0022, ( vm, t, a ) =\u003E false );\n\t\tvar ctor = vm.ObjectCtor;\n\t\tvm.Method( ctor, \u0022registerClass\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar name = Arg( a, 0 ).ToStr( vm );\n\t\t\tif ( Arg( a, 1 ).AsObj is AsFunction f ) vm.Player.RegisteredClasses[name] = f;\n\t\t\telse vm.Player.RegisteredClasses.Remove( name );\n\t\t\treturn true;\n\t\t} );\n\t}\n\n\t// ========================================================================= Function\n\n\tstatic void InstallFunction( Avm1 vm )\n\t{\n\t\tvar p = vm.FunctionProto;\n\t\tvm.FunctionCtor = Ctor( vm, \u0022Function\u0022, p, ( vm, t, a ) =\u003E Arg( a, 0 ), ( vm, t, a ) =\u003E Arg( a, 0 ).IsObject ? Arg( a, 0 ) : Value.From( vm.NewObject() ) );\n\t\tvm.Method( p, \u0022call\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tif ( t.AsObj is not AsFunction f ) return Value.Undefined;\n\t\t\tvar thisArg = Arg( a, 0 );\n\t\t\tvar rest = a.Length \u003E 1 ? a[1..] : Array.Empty\u003CValue\u003E();\n\t\t\treturn f.Call( vm, thisArg.IsNullish ? Value.Undefined : thisArg, rest );\n\t\t} );\n\t\tvm.Method( p, \u0022apply\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tif ( t.AsObj is not AsFunction f ) return Value.Undefined;\n\t\t\tvar thisArg = Arg( a, 0 );\n\t\t\tvar args = Array.Empty\u003CValue\u003E();\n\t\t\tif ( Arg( a, 1 ).AsObj is AsArray arr ) args = arr.Items.ToArray();\n\t\t\telse if ( Arg( a, 1 ).IsObject )\n\t\t\t{\n\t\t\t\tint n = Arg( a, 1 ).Obj.Get( \u0022length\u0022, vm ).ToInt32( vm );\n\t\t\t\targs = new Value[Math.Max( 0, n )];\n\t\t\t\tfor ( int i = 0; i \u003C args.Length; i\u002B\u002B ) args[i] = Arg( a, 1 ).Obj.Get( i.ToString(), vm );\n\t\t\t}\n\t\t\treturn f.Call( vm, thisArg.IsNullish ? Value.Undefined : thisArg, args );\n\t\t} );\n\t\tvm.Method( p, \u0022toString\u0022, ( vm, t, a ) =\u003E \u0022[type Function]\u0022 );\n\t}\n\n\t// ========================================================================= Array\n\n\tstatic void InstallArray( Avm1 vm )\n\t{\n\t\tvar p = new AsArray( vm.ObjectProto );\n\t\tvm.ArrayProto = p;\n\t\tNativeCall make = ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar arr = vm.NewArray();\n\t\t\tif ( a.Length == 1 \u0026\u0026 a[0].IsNumber )\n\t\t\t{\n\t\t\t\tint n = Value.ToInt32( a[0].Num );\n\t\t\t\tfor ( int i = 0; i \u003C n; i\u002B\u002B ) arr.Items.Add( Value.Undefined );\n\t\t\t}\n\t\t\telse arr.Items.AddRange( a );\n\t\t\treturn arr;\n\t\t};\n\t\tvm.ArrayCtor = Ctor( vm, \u0022Array\u0022, p, make, make );\n\t\tvar ctor = vm.ArrayCtor;\n\t\tctor.SetOwn( \u0022CASEINSENSITIVE\u0022, 1 ); ctor.SetOwn( \u0022DESCENDING\u0022, 2 ); ctor.SetOwn( \u0022UNIQUESORT\u0022, 4 );\n\t\tctor.SetOwn( \u0022RETURNINDEXEDARRAY\u0022, 8 ); ctor.SetOwn( \u0022NUMERIC\u0022, 16 );\n\n\t\tAsArray A( Value t ) =\u003E t.AsObj as AsArray;\n\n\t\tvm.Method( p, \u0022push\u0022, ( vm, t, a ) =\u003E { var arr = A( t ); if ( arr == null ) return Value.Undefined; arr.Items.AddRange( a ); return arr.Items.Count; } );\n\t\tvm.Method( p, \u0022pop\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar arr = A( t ); if ( arr == null || arr.Items.Count == 0 ) return Value.Undefined;\n\t\t\tvar v = arr.Items[^1]; arr.Items.RemoveAt( arr.Items.Count - 1 ); return v;\n\t\t} );\n\t\tvm.Method( p, \u0022shift\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar arr = A( t ); if ( arr == null || arr.Items.Count == 0 ) return Value.Undefined;\n\t\t\tvar v = arr.Items[0]; arr.Items.RemoveAt( 0 ); return v;\n\t\t} );\n\t\tvm.Method( p, \u0022unshift\u0022, ( vm, t, a ) =\u003E { var arr = A( t ); if ( arr == null ) return Value.Undefined; arr.Items.InsertRange( 0, a ); return arr.Items.Count; } );\n\t\tvm.Method( p, \u0022reverse\u0022, ( vm, t, a ) =\u003E { var arr = A( t ); arr?.Items.Reverse(); return t; } );\n\t\tvm.Method( p, \u0022join\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar arr = A( t ); if ( arr == null ) return \u0022\u0022;\n\t\t\tvar sep = Arg( a, 0 ).IsUndefined ? \u0022,\u0022 : Arg( a, 0 ).ToStr( vm );\n\t\t\treturn Join( vm, arr, sep );\n\t\t} );\n\t\tvm.Method( p, \u0022toString\u0022, ( vm, t, a ) =\u003E { var arr = A( t ); return arr == null ? \u0022\u0022 : Join( vm, arr, \u0022,\u0022 ); } );\n\t\tvm.Method( p, \u0022concat\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar arr = A( t );\n\t\t\tvar r = vm.NewArray( arr?.Items );\n\t\t\tforeach ( var x in a ) { if ( x.AsObj is AsArray xa ) r.Items.AddRange( xa.Items ); else r.Items.Add( x ); }\n\t\t\treturn r;\n\t\t} );\n\t\tvm.Method( p, \u0022slice\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar arr = A( t ); if ( arr == null ) return vm.NewArray();\n\t\t\tint n = arr.Items.Count;\n\t\t\tint s = Clamp( Arg( a, 0 ).IsUndefined ? 0 : Arg( a, 0 ).ToInt32( vm ), n );\n\t\t\tint e = Clamp( Arg( a, 1 ).IsUndefined ? n : Arg( a, 1 ).ToInt32( vm ), n );\n\t\t\tvar r = vm.NewArray();\n\t\t\tfor ( int i = s; i \u003C e; i\u002B\u002B ) r.Items.Add( arr.Items[i] );\n\t\t\treturn r;\n\t\t} );\n\t\tvm.Method( p, \u0022splice\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar arr = A( t ); if ( arr == null || a.Length == 0 ) return Value.Undefined;\n\t\t\tint n = arr.Items.Count;\n\t\t\tint s = Clamp( Arg( a, 0 ).ToInt32( vm ), n );\n\t\t\tint del = a.Length \u003E 1 ? Math.Clamp( Arg( a, 1 ).ToInt32( vm ), 0, n - s ) : n - s;\n\t\t\tvar removed = vm.NewArray( arr.Items.GetRange( s, del ) );\n\t\t\tarr.Items.RemoveRange( s, del );\n\t\t\tif ( a.Length \u003E 2 ) arr.Items.InsertRange( s, a[2..] );\n\t\t\treturn removed;\n\t\t} );\n\t\tvm.Method( p, \u0022sort\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar arr = A( t ); if ( arr == null ) return Value.Undefined;\n\t\t\tAsFunction cmp = Arg( a, 0 ).AsObj as AsFunction;\n\t\t\tint flags = cmp != null ? Arg( a, 1 ).ToInt32( vm ) : (Arg( a, 0 ).IsNumber ? Arg( a, 0 ).ToInt32( vm ) : 0);\n\t\t\treturn SortArray( vm, arr, flags, ( x, y ) =\u003E\n\t\t\t{\n\t\t\t\tif ( cmp != null ) return Math.Sign( vm.CallFunction( cmp, Value.Undefined, new[] { x, y } ).ToNumber( vm ) );\n\t\t\t\treturn CompareDefault( vm, x, y, flags );\n\t\t\t} );\n\t\t} );\n\t\tvm.Method( p, \u0022sortOn\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar arr = A( t ); if ( arr == null ) return Value.Undefined;\n\t\t\tvar fields = new List\u003Cstring\u003E();\n\t\t\tif ( Arg( a, 0 ).AsObj is AsArray fa ) foreach ( var f in fa.Items ) fields.Add( f.ToStr( vm ) );\n\t\t\telse fields.Add( Arg( a, 0 ).ToStr( vm ) );\n\t\t\tint flags = Arg( a, 1 ).IsNumber ? Arg( a, 1 ).ToInt32( vm ) : 0;\n\t\t\treturn SortArray( vm, arr, flags, ( x, y ) =\u003E\n\t\t\t{\n\t\t\t\tforeach ( var f in fields )\n\t\t\t\t{\n\t\t\t\t\tint c = CompareDefault( vm, vm.GetMember( x, f ), vm.GetMember( y, f ), flags );\n\t\t\t\t\tif ( c != 0 ) return c;\n\t\t\t\t}\n\t\t\t\treturn 0;\n\t\t\t} );\n\t\t} );\n\t}\n\n\tstatic int Clamp( int i, int n ) =\u003E i \u003C 0 ? Math.Max( 0, n \u002B i ) : Math.Min( i, n );\n\n\tstatic string Join( Avm1 vm, AsArray arr, string sep )\n\t{\n\t\tvar sb = new StringBuilder();\n\t\tfor ( int i = 0; i \u003C arr.Items.Count; i\u002B\u002B )\n\t\t{\n\t\t\tif ( i \u003E 0 ) sb.Append( sep );\n\t\t\tvar v = arr.Items[i];\n\t\t\tif ( !v.IsNullish ) sb.Append( v.ToStr( vm ) );\n\t\t\telse if ( v.IsNull ) sb.Append( \u0022null\u0022 );\n\t\t\telse sb.Append( \u0022undefined\u0022 );\n\t\t}\n\t\treturn sb.ToString();\n\t}\n\n\tstatic int CompareDefault( Avm1 vm, Value x, Value y, int flags )\n\t{\n\t\tint c;\n\t\tif ( (flags \u0026 16) != 0 )\n\t\t{\n\t\t\tdouble a = x.ToNumber( vm ), b = y.ToNumber( vm );\n\t\t\tc = a \u003C b ? -1 : a \u003E b ? 1 : 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tvar a = x.ToStr( vm ); var b = y.ToStr( vm );\n\t\t\tc = (flags \u0026 1) != 0 ? string.Compare( a, b, StringComparison.OrdinalIgnoreCase ) : string.CompareOrdinal( a, b );\n\t\t\tc = Math.Sign( c );\n\t\t}\n\t\treturn (flags \u0026 2) != 0 ? -c : c;\n\t}\n\n\tstatic Value SortArray( Avm1 vm, AsArray arr, int flags, Comparison\u003CValue\u003E cmp )\n\t{\n\t\tvar idx = new List\u003Cint\u003E();\n\t\tfor ( int i = 0; i \u003C arr.Items.Count; i\u002B\u002B ) idx.Add( i );\n\t\tvar items = arr.Items;\n\t\t// stable merge sort via index comparison\n\t\tidx.Sort( ( i, j ) =\u003E { int c = cmp( items[i], items[j] ); return c != 0 ? c : i.CompareTo( j ); } );\n\t\tif ( (flags \u0026 4) != 0 )\n\t\t\tfor ( int k = 1; k \u003C idx.Count; k\u002B\u002B ) if ( cmp( items[idx[k - 1]], items[idx[k]] ) == 0 ) return 0;\n\t\tif ( (flags \u0026 8) != 0 )\n\t\t{\n\t\t\tvar r = vm.NewArray();\n\t\t\tforeach ( var i in idx ) r.Items.Add( i );\n\t\t\treturn r;\n\t\t}\n\t\tvar sorted = new List\u003CValue\u003E();\n\t\tforeach ( var i in idx ) sorted.Add( items[i] );\n\t\tarr.Items.Clear();\n\t\tarr.Items.AddRange( sorted );\n\t\treturn Value.From( arr );\n\t}\n\n\t// ========================================================================= String\n\n\tstatic void InstallString( Avm1 vm )\n\t{\n\t\tvar p = new AsBoxed( vm.ObjectProto, \u0022\u0022 );\n\t\tvm.StringProto = p;\n\t\tvar ctor = Ctor( vm, \u0022String\u0022, p, ( vm, t, a ) =\u003E a.Length == 0 ? \u0022\u0022 : (Value)Arg( a, 0 ).ToStr( vm ),\n\t\t\t( vm, t, a ) =\u003E Value.From( new AsBoxed( vm.StringProto, a.Length == 0 ? \u0022\u0022 : Arg( a, 0 ).ToStr( vm ) ) ) );\n\t\tvm.Method( ctor, \u0022fromCharCode\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar sb = new StringBuilder();\n\t\t\tforeach ( var c in a ) sb.Append( (char)c.ToInt32( vm ) );\n\t\t\treturn sb.ToString();\n\t\t} );\n\t\tstring S( Avm1 vm, Value t ) =\u003E t.IsObject \u0026\u0026 t.Obj is AsBoxed b ? b.Primitive.ToStr( vm ) : t.ToStr( vm );\n\n\t\tvm.Method( p, \u0022toString\u0022, ( vm, t, a ) =\u003E S( vm, t ) );\n\t\tvm.Method( p, \u0022valueOf\u0022, ( vm, t, a ) =\u003E S( vm, t ) );\n\t\tvm.Method( p, \u0022charAt\u0022, ( vm, t, a ) =\u003E { var s = S( vm, t ); int i = Arg( a, 0 ).ToInt32( vm ); return i \u003E= 0 \u0026\u0026 i \u003C s.Length ? s[i].ToString() : \u0022\u0022; } );\n\t\tvm.Method( p, \u0022charCodeAt\u0022, ( vm, t, a ) =\u003E { var s = S( vm, t ); int i = Arg( a, 0 ).ToInt32( vm ); return i \u003E= 0 \u0026\u0026 i \u003C s.Length ? s[i] : Value.NaN; } );\n\t\tvm.Method( p, \u0022concat\u0022, ( vm, t, a ) =\u003E { var sb = new StringBuilder( S( vm, t ) ); foreach ( var x in a ) sb.Append( x.ToStr( vm ) ); return sb.ToString(); } );\n\t\tvm.Method( p, \u0022indexOf\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar s = S( vm, t ); var f = Arg( a, 0 ).ToStr( vm );\n\t\t\tint start = Arg( a, 1 ).IsUndefined ? 0 : Math.Clamp( Arg( a, 1 ).ToInt32( vm ), 0, s.Length );\n\t\t\treturn s.IndexOf( f, start, StringComparison.Ordinal );\n\t\t} );\n\t\tvm.Method( p, \u0022lastIndexOf\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar s = S( vm, t ); var f = Arg( a, 0 ).ToStr( vm );\n\t\t\tint start = Arg( a, 1 ).IsUndefined ? s.Length : Math.Clamp( Arg( a, 1 ).ToInt32( vm ), 0, s.Length );\n\t\t\tif ( f.Length == 0 ) return Math.Min( start, s.Length );\n\t\t\tint from = Math.Min( start \u002B f.Length - 1, s.Length - 1 );\n\t\t\treturn from \u003C 0 ? -1 : s.LastIndexOf( f, from, StringComparison.Ordinal );\n\t\t} );\n\t\tvm.Method( p, \u0022slice\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar s = S( vm, t ); int n = s.Length;\n\t\t\tint st = Clamp( Arg( a, 0 ).IsUndefined ? 0 : Arg( a, 0 ).ToInt32( vm ), n );\n\t\t\tint e = Clamp( Arg( a, 1 ).IsUndefined ? n : Arg( a, 1 ).ToInt32( vm ), n );\n\t\t\treturn e \u003E st ? s.Substring( st, e - st ) : \u0022\u0022;\n\t\t} );\n\t\tvm.Method( p, \u0022substring\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar s = S( vm, t ); int n = s.Length;\n\t\t\tint st = Math.Clamp( Arg( a, 0 ).ToInt32( vm ), 0, n );\n\t\t\tint e = Arg( a, 1 ).IsUndefined ? n : Math.Clamp( Arg( a, 1 ).ToInt32( vm ), 0, n );\n\t\t\tif ( st \u003E e ) (st, e) = (e, st);\n\t\t\treturn s.Substring( st, e - st );\n\t\t} );\n\t\tvm.Method( p, \u0022substr\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar s = S( vm, t ); int n = s.Length;\n\t\t\tint st = Clamp( Arg( a, 0 ).ToInt32( vm ), n );\n\t\t\tint len = Arg( a, 1 ).IsUndefined ? n - st : Math.Clamp( Arg( a, 1 ).ToInt32( vm ), 0, n - st );\n\t\t\treturn s.Substring( st, len );\n\t\t} );\n\t\tvm.Method( p, \u0022split\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar s = S( vm, t );\n\t\t\tvar r = vm.NewArray();\n\t\t\tif ( Arg( a, 0 ).IsUndefined ) { r.Items.Add( s ); return r; }\n\t\t\tvar sep = Arg( a, 0 ).ToStr( vm );\n\t\t\tint limit = Arg( a, 1 ).IsUndefined ? int.MaxValue : Arg( a, 1 ).ToInt32( vm );\n\t\t\tif ( sep.Length == 0 ) { foreach ( var ch in s ) { if ( r.Items.Count \u003E= limit ) break; r.Items.Add( ch.ToString() ); } return r; }\n\t\t\tforeach ( var part in s.Split( sep ) ) { if ( r.Items.Count \u003E= limit ) break; r.Items.Add( part ); }\n\t\t\treturn r;\n\t\t} );\n\t\tvm.Method( p, \u0022toLowerCase\u0022, ( vm, t, a ) =\u003E S( vm, t ).ToLowerInvariant() );\n\t\tvm.Method( p, \u0022toUpperCase\u0022, ( vm, t, a ) =\u003E S( vm, t ).ToUpperInvariant() );\n\t}\n\n\t// ========================================================================= Number / Boolean\n\n\tstatic void InstallNumberBoolean( Avm1 vm )\n\t{\n\t\tvar np = new AsBoxed( vm.ObjectProto, 0 );\n\t\tvm.NumberProto = np;\n\t\tvar nc = Ctor( vm, \u0022Number\u0022, np, ( vm, t, a ) =\u003E a.Length == 0 ? 0 : Arg( a, 0 ).ToNumber( vm ),\n\t\t\t( vm, t, a ) =\u003E Value.From( new AsBoxed( vm.NumberProto, a.Length == 0 ? 0 : Arg( a, 0 ).ToNumber( vm ) ) ) );\n\t\tnc.SetOwn( \u0022MAX_VALUE\u0022, double.MaxValue ); nc.SetOwn( \u0022MIN_VALUE\u0022, double.Epsilon ); nc.SetOwn( \u0022NaN\u0022, double.NaN );\n\t\tnc.SetOwn( \u0022POSITIVE_INFINITY\u0022, double.PositiveInfinity ); nc.SetOwn( \u0022NEGATIVE_INFINITY\u0022, double.NegativeInfinity );\n\t\tdouble N( Avm1 vm, Value t ) =\u003E t.IsObject \u0026\u0026 t.Obj is AsBoxed b ? b.Primitive.ToNumber( vm ) : t.ToNumber( vm );\n\t\tvm.Method( np, \u0022valueOf\u0022, ( vm, t, a ) =\u003E N( vm, t ) );\n\t\tvm.Method( np, \u0022toString\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tdouble d = N( vm, t );\n\t\t\tint radix = Arg( a, 0 ).IsUndefined ? 10 : Arg( a, 0 ).ToInt32( vm );\n\t\t\tif ( radix == 10 || radix \u003C 2 || radix \u003E 36 || double.IsNaN( d ) || double.IsInfinity( d ) ) return Value.NumberToString( d );\n\t\t\tlong n = (long)d;\n\t\t\tbool neg = n \u003C 0; if ( neg ) n = -n;\n\t\t\tvar sb = new StringBuilder();\n\t\t\tdo { int dg = (int)(n % radix); sb.Insert( 0, (char)(dg \u003C 10 ? \u00270\u0027 \u002B dg : \u0027a\u0027 \u002B dg - 10) ); n /= radix; } while ( n \u003E 0 );\n\t\t\tif ( neg ) sb.Insert( 0, \u0027-\u0027 );\n\t\t\treturn sb.ToString();\n\t\t} );\n\n\t\tvar bp = new AsBoxed( vm.ObjectProto, false );\n\t\tvm.BooleanProto = bp;\n\t\tCtor( vm, \u0022Boolean\u0022, bp, ( vm, t, a ) =\u003E Arg( a, 0 ).ToBool( vm.SwfVersion ),\n\t\t\t( vm, t, a ) =\u003E Value.From( new AsBoxed( vm.BooleanProto, Arg( a, 0 ).ToBool( vm.SwfVersion ) ) ) );\n\t\tbool B( Avm1 vm, Value t ) =\u003E t.IsObject \u0026\u0026 t.Obj is AsBoxed b ? b.Primitive.ToBool( vm.SwfVersion ) : t.ToBool( vm.SwfVersion );\n\t\tvm.Method( bp, \u0022valueOf\u0022, ( vm, t, a ) =\u003E B( vm, t ) );\n\t\tvm.Method( bp, \u0022toString\u0022, ( vm, t, a ) =\u003E B( vm, t ) ? \u0022true\u0022 : \u0022false\u0022 );\n\t}\n\n\t// ========================================================================= Math\n\n\tstatic void InstallMath( Avm1 vm )\n\t{\n\t\tvar m = vm.NewObject();\n\t\tvm.Global.DefineHidden( \u0022Math\u0022, Value.From( m ) );\n\t\tdouble D( Avm1 vm, Value[] a, int i ) =\u003E Arg( a, i ).ToNumber( vm );\n\t\tvm.Method( m, \u0022abs\u0022, ( vm, t, a ) =\u003E Math.Abs( D( vm, a, 0 ) ) );\n\t\tvm.Method( m, \u0022acos\u0022, ( vm, t, a ) =\u003E Math.Acos( D( vm, a, 0 ) ) );\n\t\tvm.Method( m, \u0022asin\u0022, ( vm, t, a ) =\u003E Math.Asin( D( vm, a, 0 ) ) );\n\t\tvm.Method( m, \u0022atan\u0022, ( vm, t, a ) =\u003E Math.Atan( D( vm, a, 0 ) ) );\n\t\tvm.Method( m, \u0022atan2\u0022, ( vm, t, a ) =\u003E Math.Atan2( D( vm, a, 0 ), D( vm, a, 1 ) ) );\n\t\tvm.Method( m, \u0022ceil\u0022, ( vm, t, a ) =\u003E Math.Ceiling( D( vm, a, 0 ) ) );\n\t\tvm.Method( m, \u0022cos\u0022, ( vm, t, a ) =\u003E Math.Cos( D( vm, a, 0 ) ) );\n\t\tvm.Method( m, \u0022exp\u0022, ( vm, t, a ) =\u003E Math.Exp( D( vm, a, 0 ) ) );\n\t\tvm.Method( m, \u0022floor\u0022, ( vm, t, a ) =\u003E Math.Floor( D( vm, a, 0 ) ) );\n\t\tvm.Method( m, \u0022log\u0022, ( vm, t, a ) =\u003E Math.Log( D( vm, a, 0 ) ) );\n\t\tvm.Method( m, \u0022max\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tif ( a.Length == 0 ) return double.NegativeInfinity;\n\t\t\tdouble r = double.NegativeInfinity;\n\t\t\tforeach ( var x in a ) { var d = x.ToNumber( vm ); if ( double.IsNaN( d ) ) return double.NaN; r = Math.Max( r, d ); }\n\t\t\treturn r;\n\t\t} );\n\t\tvm.Method( m, \u0022min\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tif ( a.Length == 0 ) return double.PositiveInfinity;\n\t\t\tdouble r = double.PositiveInfinity;\n\t\t\tforeach ( var x in a ) { var d = x.ToNumber( vm ); if ( double.IsNaN( d ) ) return double.NaN; r = Math.Min( r, d ); }\n\t\t\treturn r;\n\t\t} );\n\t\tvm.Method( m, \u0022pow\u0022, ( vm, t, a ) =\u003E Math.Pow( D( vm, a, 0 ), D( vm, a, 1 ) ) );\n\t\tvm.Method( m, \u0022random\u0022, ( vm, t, a ) =\u003E vm.Rng.NextDouble() );\n\t\tvm.Method( m, \u0022round\u0022, ( vm, t, a ) =\u003E Math.Floor( D( vm, a, 0 ) \u002B 0.5 ) );\n\t\tvm.Method( m, \u0022sin\u0022, ( vm, t, a ) =\u003E Math.Sin( D( vm, a, 0 ) ) );\n\t\tvm.Method( m, \u0022sqrt\u0022, ( vm, t, a ) =\u003E Math.Sqrt( D( vm, a, 0 ) ) );\n\t\tvm.Method( m, \u0022tan\u0022, ( vm, t, a ) =\u003E Math.Tan( D( vm, a, 0 ) ) );\n\t\tm.DefineHidden( \u0022PI\u0022, Math.PI ); m.DefineHidden( \u0022E\u0022, Math.E ); m.DefineHidden( \u0022LN2\u0022, Math.Log( 2 ) ); m.DefineHidden( \u0022LN10\u0022, Math.Log( 10 ) );\n\t\tm.DefineHidden( \u0022LOG2E\u0022, 1 / Math.Log( 2 ) ); m.DefineHidden( \u0022LOG10E\u0022, 1 / Math.Log( 10 ) ); m.DefineHidden( \u0022SQRT1_2\u0022, Math.Sqrt( 0.5 ) ); m.DefineHidden( \u0022SQRT2\u0022, Math.Sqrt( 2 ) );\n\t}\n\n\t// ========================================================================= Date\n\n\tstatic void InstallDate( Avm1 vm )\n\t{\n\t\tvar p = vm.NewObject();\n\t\tvm.DateProto = p;\n\t\tNativeCall make = ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar o = new AsObject( vm.DateProto );\n\t\t\tDateTime dt;\n\t\t\tif ( a.Length == 0 ) dt = DateTime.Now;\n\t\t\telse if ( a.Length == 1 ) dt = new DateTime( 1970, 1, 1, 0, 0, 0, DateTimeKind.Utc ).AddMilliseconds( Arg( a, 0 ).ToNumber( vm ) ).ToLocalTime();\n\t\t\telse\n\t\t\t{\n\t\t\t\tint y = Arg( a, 0 ).ToInt32( vm ); if ( y \u003C 100 ) y \u002B= 1900;\n\t\t\t\tdt = new DateTime( y, 1, 1 ).AddMonths( Arg( a, 1 ).ToInt32( vm ) ).AddDays( (a.Length \u003E 2 ? Arg( a, 2 ).ToInt32( vm ) : 1) - 1 )\n\t\t\t\t\t.AddHours( Arg( a, 3 ).IsUndefined ? 0 : Arg( a, 3 ).ToInt32( vm ) ).AddMinutes( Arg( a, 4 ).IsUndefined ? 0 : Arg( a, 4 ).ToInt32( vm ) )\n\t\t\t\t\t.AddSeconds( Arg( a, 5 ).IsUndefined ? 0 : Arg( a, 5 ).ToInt32( vm ) );\n\t\t\t}\n\t\t\to.Native = dt;\n\t\t\treturn Value.From( o );\n\t\t};\n\t\tCtor( vm, \u0022Date\u0022, p, ( vm, t, a ) =\u003E DateTime.Now.ToString( \u0022ddd MMM d HH:mm:ss yyyy\u0022, CultureInfo.InvariantCulture ), make );\n\t\tDateTime Dt( Value t ) =\u003E t.AsObj?.Native is DateTime d ? d : DateTime.Now;\n\t\tvm.Method( p, \u0022getFullYear\u0022, ( vm, t, a ) =\u003E Dt( t ).Year );\n\t\tvm.Method( p, \u0022getYear\u0022, ( vm, t, a ) =\u003E Dt( t ).Year - 1900 );\n\t\tvm.Method( p, \u0022getMonth\u0022, ( vm, t, a ) =\u003E Dt( t ).Month - 1 );\n\t\tvm.Method( p, \u0022getDate\u0022, ( vm, t, a ) =\u003E Dt( t ).Day );\n\t\tvm.Method( p, \u0022getDay\u0022, ( vm, t, a ) =\u003E (int)Dt( t ).DayOfWeek );\n\t\tvm.Method( p, \u0022getHours\u0022, ( vm, t, a ) =\u003E Dt( t ).Hour );\n\t\tvm.Method( p, \u0022getMinutes\u0022, ( vm, t, a ) =\u003E Dt( t ).Minute );\n\t\tvm.Method( p, \u0022getSeconds\u0022, ( vm, t, a ) =\u003E Dt( t ).Second );\n\t\tvm.Method( p, \u0022getMilliseconds\u0022, ( vm, t, a ) =\u003E Dt( t ).Millisecond );\n\t\tvm.Method( p, \u0022getTime\u0022, ( vm, t, a ) =\u003E (Dt( t ).ToUniversalTime() - new DateTime( 1970, 1, 1, 0, 0, 0, DateTimeKind.Utc )).TotalMilliseconds );\n\t\tvm.Method( p, \u0022valueOf\u0022, ( vm, t, a ) =\u003E (Dt( t ).ToUniversalTime() - new DateTime( 1970, 1, 1, 0, 0, 0, DateTimeKind.Utc )).TotalMilliseconds );\n\t\tvm.Method( p, \u0022getTimezoneOffset\u0022, ( vm, t, a ) =\u003E -(Dt( t ) - Dt( t ).ToUniversalTime()).TotalMinutes );\n\t\tvm.Method( p, \u0022toString\u0022, ( vm, t, a ) =\u003E Dt( t ).ToString( \u0022ddd MMM d HH:mm:ss yyyy\u0022, CultureInfo.InvariantCulture ) );\n\t}\n\n\t// ========================================================================= global functions\n\n\tstatic void InstallGlobals( Avm1 vm )\n\t{\n\t\tvar g = vm.Global;\n\t\tvm.Method( g, \u0022trace\u0022, ( vm, t, a ) =\u003E { vm.Trace( Arg( a, 0 ).ToStr( vm ) ); return Value.Undefined; } );\n\t\tvm.Method( g, \u0022isNaN\u0022, ( vm, t, a ) =\u003E double.IsNaN( Arg( a, 0 ).ToNumber( vm ) ) );\n\t\tvm.Method( g, \u0022isFinite\u0022, ( vm, t, a ) =\u003E { var d = Arg( a, 0 ).ToNumber( vm ); return !double.IsNaN( d ) \u0026\u0026 !double.IsInfinity( d ); } );\n\t\tvm.Method( g, \u0022parseInt\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar s = Arg( a, 0 ).ToStr( vm ).Trim();\n\t\t\tint radix = Arg( a, 1 ).IsUndefined ? 0 : Arg( a, 1 ).ToInt32( vm );\n\t\t\tbool neg = false; int i = 0;\n\t\t\tif ( i \u003C s.Length \u0026\u0026 (s[i] == \u0027-\u0027 || s[i] == \u0027\u002B\u0027) ) { neg = s[i] == \u0027-\u0027; i\u002B\u002B; }\n\t\t\tif ( radix == 0 )\n\t\t\t{\n\t\t\t\tif ( i \u002B 1 \u003C s.Length \u0026\u0026 s[i] == \u00270\u0027 \u0026\u0026 (s[i \u002B 1] == \u0027x\u0027 || s[i \u002B 1] == \u0027X\u0027) ) { radix = 16; i \u002B= 2; }\n\t\t\t\telse if ( i \u002B 1 \u003C s.Length \u0026\u0026 s[i] == \u00270\u0027 ) radix = 8;\n\t\t\t\telse radix = 10;\n\t\t\t}\n\t\t\telse if ( radix == 16 \u0026\u0026 i \u002B 1 \u003C s.Length \u0026\u0026 s[i] == \u00270\u0027 \u0026\u0026 (s[i \u002B 1] == \u0027x\u0027 || s[i \u002B 1] == \u0027X\u0027) ) i \u002B= 2;\n\t\t\tdouble r = 0; bool any = false;\n\t\t\tfor ( ; i \u003C s.Length; i\u002B\u002B )\n\t\t\t{\n\t\t\t\tchar c = char.ToLowerInvariant( s[i] );\n\t\t\t\tint d = c \u003E= \u00270\u0027 \u0026\u0026 c \u003C= \u00279\u0027 ? c - \u00270\u0027 : c \u003E= \u0027a\u0027 \u0026\u0026 c \u003C= \u0027z\u0027 ? c - \u0027a\u0027 \u002B 10 : 99;\n\t\t\t\tif ( d \u003E= radix ) break;\n\t\t\t\tr = r * radix \u002B d; any = true;\n\t\t\t}\n\t\t\treturn any ? (neg ? -r : r) : double.NaN;\n\t\t} );\n\t\tvm.Method( g, \u0022parseFloat\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar s = Arg( a, 0 ).ToStr( vm ).TrimStart();\n\t\t\tint i = 0;\n\t\t\tif ( i \u003C s.Length \u0026\u0026 (s[i] == \u0027-\u0027 || s[i] == \u0027\u002B\u0027) ) i\u002B\u002B;\n\t\t\twhile ( i \u003C s.Length \u0026\u0026 char.IsDigit( s[i] ) ) i\u002B\u002B;\n\t\t\tif ( i \u003C s.Length \u0026\u0026 s[i] == \u0027.\u0027 ) { i\u002B\u002B; while ( i \u003C s.Length \u0026\u0026 char.IsDigit( s[i] ) ) i\u002B\u002B; }\n\t\t\tif ( i \u003C s.Length \u0026\u0026 (s[i] == \u0027e\u0027 || s[i] == \u0027E\u0027) )\n\t\t\t{\n\t\t\t\tint j = i \u002B 1; if ( j \u003C s.Length \u0026\u0026 (s[j] == \u0027-\u0027 || s[j] == \u0027\u002B\u0027) ) j\u002B\u002B;\n\t\t\t\tif ( j \u003C s.Length \u0026\u0026 char.IsDigit( s[j] ) ) { i = j; while ( i \u003C s.Length \u0026\u0026 char.IsDigit( s[i] ) ) i\u002B\u002B; }\n\t\t\t}\n\t\t\treturn double.TryParse( s.Substring( 0, i ), NumberStyles.Float, CultureInfo.InvariantCulture, out var r ) ? r : double.NaN;\n\t\t} );\n\t\tvm.Method( g, \u0022escape\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar s = Arg( a, 0 ).ToStr( vm ); var sb = new StringBuilder();\n\t\t\tforeach ( var b in Encoding.UTF8.GetBytes( s ) )\n\t\t\t{\n\t\t\t\tchar c = (char)b;\n\t\t\t\tif ( (c \u003E= \u0027a\u0027 \u0026\u0026 c \u003C= \u0027z\u0027) || (c \u003E= \u0027A\u0027 \u0026\u0026 c \u003C= \u0027Z\u0027) || (c \u003E= \u00270\u0027 \u0026\u0026 c \u003C= \u00279\u0027) || \u0022@*_\u002B-./\u0022.IndexOf( c ) \u003E= 0 ) sb.Append( c );\n\t\t\t\telse sb.Append( \u0027%\u0027 ).Append( b.ToString( \u0022X2\u0022 ) );\n\t\t\t}\n\t\t\treturn sb.ToString();\n\t\t} );\n\t\tvm.Method( g, \u0022unescape\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar s = Arg( a, 0 ).ToStr( vm ); var bytes = new List\u003Cbyte\u003E();\n\t\t\tfor ( int i = 0; i \u003C s.Length; i\u002B\u002B )\n\t\t\t{\n\t\t\t\tif ( s[i] == \u0027%\u0027 \u0026\u0026 i \u002B 2 \u003C s.Length \u0026\u0026 int.TryParse( s.Substring( i \u002B 1, 2 ), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var h ) ) { bytes.Add( (byte)h ); i \u002B= 2; }\n\t\t\t\telse if ( s[i] == \u0027\u002B\u0027 ) bytes.Add( (byte)\u0027 \u0027 );\n\t\t\t\telse bytes.AddRange( Encoding.UTF8.GetBytes( s[i].ToString() ) );\n\t\t\t}\n\t\t\treturn Encoding.UTF8.GetString( bytes.ToArray() );\n\t\t} );\n\t\tvm.Method( g, \u0022getTimer\u0022, ( vm, t, a ) =\u003E Math.Floor( vm.Player.TimeMs ) );\n\t\tvm.Method( g, \u0022getVersion\u0022, ( vm, t, a ) =\u003E \u0022WIN 11,0,1,152\u0022 );\n\t\tvm.Method( g, \u0022updateAfterEvent\u0022, ( vm, t, a ) =\u003E Value.Undefined );\n\t\tvm.Method( g, \u0022random\u0022, ( vm, t, a ) =\u003E { int n = Arg( a, 0 ).ToInt32( vm ); return n \u003C= 0 ? 0 : vm.Rng.Next( n ); } );\n\t\tvm.Method( g, \u0022int\u0022, ( vm, t, a ) =\u003E (double)Arg( a, 0 ).ToInt32( vm ) );\n\t\tvm.Method( g, \u0022targetPath\u0022, ( vm, t, a ) =\u003E Arg( a, 0 ).AsObj is DisplayObject d ? (Value)d.TargetPath : Value.Undefined );\n\t\tvm.Method( g, \u0022setInterval\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tif ( Arg( a, 0 ).AsObj is AsFunction f ) return vm.Player.SetInterval( f, Value.Undefined, Arg( a, 1 ).ToNumber( vm ), a.Length \u003E 2 ? a[2..] : Array.Empty\u003CValue\u003E() );\n\t\t\tif ( Arg( a, 0 ).IsObject )\n\t\t\t{\n\t\t\t\tvar obj = Arg( a, 0 );\n\t\t\t\tvar fm = vm.GetMember( obj, Arg( a, 1 ).ToStr( vm ) ).AsObj as AsFunction;\n\t\t\t\tif ( fm != null ) return vm.Player.SetInterval( fm, obj, Arg( a, 2 ).ToNumber( vm ), a.Length \u003E 3 ? a[3..] : Array.Empty\u003CValue\u003E() );\n\t\t\t}\n\t\t\treturn Value.Undefined;\n\t\t} );\n\t\tvm.Method( g, \u0022clearInterval\u0022, ( vm, t, a ) =\u003E { vm.Player.ClearInterval( Arg( a, 0 ).ToInt32( vm ) ); return Value.Undefined; } );\n\t\tvm.Method( g, \u0022setTimeout\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tif ( Arg( a, 0 ).AsObj is AsFunction f ) return vm.Player.SetInterval( f, Value.Undefined, Arg( a, 1 ).ToNumber( vm ), a.Length \u003E 2 ? a[2..] : Array.Empty\u003CValue\u003E(), true );\n\t\t\treturn Value.Undefined;\n\t\t} );\n\t\tvm.Method( g, \u0022clearTimeout\u0022, ( vm, t, a ) =\u003E { vm.Player.ClearInterval( Arg( a, 0 ).ToInt32( vm ) ); return Value.Undefined; } );\n\t\tvm.Method( g, \u0022ASSetPropFlags\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar o = Arg( a, 0 ).AsObj; if ( o == null ) return Value.Undefined;\n\t\t\tint set = Arg( a, 2 ).ToInt32( vm ), clear = Arg( a, 3 ).ToInt32( vm );\n\t\t\tvar names = new List\u003Cstring\u003E();\n\t\t\tif ( Arg( a, 1 ).IsNull ) names.AddRange( o.OwnKeys );\n\t\t\telse if ( Arg( a, 1 ).AsObj is AsArray arr ) foreach ( var x in arr.Items ) names.Add( x.ToStr( vm ) );\n\t\t\telse names.AddRange( Arg( a, 1 ).ToStr( vm ).Split( \u0027,\u0027 ) );\n\t\t\tforeach ( var n in names ) o.SetFlags( n, (PropFlags)(set \u0026 7), (PropFlags)(clear \u0026 7) );\n\t\t\treturn Value.Undefined;\n\t\t} );\n\t\tg.DefineHidden( \u0022NaN\u0022, double.NaN );\n\t\tg.DefineHidden( \u0022Infinity\u0022, double.PositiveInfinity );\n\n\t\tvar system = vm.NewObject();\n\t\tvar security = vm.NewObject();\n\t\tvm.Method( security, \u0022allowDomain\u0022, ( vm, t, a ) =\u003E Value.Undefined );\n\t\tvm.Method( security, \u0022allowInsecureDomain\u0022, ( vm, t, a ) =\u003E Value.Undefined );\n\t\tsystem.SetOwn( \u0022security\u0022, Value.From( security ) );\n\t\tvar caps = vm.NewObject();\n\t\tcaps.SetOwn( \u0022version\u0022, \u0022WIN 11,0,1,152\u0022 ); caps.SetOwn( \u0022os\u0022, \u0022Windows\u0022 ); caps.SetOwn( \u0022playerType\u0022, \u0022StandAlone\u0022 );\n\t\tcaps.SetOwn( \u0022screenResolutionX\u0022, 1920 ); caps.SetOwn( \u0022screenResolutionY\u0022, 1080 ); caps.SetOwn( \u0022language\u0022, \u0022en\u0022 );\n\t\tsystem.SetOwn( \u0022capabilities\u0022, Value.From( caps ) );\n\t\tsystem.SetOwn( \u0022useCodepage\u0022, false );\n\t\tg.DefineHidden( \u0022System\u0022, Value.From( system ) );\n\n\t\t// TextFormat (plain property bag)\n\t\tvar tfp = vm.NewObject();\n\t\tCtor( vm, \u0022TextFormat\u0022, tfp, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tif ( !t.IsObject ) return Value.Undefined;\n\t\t\tstring[] names = { \u0022font\u0022, \u0022size\u0022, \u0022color\u0022, \u0022bold\u0022, \u0022italic\u0022, \u0022underline\u0022, \u0022url\u0022, \u0022target\u0022, \u0022align\u0022, \u0022leftMargin\u0022, \u0022rightMargin\u0022, \u0022indent\u0022, \u0022leading\u0022 };\n\t\t\tfor ( int i = 0; i \u003C names.Length; i\u002B\u002B ) t.Obj.Set( names[i], i \u003C a.Length ? a[i] : Value.Null, vm );\n\t\t\treturn Value.Undefined;\n\t\t} );\n\t}\n\n\t// ========================================================================= MovieClip\n\n\tstatic MovieClip Clip( Value t ) =\u003E t.AsObj as MovieClip;\n\tstatic DisplayObject Dobj( Value t ) =\u003E t.AsObj as DisplayObject;\n\n\tstatic void InstallMovieClip( Avm1 vm )\n\t{\n\t\tvar p = vm.NewObject();\n\t\tvm.MovieClipProto = p;\n\t\tCtor( vm, \u0022MovieClip\u0022, p, ( vm, t, a ) =\u003E Value.Undefined );\n\n\t\tvm.Method( p, \u0022play\u0022, ( vm, t, a ) =\u003E { Clip( t )?.Play(); return Value.Undefined; } );\n\t\tvm.Method( p, \u0022stop\u0022, ( vm, t, a ) =\u003E { Clip( t )?.Stop(); return Value.Undefined; } );\n\t\tvm.Method( p, \u0022nextFrame\u0022, ( vm, t, a ) =\u003E { Clip( t )?.NextFrame(); return Value.Undefined; } );\n\t\tvm.Method( p, \u0022prevFrame\u0022, ( vm, t, a ) =\u003E { Clip( t )?.PrevFrame(); return Value.Undefined; } );\n\t\tNativeCall gotoFn( bool stop ) =\u003E ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar mc = Clip( t ); if ( mc == null ) return Value.Undefined;\n\t\t\tvar f = Arg( a, 0 );\n\t\t\tif ( f.IsString )\n\t\t\t{\n\t\t\t\tvar n = Value.StringToNumber( f.Str, vm.SwfVersion );\n\t\t\t\tif ( !double.IsNaN( n ) ) mc.GotoFrame( (int)n, stop ); else mc.GotoLabel( f.Str, stop );\n\t\t\t}\n\t\t\telse mc.GotoFrame( f.ToInt32( vm ), stop );\n\t\t\treturn Value.Undefined;\n\t\t};\n\t\tvm.Method( p, \u0022gotoAndPlay\u0022, gotoFn( false ) );\n\t\tvm.Method( p, \u0022gotoAndStop\u0022, gotoFn( true ) );\n\n\t\tvm.Method( p, \u0022attachMovie\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar mc = Clip( t ); if ( mc == null ) return Value.Undefined;\n\t\t\tvar id = Arg( a, 0 ).ToStr( vm ); var name = Arg( a, 1 ).ToStr( vm ); int depth = Arg( a, 2 ).ToInt32( vm );\n\t\t\tvar def = vm.Player.Lib.GetExport( id );\n\t\t\tif ( def == null ) return Value.Undefined;\n\t\t\tvar obj = vm.Player.Instantiate( def.Id, mc );\n\t\t\tif ( obj == null ) return Value.Undefined;\n\t\t\tobj.Name = name;\n\t\t\tmc.AddChild( obj, depth );\n\t\t\tvm.Player.Construct( obj, Arg( a, 3 ).AsObj, byScript: true );\n\t\t\treturn Value.From( obj );\n\t\t} );\n\t\tvm.Method( p, \u0022createEmptyMovieClip\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar mc = Clip( t ); if ( mc == null ) return Value.Undefined;\n\t\t\tvar c = new MovieClip( vm.Player, null ) { Name = Arg( a, 0 ).ToStr( vm ) };\n\t\t\tmc.AddChild( c, Arg( a, 1 ).ToInt32( vm ) );\n\t\t\tvm.Player.Construct( c, null, true );\n\t\t\treturn Value.From( c );\n\t\t} );\n\t\tvm.Method( p, \u0022duplicateMovieClip\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar mc = Clip( t ); if ( mc == null || mc.Parent == null ) return Value.Undefined;\n\t\t\tvar name = Arg( a, 0 ).ToStr( vm ); int depth = Arg( a, 1 ).ToInt32( vm );\n\t\t\tmc.Duplicate( name, depth );\n\t\t\tvar copy = mc.Parent.ChildAtDepth( depth );\n\t\t\tif ( copy != null \u0026\u0026 Arg( a, 2 ).AsObj is AsObject init )\n\t\t\t\tforeach ( var k in init.EnumerableKeys() ) copy.Set( k, init.Get( k, vm ), vm );\n\t\t\treturn copy != null ? Value.From( copy ) : Value.Undefined;\n\t\t} );\n\t\tvm.Method( p, \u0022removeMovieClip\u0022, ( vm, t, a ) =\u003E { Dobj( t )?.RemoveFromScript(); return Value.Undefined; } );\n\t\tvm.Method( p, \u0022unloadMovie\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar mc = Clip( t ); if ( mc == null ) return Value.Undefined;\n\t\t\tforeach ( var c in mc.Children.ToArray() ) mc.RemoveChild( c );\n\t\t\treturn Value.Undefined;\n\t\t} );\n\t\tvm.Method( p, \u0022loadMovie\u0022, ( vm, t, a ) =\u003E Value.Undefined );\n\t\tvm.Method( p, \u0022swapDepths\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar mc = Clip( t ); if ( mc == null || mc.Parent == null ) return Value.Undefined;\n\t\t\tvar x = Arg( a, 0 );\n\t\t\tif ( x.AsObj is DisplayObject other ) { if ( other.Parent == mc.Parent ) mc.SwapDepths( other.Depth ); }\n\t\t\telse mc.SwapDepths( x.ToInt32( vm ) );\n\t\t\treturn Value.Undefined;\n\t\t} );\n\t\tvm.Method( p, \u0022getDepth\u0022, ( vm, t, a ) =\u003E Dobj( t ) is { } d ? d.Depth : Value.Undefined );\n\t\tvm.Method( p, \u0022getNextHighestDepth\u0022, ( vm, t, a ) =\u003E Clip( t ) is { } mc ? mc.NextHighestDepth() : Value.Undefined );\n\t\tvm.Method( p, \u0022getInstanceAtDepth\u0022, ( vm, t, a ) =\u003E Clip( t )?.ChildAtDepth( Arg( a, 0 ).ToInt32( vm ) ) is { } c ? Value.From( c ) : Value.Undefined );\n\t\tvm.Method( p, \u0022getBytesLoaded\u0022, ( vm, t, a ) =\u003E 7586504 );\n\t\tvm.Method( p, \u0022getBytesTotal\u0022, ( vm, t, a ) =\u003E 7586504 );\n\t\tvm.Method( p, \u0022getSWFVersion\u0022, ( vm, t, a ) =\u003E vm.SwfVersion );\n\t\tvm.Method( p, \u0022getURL\u0022, ( vm, t, a ) =\u003E { vm.Player.GetUrl( Arg( a, 0 ).ToStr( vm ), Arg( a, 1 ).ToStr( vm ) ); return Value.Undefined; } );\n\t\tvm.Method( p, \u0022getBounds\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar d = Dobj( t ); if ( d == null ) return Value.Undefined;\n\t\t\tvar space = Arg( a, 0 ).AsObj as DisplayObject ?? d;\n\t\t\tvar m = space.WorldMatrix.Inverse().Mul( d.WorldMatrix );\n\t\t\tvar b = d.BoundsIn( m );\n\t\t\tvar r = vm.NewObject();\n\t\t\tif ( b == null ) { r.Set( \u0022xMin\u0022, 6710886.35, vm ); r.Set( \u0022yMin\u0022, 6710886.35, vm ); r.Set( \u0022xMax\u0022, 6710886.35, vm ); r.Set( \u0022yMax\u0022, 6710886.35, vm ); return r; }\n\t\t\tr.Set( \u0022xMin\u0022, b.Value.x0, vm ); r.Set( \u0022yMin\u0022, b.Value.y0, vm ); r.Set( \u0022xMax\u0022, b.Value.x1, vm ); r.Set( \u0022yMax\u0022, b.Value.y1, vm );\n\t\t\treturn r;\n\t\t} );\n\t\tvm.Method( p, \u0022localToGlobal\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar d = Dobj( t ); var pt = Arg( a, 0 ).AsObj; if ( d == null || pt == null ) return Value.Undefined;\n\t\t\tvar (x, y) = d.WorldMatrix.Apply( pt.Get( \u0022x\u0022, vm ).ToNumber( vm ), pt.Get( \u0022y\u0022, vm ).ToNumber( vm ) );\n\t\t\tpt.Set( \u0022x\u0022, x, vm ); pt.Set( \u0022y\u0022, y, vm );\n\t\t\treturn Value.Undefined;\n\t\t} );\n\t\tvm.Method( p, \u0022globalToLocal\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar d = Dobj( t ); var pt = Arg( a, 0 ).AsObj; if ( d == null || pt == null ) return Value.Undefined;\n\t\t\tvar (x, y) = d.WorldMatrix.Inverse().Apply( pt.Get( \u0022x\u0022, vm ).ToNumber( vm ), pt.Get( \u0022y\u0022, vm ).ToNumber( vm ) );\n\t\t\tpt.Set( \u0022x\u0022, x, vm ); pt.Set( \u0022y\u0022, y, vm );\n\t\t\treturn Value.Undefined;\n\t\t} );\n\t\tvm.Method( p, \u0022hitTest\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar d = Dobj( t ); if ( d == null ) return false;\n\t\t\tif ( a.Length \u003E= 2 )\n\t\t\t{\n\t\t\t\tdouble x = Arg( a, 0 ).ToNumber( vm ), y = Arg( a, 1 ).ToNumber( vm );\n\t\t\t\tbool shape = Arg( a, 2 ).ToBool( vm.SwfVersion );\n\t\t\t\tif ( shape ) return d.HitTestPoint( x, y, true );\n\t\t\t\tvar b = d.BoundsIn( d.WorldMatrix );\n\t\t\t\treturn b != null \u0026\u0026 x \u003E= b.Value.x0 \u0026\u0026 x \u003C= b.Value.x1 \u0026\u0026 y \u003E= b.Value.y0 \u0026\u0026 y \u003C= b.Value.y1;\n\t\t\t}\n\t\t\tvar other = Arg( a, 0 ).AsObj as DisplayObject;\n\t\t\tif ( other == null ) return false;\n\t\t\tvar b1 = d.BoundsIn( d.WorldMatrix ); var b2 = other.BoundsIn( other.WorldMatrix );\n\t\t\tif ( b1 == null || b2 == null ) return false;\n\t\t\treturn b1.Value.x0 \u003C= b2.Value.x1 \u0026\u0026 b2.Value.x0 \u003C= b1.Value.x1 \u0026\u0026 b1.Value.y0 \u003C= b2.Value.y1 \u0026\u0026 b2.Value.y0 \u003C= b1.Value.y1;\n\t\t} );\n\t\tvm.Method( p, \u0022setMask\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar d = Dobj( t ); if ( d == null ) return Value.Undefined;\n\t\t\tif ( d.MaskedBy != null ) { d.MaskedBy.MaskFor = null; d.MaskedBy = null; }\n\t\t\tif ( Arg( a, 0 ).AsObj is DisplayObject m ) { d.MaskedBy = m; m.MaskFor = d; }\n\t\t\treturn Value.Undefined;\n\t\t} );\n\t\tvm.Method( p, \u0022startDrag\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar d = Dobj( t ); if ( d == null ) return Value.Undefined;\n\t\t\tdouble[] rect = a.Length \u003E= 5 ? new[] { Arg( a, 1 ).ToNumber( vm ), Arg( a, 2 ).ToNumber( vm ), Arg( a, 3 ).ToNumber( vm ), Arg( a, 4 ).ToNumber( vm ) } : null;\n\t\t\tvm.Player.StartDrag( d, Arg( a, 0 ).ToBool( vm.SwfVersion ), rect );\n\t\t\treturn Value.Undefined;\n\t\t} );\n\t\tvm.Method( p, \u0022stopDrag\u0022, ( vm, t, a ) =\u003E { vm.Player.StopDrag(); return Value.Undefined; } );\n\t\tvm.Method( p, \u0022createTextField\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar mc = Clip( t ); if ( mc == null ) return Value.Undefined;\n\t\t\tvar def = new EditTextDef { X0 = Arg( a, 2 ).ToNumber( vm ), Y0 = Arg( a, 3 ).ToNumber( vm ), Size = 12, FontId = 0, Color = new double[] { 0, 0, 0, 255 } };\n\t\t\tdef.X1 = def.X0 \u002B Arg( a, 4 ).ToNumber( vm ); def.Y1 = def.Y0 \u002B Arg( a, 5 ).ToNumber( vm );\n\t\t\tvar tf = new EditText( vm.Player, def ) { Name = Arg( a, 0 ).ToStr( vm ) };\n\t\t\tmc.AddChild( tf, Arg( a, 1 ).ToInt32( vm ) );\n\t\t\treturn Value.From( tf );\n\t\t} );\n\t}\n\n\tstatic void InstallTextFieldButton( Avm1 vm )\n\t{\n\t\tvar tp = vm.NewObject();\n\t\tvm.TextFieldProto = tp;\n\t\tCtor( vm, \u0022TextField\u0022, tp, ( vm, t, a ) =\u003E Value.Undefined );\n\t\tvm.Method( tp, \u0022setTextFormat\u0022, ( vm, t, a ) =\u003E Value.Undefined );\n\t\tvm.Method( tp, \u0022setNewTextFormat\u0022, ( vm, t, a ) =\u003E Value.Undefined );\n\t\tvm.Method( tp, \u0022getTextFormat\u0022, ( vm, t, a ) =\u003E Value.From( vm.NewObject() ) );\n\t\tvm.Method( tp, \u0022getDepth\u0022, ( vm, t, a ) =\u003E Dobj( t ) is { } d ? d.Depth : Value.Undefined );\n\t\tvm.Method( tp, \u0022removeTextField\u0022, ( vm, t, a ) =\u003E { Dobj( t )?.RemoveFromScript(); return Value.Undefined; } );\n\t\tvm.Method( tp, \u0022replaceSel\u0022, ( vm, t, a ) =\u003E Value.Undefined );\n\n\t\tvar bp = vm.NewObject();\n\t\tvm.ButtonProto = bp;\n\t\tCtor( vm, \u0022Button\u0022, bp, ( vm, t, a ) =\u003E Value.Undefined );\n\t\tvm.Method( bp, \u0022getDepth\u0022, ( vm, t, a ) =\u003E Dobj( t ) is { } d ? d.Depth : Value.Undefined );\n\t}\n\n\t// ========================================================================= Sound\n\n\tsealed class SoundState\n\t{\n\t\tpublic DisplayObject Target;\n\t\tpublic SoundDef Def;\n\t\tpublic object Handle;\n\t\tpublic double Volume = 100, Pan = 0;\n\t\tpublic double StartedAt;\n\t\tpublic bool Completed;\n\t}\n\n\tstatic readonly List\u003C(AsObject obj, SoundState st)\u003E liveSounds = new();\n\n\tpublic static void StopAllSoundObjects( Player p )\n\t{\n\t\tforeach ( var (o, st) in liveSounds ) if ( st.Handle != null ) { p.Host?.StopSound( st.Handle ); st.Handle = null; }\n\t}\n\n\t/// \u003Csummary\u003EVolume is per target clip in Flash: all Sound objects on the same target share it.\u003C/summary\u003E\n\tstatic readonly Dictionary\u003CDisplayObject, double\u003E targetVolume = new();\n\n\tstatic void InstallSound( Avm1 vm )\n\t{\n\t\tvar p = vm.NewObject();\n\t\tvm.SoundProto = p;\n\t\tCtor( vm, \u0022Sound\u0022, p, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tif ( !t.IsObject ) return Value.Undefined;\n\t\t\tt.Obj.Native = new SoundState { Target = Arg( a, 0 ).AsObj as DisplayObject };\n\t\t\treturn Value.Undefined;\n\t\t} );\n\t\tSoundState St( Value t ) =\u003E t.AsObj?.Native as SoundState;\n\t\tdouble Vol( SoundState s ) =\u003E IsGlobal( vm.Player, s ) ? vm.Player.GlobalVolume * 100 : (targetVolume.TryGetValue( s.Target, out var v ) ? v : 100);\n\n\t\tvm.Method( p, \u0022attachSound\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar s = St( t ); if ( s == null ) return Value.Undefined;\n\t\t\ts.Def = vm.Player.Lib.GetExport( Arg( a, 0 ).ToStr( vm ) ) as SoundDef;\n\t\t\treturn Value.Undefined;\n\t\t} );\n\t\tvm.Method( p, \u0022start\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar s = St( t ); if ( s?.Def == null ) return Value.Undefined;\n\t\t\tdouble offset = Arg( a, 0 ).IsUndefined ? 0 : Arg( a, 0 ).ToNumber( vm );\n\t\t\tint loops = Arg( a, 1 ).IsUndefined ? 1 : Math.Max( 1, Arg( a, 1 ).ToInt32( vm ) );\n\t\t\tif ( s.Handle != null ) { vm.Player.Host?.StopSound( s.Handle ); s.Handle = null; }\n\t\t\ts.Handle = vm.Player.Host?.PlaySound( s.Def, Effective( vm.Player, s ), s.Pan / 100.0, loops, double.IsNaN( offset ) ? 0 : offset );\n\t\t\ts.StartedAt = vm.Player.TimeMs;\n\t\t\ts.Completed = false;\n\t\t\tliveSounds.RemoveAll( x =\u003E ReferenceEquals( x.obj, t.Obj ) );\n\t\t\tliveSounds.Add( (t.Obj, s) );\n\t\t\treturn Value.Undefined;\n\t\t} );\n\t\tvm.Method( p, \u0022stop\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar s = St( t ); if ( s == null ) return Value.Undefined;\n\t\t\tif ( !Arg( a, 0 ).IsUndefined )\n\t\t\t{\n\t\t\t\tvar def = vm.Player.Lib.GetExport( Arg( a, 0 ).ToStr( vm ) );\n\t\t\t\tforeach ( var (o, st) in liveSounds ) if ( st.Def == def \u0026\u0026 st.Handle != null ) { vm.Player.Host?.StopSound( st.Handle ); st.Handle = null; }\n\t\t\t\treturn Value.Undefined;\n\t\t\t}\n\t\t\tif ( s.Target == null || s.Target == vm.Player.Root ) vm.Player.StopAllSounds();\n\t\t\telse\n\t\t\t{\n\t\t\t\t// Flash: stop() on a targeted Sound stops every sound attached to that clip\n\t\t\t\tforeach ( var (o, st) in liveSounds )\n\t\t\t\t\tif ( st.Target == s.Target \u0026\u0026 st.Handle != null ) { vm.Player.Host?.StopSound( st.Handle ); st.Handle = null; st.Completed = true; }\n\t\t\t\tif ( s.Handle != null ) { vm.Player.Host?.StopSound( s.Handle ); s.Handle = null; }\n\t\t\t}\n\t\t\treturn Value.Undefined;\n\t\t} );\n\t\tvm.Method( p, \u0022setVolume\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar s = St( t ); if ( s == null ) return Value.Undefined;\n\t\t\tvar v = Arg( a, 0 ).ToNumber( vm );\n\t\t\tif ( double.IsNaN( v ) ) return Value.Undefined;\n\t\t\ts.Volume = v;\n\t\t\tif ( IsGlobal( vm.Player, s ) ) vm.Player.SetGlobalVolume( v / 100.0 );\n\t\t\telse { targetVolume[s.Target] = v; RefreshSoundVolumes( vm.Player ); }\n\t\t\treturn Value.Undefined;\n\t\t} );\n\t\tvm.Method( p, \u0022getVolume\u0022, ( vm, t, a ) =\u003E St( t ) is { } s ? Vol( s ) : 100 );\n\t\tvm.Method( p, \u0022setPan\u0022, ( vm, t, a ) =\u003E { if ( St( t ) is { } s ) s.Pan = Arg( a, 0 ).ToNumber( vm ); return Value.Undefined; } );\n\t\tvm.Method( p, \u0022getPan\u0022, ( vm, t, a ) =\u003E St( t )?.Pan ?? 0 );\n\t\tvm.Method( p, \u0022loadSound\u0022, ( vm, t, a ) =\u003E Value.Undefined );\n\t\tvm.Method( p, \u0022getBytesLoaded\u0022, ( vm, t, a ) =\u003E 1 );\n\t\tvm.Method( p, \u0022getBytesTotal\u0022, ( vm, t, a ) =\u003E 1 );\n\t\tp.AddVirtual( \u0022position\u0022, vm.Native( \u0022position\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar s = St( t ); if ( s?.Handle == null ) return 0;\n\t\t\treturn Math.Floor( (vm.Player.Host?.SoundPosition( s.Handle ) ?? 0) * 1000 );\n\t\t} ), null );\n\t\tp.AddVirtual( \u0022duration\u0022, vm.Native( \u0022duration\u0022, ( vm, t, a ) =\u003E St( t )?.Def is { } d ? Math.Floor( d.Seconds * 1000 ) : 0 ), null );\n\t}\n\n\tstatic bool IsGlobal( Player p, SoundState s ) =\u003E s.Target == null || s.Target == p.Root;\n\n\t/// \u003Csummary\u003EFlash: a sound\u0027s volume is its target clip\u0027s volume times the global (no-target) volume.\u003C/summary\u003E\n\tstatic double Effective( Player p, SoundState s )\n\t{\n\t\tdouble t = !IsGlobal( p, s ) \u0026\u0026 targetVolume.TryGetValue( s.Target, out var v ) ? v / 100.0 : 1;\n\t\treturn Math.Max( 0, t * p.GlobalVolume );\n\t}\n\n\tpublic static void RefreshSoundVolumes( Player p )\n\t{\n\t\tforeach ( var (o, st) in liveSounds )\n\t\t\tif ( st.Handle != null ) p.Host?.SetSoundVolume( st.Handle, Effective( p, st ), st.Pan / 100.0 );\n\t}\n\n\t/// \u003Csummary\u003EFire onSoundComplete for finished sounds (call once per frame).\u003C/summary\u003E\n\tpublic static void UpdateSounds( Player p )\n\t{\n\t\tforeach ( var (o, st) in liveSounds.ToArray() )\n\t\t{\n\t\t\tif ( st.Handle == null || st.Completed ) continue;\n\t\t\tif ( p.Host != null \u0026\u0026 !p.Host.IsSoundPlaying( st.Handle ) )\n\t\t\t{\n\t\t\t\tst.Completed = true;\n\t\t\t\tst.Handle = null;\n\t\t\t\tvar f = o.Get( \u0022onSoundComplete\u0022, p.Vm ).AsObj as AsFunction;\n\t\t\t\tif ( f != null ) p.QueueCall( f, Value.From( o ), Array.Empty\u003CValue\u003E() );\n\t\t\t}\n\t\t}\n\t\tliveSounds.RemoveAll( x =\u003E x.st.Completed \u0026\u0026 x.st.Handle == null );\n\t}\n\n\t// ========================================================================= Color\n\n\tstatic void InstallColor( Avm1 vm )\n\t{\n\t\tvar p = vm.NewObject();\n\t\tvm.ColorProto = p;\n\t\tCtor( vm, \u0022Color\u0022, p, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tif ( !t.IsObject ) return Value.Undefined;\n\t\t\tvar target = Arg( a, 0 ).AsObj as DisplayObject;\n\t\t\tif ( target == null \u0026\u0026 Arg( a, 0 ).IsString ) target = vm.ResolveTargetPath( vm.Player.Root, Arg( a, 0 ).Str );\n\t\t\tt.Obj.Native = target;\n\t\t\treturn Value.Undefined;\n\t\t} );\n\t\tDisplayObject T( Value t ) =\u003E t.AsObj?.Native as DisplayObject;\n\t\tvm.Method( p, \u0022setRGB\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar d = T( t ); if ( d == null ) return Value.Undefined;\n\t\t\tint c = Arg( a, 0 ).ToInt32( vm );\n\t\t\td.Cx = new ColorTransform { Rm = 0, Gm = 0, Bm = 0, Am = d.Cx.Am, Ra = (c \u003E\u003E 16) \u0026 255, Ga = (c \u003E\u003E 8) \u0026 255, Ba = c \u0026 255, Aa = d.Cx.Aa };\n\t\t\td.ScriptTransformed = true;\n\t\t\treturn Value.Undefined;\n\t\t} );\n\t\tvm.Method( p, \u0022getRGB\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar d = T( t ); if ( d == null ) return 0;\n\t\t\treturn ((int)d.Cx.Ra \u003C\u003C 16) | ((int)d.Cx.Ga \u003C\u003C 8) | (int)d.Cx.Ba;\n\t\t} );\n\t\tvm.Method( p, \u0022setTransform\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar d = T( t ); var o = Arg( a, 0 ).AsObj; if ( d == null || o == null ) return Value.Undefined;\n\t\t\tvar cx = d.Cx;\n\t\t\tdouble G( string n, double def ) { var v = o.Get( n, vm ); return v.IsUndefined ? def : v.ToNumber( vm ); }\n\t\t\tcx.Rm = G( \u0022ra\u0022, cx.Rm * 100 ) / 100; cx.Gm = G( \u0022ga\u0022, cx.Gm * 100 ) / 100; cx.Bm = G( \u0022ba\u0022, cx.Bm * 100 ) / 100; cx.Am = G( \u0022aa\u0022, cx.Am * 100 ) / 100;\n\t\t\tcx.Ra = G( \u0022rb\u0022, cx.Ra ); cx.Ga = G( \u0022gb\u0022, cx.Ga ); cx.Ba = G( \u0022bb\u0022, cx.Ba ); cx.Aa = G( \u0022ab\u0022, cx.Aa );\n\t\t\td.Cx = cx;\n\t\t\td.ScriptTransformed = true;\n\t\t\treturn Value.Undefined;\n\t\t} );\n\t\tvm.Method( p, \u0022getTransform\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar d = T( t ); var r = vm.NewObject(); if ( d == null ) return r;\n\t\t\tr.Set( \u0022ra\u0022, d.Cx.Rm * 100, vm ); r.Set( \u0022ga\u0022, d.Cx.Gm * 100, vm ); r.Set( \u0022ba\u0022, d.Cx.Bm * 100, vm ); r.Set( \u0022aa\u0022, d.Cx.Am * 100, vm );\n\t\t\tr.Set( \u0022rb\u0022, d.Cx.Ra, vm ); r.Set( \u0022gb\u0022, d.Cx.Ga, vm ); r.Set( \u0022bb\u0022, d.Cx.Ba, vm ); r.Set( \u0022ab\u0022, d.Cx.Aa, vm );\n\t\t\treturn r;\n\t\t} );\n\t}\n\n\t// ========================================================================= Key / Mouse / Stage\n\n\tstatic void InstallKeyMouseStage( Avm1 vm )\n\t{\n\t\tvar key = vm.NewObject();\n\t\tvm.Global.DefineHidden( \u0022Key\u0022, Value.From( key ) );\n\t\tMakeBroadcaster( vm, key );\n\t\tvm.Method( key, \u0022isDown\u0022, ( vm, t, a ) =\u003E vm.Player.KeysDown.Contains( Arg( a, 0 ).ToInt32( vm ) ) );\n\t\tvm.Method( key, \u0022getCode\u0022, ( vm, t, a ) =\u003E vm.Player.LastKey );\n\t\tvm.Method( key, \u0022getAscii\u0022, ( vm, t, a ) =\u003E vm.Player.LastAscii );\n\t\tvm.Method( key, \u0022isToggled\u0022, ( vm, t, a ) =\u003E false );\n\t\tvar codes = new (string, int)[] { (\u0022BACKSPACE\u0022, 8), (\u0022TAB\u0022, 9), (\u0022ENTER\u0022, 13), (\u0022SHIFT\u0022, 16), (\u0022CONTROL\u0022, 17), (\u0022CAPSLOCK\u0022, 20), (\u0022ESCAPE\u0022, 27), (\u0022SPACE\u0022, 32),\n\t\t\t(\u0022PGUP\u0022, 33), (\u0022PGDN\u0022, 34), (\u0022END\u0022, 35), (\u0022HOME\u0022, 36), (\u0022LEFT\u0022, 37), (\u0022UP\u0022, 38), (\u0022RIGHT\u0022, 39), (\u0022DOWN\u0022, 40), (\u0022INSERT\u0022, 45), (\u0022DELETEKEY\u0022, 46) };\n\t\tforeach ( var (n, c) in codes ) key.DefineHidden( n, c );\n\n\t\tvar mouse = vm.NewObject();\n\t\tvm.Global.DefineHidden( \u0022Mouse\u0022, Value.From( mouse ) );\n\t\tMakeBroadcaster( vm, mouse );\n\t\tvm.Method( mouse, \u0022show\u0022, ( vm, t, a ) =\u003E 1 );\n\t\tvm.Method( mouse, \u0022hide\u0022, ( vm, t, a ) =\u003E 1 );\n\n\t\tvar stage = vm.NewObject();\n\t\tvm.Global.DefineHidden( \u0022Stage\u0022, Value.From( stage ) );\n\t\tMakeBroadcaster( vm, stage );\n\t\tstage.AddVirtual( \u0022width\u0022, vm.Native( \u0022width\u0022, ( vm, t, a ) =\u003E vm.Player.Lib.StageWidth ), null );\n\t\tstage.AddVirtual( \u0022height\u0022, vm.Native( \u0022height\u0022, ( vm, t, a ) =\u003E vm.Player.Lib.StageHeight ), null );\n\t\tstage.SetOwn( \u0022scaleMode\u0022, \u0022showAll\u0022 );\n\t\tstage.SetOwn( \u0022align\u0022, \u0022\u0022 );\n\t\tstage.SetOwn( \u0022showMenu\u0022, true );\n\t\tbool fullscreen = false;\n\t\tstage.AddVirtual( \u0022displayState\u0022,\n\t\t\tvm.Native( \u0022displayState\u0022, ( vm, t, a ) =\u003E fullscreen ? \u0022fullScreen\u0022 : \u0022normal\u0022 ),\n\t\t\tvm.Native( \u0022displayState\u0022, ( vm, t, a ) =\u003E { fullscreen = Arg( a, 0 ).ToStr( vm ) == \u0022fullScreen\u0022; vm.Player.Host?.SetFullScreen( fullscreen ); return Value.Undefined; } ) );\n\t}\n\n\t// ========================================================================= SharedObject\n\n\tstatic void InstallSharedObject( Avm1 vm )\n\t{\n\t\tvar p = vm.NewObject();\n\t\tvm.SharedObjectProto = p;\n\t\tvar ctor = Ctor( vm, \u0022SharedObject\u0022, p, ( vm, t, a ) =\u003E Value.Undefined );\n\t\tvar cache = new Dictionary\u003Cstring, AsObject\u003E();\n\t\tvm.Method( ctor, \u0022getLocal\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar name = Arg( a, 0 ).ToStr( vm );\n\t\t\tif ( cache.TryGetValue( name, out var so ) ) return so;\n\t\t\tso = new AsObject( p ) { Native = name };\n\t\t\tAsObject data = null;\n\t\t\tvar saved = vm.Player.Host?.LoadShared( name );\n\t\t\tif ( !string.IsNullOrEmpty( saved ) )\n\t\t\t{\n\t\t\t\ttry { data = FromJson( vm, Json.Parse( saved ) ).AsObj; } catch ( Exception ) { data = null; }\n\t\t\t}\n\t\t\tso.SetOwn( \u0022data\u0022, Value.From( data ?? vm.NewObject() ) );\n\t\t\tcache[name] = so;\n\t\t\treturn so;\n\t\t} );\n\t\tvm.Method( p, \u0022flush\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tif ( t.AsObj?.Native is not string name ) return false;\n\t\t\tvar data = t.Obj.Get( \u0022data\u0022, vm );\n\t\t\tvar sb = new StringBuilder();\n\t\t\tToJson( vm, data, sb, new HashSet\u003CAsObject\u003E() );\n\t\t\tvm.Player.Host?.SaveShared( name, sb.ToString() );\n\t\t\treturn true;\n\t\t} );\n\t\tvm.Method( p, \u0022clear\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tif ( t.AsObj?.Native is not string name ) return Value.Undefined;\n\t\t\tt.Obj.SetOwn( \u0022data\u0022, Value.From( vm.NewObject() ) );\n\t\t\tvm.Player.Host?.SaveShared( name, \u0022\u0022 );\n\t\t\treturn Value.Undefined;\n\t\t} );\n\t\tvm.Method( p, \u0022getSize\u0022, ( vm, t, a ) =\u003E 1024 );\n\t}\n\n\tstatic void ToJson( Avm1 vm, Value v, StringBuilder sb, HashSet\u003CAsObject\u003E seen )\n\t{\n\t\tswitch ( v.Kind )\n\t\t{\n\t\t\tcase ValueKind.Undefined: sb.Append( \u0022{\\\u0022$u\\\u0022:1}\u0022 ); return;\n\t\t\tcase ValueKind.Null: sb.Append( \u0022null\u0022 ); return;\n\t\t\tcase ValueKind.Bool: sb.Append( v.BoolValue ? \u0022true\u0022 : \u0022false\u0022 ); return;\n\t\t\tcase ValueKind.Number:\n\t\t\t\tif ( double.IsNaN( v.Num ) || double.IsInfinity( v.Num ) ) { sb.Append( \u0022{\\\u0022$n\\\u0022:\\\u0022\u0022 ).Append( Value.NumberToString( v.Num ) ).Append( \u0022\\\u0022}\u0022 ); return; }\n\t\t\t\tsb.Append( v.Num.ToString( \u0022R\u0022, CultureInfo.InvariantCulture ) ); return;\n\t\t\tcase ValueKind.String: Str( v.Str, sb ); return;\n\t\t}\n\t\tvar o = v.Obj;\n\t\tif ( o is AsFunction || o is DisplayObject || !seen.Add( o ) ) { sb.Append( \u0022{\\\u0022$u\\\u0022:1}\u0022 ); return; }\n\t\tif ( o is AsArray arr )\n\t\t{\n\t\t\tsb.Append( \u0027[\u0027 );\n\t\t\tfor ( int i = 0; i \u003C arr.Items.Count; i\u002B\u002B ) { if ( i \u003E 0 ) sb.Append( \u0027,\u0027 ); ToJson( vm, arr.Items[i], sb, seen ); }\n\t\t\tsb.Append( \u0027]\u0027 );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tsb.Append( \u0027{\u0027 );\n\t\t\tbool first = true;\n\t\t\tforeach ( var k in o.OwnKeys )\n\t\t\t{\n\t\t\t\tif ( !first ) sb.Append( \u0027,\u0027 );\n\t\t\t\tfirst = false;\n\t\t\t\tStr( k, sb ); sb.Append( \u0027:\u0027 );\n\t\t\t\tToJson( vm, o.Get( k, vm ), sb, seen );\n\t\t\t}\n\t\t\tsb.Append( \u0027}\u0027 );\n\t\t}\n\t\tseen.Remove( o );\n\t}\n\n\tstatic void Str( string s, StringBuilder sb )\n\t{\n\t\tsb.Append( \u0027\u0022\u0027 );\n\t\tforeach ( var c in s )\n\t\t{\n\t\t\tswitch ( c )\n\t\t\t{\n\t\t\t\tcase \u0027\u0022\u0027: sb.Append( \u0022\\\\\\\u0022\u0022 ); break;\n\t\t\t\tcase \u0027\\\\\u0027: sb.Append( \u0022\\\\\\\\\u0022 ); break;\n\t\t\t\tcase \u0027\\n\u0027: sb.Append( \u0022\\\\n\u0022 ); break;\n\t\t\t\tcase \u0027\\r\u0027: sb.Append( \u0022\\\\r\u0022 ); break;\n\t\t\t\tcase \u0027\\t\u0027: sb.Append( \u0022\\\\t\u0022 ); break;\n\t\t\t\tdefault:\n\t\t\t\t\tif ( c \u003C 32 ) sb.Append( \u0022\\\\u\u0022 ).Append( ((int)c).ToString( \u0022x4\u0022 ) );\n\t\t\t\t\telse sb.Append( c );\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tsb.Append( \u0027\u0022\u0027 );\n\t}\n\n\tstatic Value FromJson( Avm1 vm, object j )\n\t{\n\t\tswitch ( j )\n\t\t{\n\t\t\tcase null: return Value.Null;\n\t\t\tcase bool b: return b;\n\t\t\tcase double d: return d;\n\t\t\tcase string s: return s;\n\t\t\tcase List\u003Cobject\u003E l:\n\t\t\t\t{\n\t\t\t\t\tvar arr = vm.NewArray();\n\t\t\t\t\tforeach ( var x in l ) arr.Items.Add( FromJson( vm, x ) );\n\t\t\t\t\treturn arr;\n\t\t\t\t}\n\t\t\tcase Dictionary\u003Cstring, object\u003E dict:\n\t\t\t\t{\n\t\t\t\t\tif ( dict.Count == 1 \u0026\u0026 dict.ContainsKey( \u0022$u\u0022 ) ) return Value.Undefined;\n\t\t\t\t\tif ( dict.Count == 1 \u0026\u0026 dict.TryGetValue( \u0022$n\u0022, out var n ) ) return Value.StringToNumber( (string)n, 7 );\n\t\t\t\t\tvar o = vm.NewObject();\n\t\t\t\t\tforeach ( var kv in dict ) o.Set( kv.Key, FromJson( vm, kv.Value ), vm );\n\t\t\t\t\treturn o;\n\t\t\t\t}\n\t\t}\n\t\treturn Value.Undefined;\n\t}\n\n\t// ========================================================================= AsBroadcaster\n\n\tstatic void InstallBroadcaster( Avm1 vm )\n\t{\n\t\tvar b = vm.NewObject();\n\t\tvm.Global.DefineHidden( \u0022AsBroadcaster\u0022, Value.From( b ) );\n\t\tvm.Method( b, \u0022initialize\u0022, ( vm, t, a ) =\u003E { if ( Arg( a, 0 ).AsObj is AsObject o ) MakeBroadcaster( vm, o ); return Value.Undefined; } );\n\t}\n\n\tstatic void MakeBroadcaster( Avm1 vm, AsObject o )\n\t{\n\t\to.DefineHidden( \u0022_listeners\u0022, Value.From( vm.NewArray() ) );\n\t\tvm.Method( o, \u0022addListener\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar l = vm.GetMember( t, \u0022_listeners\u0022 ).AsObj as AsArray;\n\t\t\tif ( l == null ) return false;\n\t\t\tvar x = Arg( a, 0 );\n\t\t\tl.Items.RemoveAll( v =\u003E vm.StrictEquals( v, x ) );\n\t\t\tl.Items.Add( x );\n\t\t\treturn true;\n\t\t} );\n\t\tvm.Method( o, \u0022removeListener\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar l = vm.GetMember( t, \u0022_listeners\u0022 ).AsObj as AsArray;\n\t\t\tif ( l == null ) return false;\n\t\t\tvar x = Arg( a, 0 );\n\t\t\treturn l.Items.RemoveAll( v =\u003E vm.StrictEquals( v, x ) ) \u003E 0;\n\t\t} );\n\t\tvm.Method( o, \u0022broadcastMessage\u0022, ( vm, t, a ) =\u003E\n\t\t{\n\t\t\tvar l = vm.GetMember( t, \u0022_listeners\u0022 ).AsObj as AsArray;\n\t\t\tif ( l == null || a.Length == 0 ) return Value.Undefined;\n\t\t\tvar name = Arg( a, 0 ).ToStr( vm );\n\t\t\tvar rest = a[1..];\n\t\t\tforeach ( var x in l.Items.ToArray() )\n\t\t\t{\n\t\t\t\tvar f = vm.GetMember( x, name ).AsObj as AsFunction;\n\t\t\t\tif ( f != null ) vm.CallFunction( f, x, rest );\n\t\t\t}\n\t\t\treturn l.Items.Count \u003E 0;\n\t\t} );\n\t}\n}\n"},{"Ident":"farten.shoes_and_daggers","Path":"Game/Fit.cs","FileName":"Fit.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":443203,"IsPrivate":false,"Code":"using System;\n\n/// \u003Csummary\u003EWhere one S\u0026amp;S2 piece sits: centre from its bone\u0027s joint (rig px), size, extra turn, mirrored.\u003C/summary\u003E\npublic readonly record struct FitEntry( float X, float Y, float Scale, float Turn, bool Flip );\n\n/// \u003Csummary\u003E\n/// The fitting table: where every body part, face, hair, beard, helmet, armour piece, weapon and shield\n/// sits on the gladiator. Starts from \u003Csee cref=\u0022FitDefaults\u0022/\u003E; the Fitting Room changes it and saves\n/// only what you changed to fitting.json in the game\u0027s data folder.\n/// \u003C/summary\u003E\npublic static class Fit\n{\n\tpublic const string FileName = \u0022fitting.json\u0022;\n\n\tstatic Dictionary\u003Cstring, FitEntry\u003E values;\n\n\t/// \u003Csummary\u003EGoes up every time something changes, so puppets know to redraw.\u003C/summary\u003E\n\tpublic static int Version { get; private set; }\n\n\tstatic Dictionary\u003Cstring, FitEntry\u003E Values\n\t{\n\t\tget\n\t\t{\n\t\t\tif ( values is null ) Load();\n\t\t\treturn values;\n\t\t}\n\t}\n\n\tpublic static FitEntry Get( string key ) =\u003E Values.TryGetValue( key, out var e ) ? e : new FitEntry( 0, 0, 1, 0, false );\n\n\tpublic static bool Has( string key ) =\u003E Values.ContainsKey( key );\n\n\tpublic static void Set( string key, FitEntry e )\n\t{\n\t\tValues[key] = e;\n\t\tVersion\u002B\u002B;\n\t}\n\n\tpublic static void Reset( string key )\n\t{\n\t\tif ( FitDefaults.Values.TryGetValue( key, out var d ) ) Values[key] = d;\n\t\telse Values.Remove( key );\n\t\tVersion\u002B\u002B;\n\t}\n\n\tpublic static bool Changed( string key ) =\u003E\n\t\tValues.TryGetValue( key, out var v ) \u0026\u0026 (!FitDefaults.Values.TryGetValue( key, out var d ) || v != d);\n\n\tpublic static void Load()\n\t{\n\t\tvalues = new Dictionary\u003Cstring, FitEntry\u003E( FitDefaults.Values );\n\t\ttry\n\t\t{\n\t\t\tif ( FileSystem.Data.FileExists( FileName ) )\n\t\t\t{\n\t\t\t\tvar saved = FileSystem.Data.ReadJson\u003CDictionary\u003Cstring, float[]\u003E\u003E( FileName );\n\t\t\t\tif ( saved is not null )\n\t\t\t\t{\n\t\t\t\t\tforeach ( var (k, v) in saved )\n\t\t\t\t\t{\n\t\t\t\t\t\tif ( v is null || v.Length \u003C 5 ) continue;\n\t\t\t\t\t\tvalues[k] = new FitEntry( v[0], v[1], v[2], v[3], v[4] \u003E 0.5f );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tcatch ( Exception e )\n\t\t{\n\t\t\tLog.Warning( $\u0022Couldn\u0027t read {FileName}: {e.Message}\u0022 );\n\t\t}\n\t\tVersion\u002B\u002B;\n\t}\n\n\t/// \u003Csummary\u003EWrites every entry that differs from the defaults.\u003C/summary\u003E\n\tpublic static void Save()\n\t{\n\t\tvar outp = new Dictionary\u003Cstring, float[]\u003E();\n\t\tforeach ( var (k, v) in Values )\n\t\t{\n\t\t\tif ( !Changed( k ) ) continue;\n\t\t\toutp[k] = new[] { MathF.Round( v.X, 1 ), MathF.Round( v.Y, 1 ), MathF.Round( v.Scale, 3 ), MathF.Round( v.Turn, 1 ), v.Flip ? 1f : 0f };\n\t\t}\n\t\tFileSystem.Data.WriteJson( FileName, outp );\n\t}\n}\n"},{"Ident":"farten.shoes_and_daggers","Path":"Game/GearData.cs","FileName":"GearData.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":443203,"IsPrivate":false,"Code":"// Generated from the S\u0026S2 textures (tools/build.py). Item lists, names, stats and prices.\n// Tiers go cheapest first. Art: resources/sad/ss/item/\u003CSrc\u003E.png; worn art is positioned by the fitting table.\npublic static class GearData\n{\n\tpublic static readonly int HairCount = 37;\n\tpublic static readonly int BeardCount = 22;\n\tpublic static readonly string[] Races = { \u0022Human\u0022, \u0022Elf\u0022, \u0022Orc\u0022, \u0022Devil\u0022, \u0022Undead\u0022 };\n\n\t// (Src, Name, Min, Max, Strength, Price); index 0 is bare fists\n\tpublic static readonly WeaponDef[] Weapons =\n\t{\n\t\tnew( 0, 0, \u0022Bare Fists\u0022, 2, 4, 0, 0 ),\n\t\tnew( 1, 513, \u0022Wooden Club\u0022, 3, 7, 0, 0 ),\n\t\tnew( 2, 79, \u0022Rusty Dagger\u0022, 3, 8, 0, 30 ),\n\t\tnew( 3, 525, \u0022Spiked Cudgel\u0022, 4, 9, 1, 40 ),\n\t\tnew( 4, 509, \u0022Hatchet\u0022, 4, 9, 2, 40 ),\n\t\tnew( 5, 527, \u0022Cleaver\u0022, 4, 10, 2, 40 ),\n\t\tnew( 6, 499, \u0022Short Sword\u0022, 5, 11, 2, 50 ),\n\t\tnew( 7, 497, \u0022Gladius\u0022, 5, 12, 3, 50 ),\n\t\tnew( 8, 515, \u0022Stone Mallet\u0022, 5, 13, 4, 60 ),\n\t\tnew( 9, 511, \u0022Woodsman\u0027s Axe\u0022, 6, 13, 4, 60 ),\n\t\tnew( 10, 577, \u0022Golden Hatchet\u0022, 6, 14, 4, 70 ),\n\t\tnew( 11, 579, \u0022Broadsword\u0022, 6, 15, 5, 80 ),\n\t\tnew( 12, 501, \u0022Fire Axe\u0022, 7, 16, 6, 90 ),\n\t\tnew( 13, 503, \u0022Butcher\u0027s Blade\u0022, 7, 17, 6, 90 ),\n\t\tnew( 14, 289, \u0022War Hammer\u0022, 8, 17, 6, 100 ),\n\t\tnew( 15, 287, \u0022Tree Maul\u0022, 8, 18, 7, 110 ),\n\t\tnew( 16, 521, \u0022Morning Star\u0022, 8, 19, 8, 130 ),\n\t\tnew( 17, 519, \u0022Flanged Mace\u0022, 9, 20, 8, 140 ),\n\t\tnew( 18, 523, \u0022Iron Flail\u0022, 9, 21, 8, 150 ),\n\t\tnew( 19, 291, \u0022Lucerne Hammer\u0022, 9, 21, 9, 170 ),\n\t\tnew( 20, 297, \u0022Sapphire Mace\u0022, 10, 22, 10, 180 ),\n\t\tnew( 21, 295, \u0022Spiked Mace\u0022, 10, 23, 10, 200 ),\n\t\tnew( 22, 489, \u0022Longsword\u0022, 10, 24, 10, 220 ),\n\t\tnew( 23, 495, \u0022Knight\u0027s Sword\u0022, 11, 25, 11, 240 ),\n\t\tnew( 24, 493, \u0022Noble Sword\u0022, 11, 25, 12, 270 ),\n\t\tnew( 25, 507, \u0022Bearded Axe\u0022, 11, 26, 12, 300 ),\n\t\tnew( 26, 505, \u0022Golden Axe\u0022, 12, 27, 12, 330 ),\n\t\tnew( 27, 279, \u0022Bearded Halberd\u0022, 12, 28, 13, 360 ),\n\t\tnew( 28, 573, \u0022Jewelled Sword\u0022, 12, 29, 14, 390 ),\n\t\tnew( 29, 557, \u0022Steel Sword\u0022, 13, 29, 14, 430 ),\n\t\tnew( 30, 561, \u0022Skull Sword\u0022, 13, 30, 14, 480 ),\n\t\tnew( 31, 958, \u0022Emerald Sword\u0022, 14, 31, 15, 520 ),\n\t\tnew( 32, 253, \u0022Black Scimitar\u0022, 14, 32, 16, 580 ),\n\t\tnew( 33, 960, \u0022Twilight Sword\u0022, 14, 33, 16, 630 ),\n\t\tnew( 34, 956, \u0022Blood Scimitar\u0022, 15, 33, 16, 700 ),\n\t\tnew( 35, 255, \u0022Ice Scimitar\u0022, 15, 34, 17, 770 ),\n\t\tnew( 36, 571, \u0022Orc Axe\u0022, 15, 35, 18, 840 ),\n\t\tnew( 37, 517, \u0022Golden Hammer\u0022, 16, 36, 18, 930 ),\n\t\tnew( 38, 551, \u0022Thunder Hammer\u0022, 16, 37, 18, 1020 ),\n\t\tnew( 39, 952, \u0022Blood Hammer\u0022, 16, 37, 19, 1120 ),\n\t\tnew( 40, 954, \u0022Skull Axe\u0022, 17, 38, 20, 1230 ),\n\t\tnew( 41, 275, \u0022Death Axe\u0022, 17, 39, 20, 1360 ),\n\t\tnew( 42, 277, \u0022Frost Axe\u0022, 17, 40, 20, 1490 ),\n\t\tnew( 43, 555, \u0022Sun Axe\u0022, 18, 41, 21, 1640 ),\n\t\tnew( 44, 293, \u0022Star Mace\u0022, 18, 41, 22, 1810 ),\n\t\tnew( 45, 285, \u0022Thorn Club\u0022, 18, 42, 22, 1990 ),\n\t\tnew( 46, 569, \u0022Dark Sword\u0022, 19, 43, 22, 2190 ),\n\t\tnew( 47, 249, \u0022Venom Blade\u0022, 19, 44, 23, 2410 ),\n\t\tnew( 48, 251, \u0022Katana\u0022, 19, 45, 24, 2650 ),\n\t\tnew( 49, 247, \u0022Shadow Blade\u0022, 20, 45, 24, 2910 ),\n\t\tnew( 50, 257, \u0022Rapier\u0022, 20, 46, 24, 3200 ),\n\t\tnew( 51, 259, \u0022Duellist\u0027s Sword\u0022, 20, 47, 25, 3520 ),\n\t\tnew( 52, 962, \u0022Corsair Blade\u0022, 21, 48, 26, 3870 ),\n\t\tnew( 53, 261, \u0022Inferno Sword\u0022, 21, 49, 26, 4260 ),\n\t\tnew( 54, 547, \u0022Ice Sword\u0022, 22, 49, 26, 4690 ),\n\t\tnew( 55, 529, \u0022Royal Sword\u0022, 22, 50, 27, 5160 ),\n\t\tnew( 56, 271, \u0022Spear\u0022, 22, 51, 28, 5670 ),\n\t\tnew( 57, 267, \u0022Glaive\u0022, 23, 52, 28, 6240 ),\n\t\tnew( 58, 269, \u0022Battle Halberd\u0022, 23, 53, 28, 6860 ),\n\t\tnew( 59, 265, \u0022Poleaxe\u0022, 23, 53, 29, 7550 ),\n\t\tnew( 60, 281, \u0022Scythe\u0022, 24, 54, 30, 8300 ),\n\t\tnew( 61, 273, \u0022Crescent Staff\u0022, 24, 55, 30, 9130 ),\n\t\tnew( 62, 533, \u0022Thorn Staff\u0022, 24, 56, 30, 10050 ),\n\t\tnew( 63, 565, \u0022Sun Staff\u0022, 25, 57, 31, 11050 ),\n\t};\n\n\t// per slot: (Src, Name, Level, Points, Price)\n\tpublic static readonly GearDef[][] Gear =\n\t{\n\t\tnew GearDef[] // Helm\n\t\t{\n\t\t\tnew( GearSlot.Helm, 0, 1369, \u0022Ragged Helm\u0022, 1, 4, 50 ),\n\t\t\tnew( GearSlot.Helm, 1, 913, \u0022Ragged Helm II\u0022, 1, 5, 60 ),\n\t\t\tnew( GearSlot.Helm, 2, 782, \u0022Ragged Helm III\u0022, 2, 5, 60 ),\n\t\t\tnew( GearSlot.Helm, 3, 1479, \u0022Leather Helm\u0022, 2, 6, 70 ),\n\t\t\tnew( GearSlot.Helm, 4, 917, \u0022Leather Helm II\u0022, 3, 7, 80 ),\n\t\t\tnew( GearSlot.Helm, 5, 931, \u0022Studded Helm\u0022, 4, 7, 100 ),\n\t\t\tnew( GearSlot.Helm, 6, 788, \u0022Studded Helm II\u0022, 4, 8, 110 ),\n\t\t\tnew( GearSlot.Helm, 7, 915, \u0022Bronze Helm\u0022, 5, 9, 130 ),\n\t\t\tnew( GearSlot.Helm, 8, 323, \u0022Bronze Helm II\u0022, 6, 9, 150 ),\n\t\t\tnew( GearSlot.Helm, 9, 1493, \u0022Bronze Helm III\u0022, 6, 10, 170 ),\n\t\t\tnew( GearSlot.Helm, 10, 921, \u0022Iron Helm\u0022, 7, 10, 190 ),\n\t\t\tnew( GearSlot.Helm, 11, 1371, \u0022Iron Helm II\u0022, 8, 11, 220 ),\n\t\t\tnew( GearSlot.Helm, 12, 1485, \u0022Chain Helm\u0022, 8, 12, 250 ),\n\t\t\tnew( GearSlot.Helm, 13, 1481, \u0022Chain Helm II\u0022, 9, 12, 290 ),\n\t\t\tnew( GearSlot.Helm, 14, 485, \u0022Steel Helm\u0022, 10, 13, 330 ),\n\t\t\tnew( GearSlot.Helm, 15, 1483, \u0022Steel Helm II\u0022, 10, 14, 380 ),\n\t\t\tnew( GearSlot.Helm, 16, 1495, \u0022Steel Helm III\u0022, 11, 14, 440 ),\n\t\t\tnew( GearSlot.Helm, 17, 313, \u0022Legion Helm\u0022, 12, 15, 510 ),\n\t\t\tnew( GearSlot.Helm, 18, 923, \u0022Legion Helm II\u0022, 12, 16, 580 ),\n\t\t\tnew( GearSlot.Helm, 19, 919, \u0022Knight\u0027s Helm\u0022, 13, 16, 670 ),\n\t\t\tnew( GearSlot.Helm, 20, 911, \u0022Knight\u0027s Helm II\u0022, 13, 17, 770 ),\n\t\t\tnew( GearSlot.Helm, 21, 315, \u0022Gilded Helm\u0022, 14, 18, 880 ),\n\t\t\tnew( GearSlot.Helm, 22, 317, \u0022Gilded Helm II\u0022, 15, 18, 1020 ),\n\t\t\tnew( GearSlot.Helm, 23, 925, \u0022Gilded Helm III\u0022, 15, 19, 1170 ),\n\t\t\tnew( GearSlot.Helm, 24, 319, \u0022Champion\u0027s Helm\u0022, 16, 20, 1340 ),\n\t\t\tnew( GearSlot.Helm, 25, 1487, \u0022Champion\u0027s Helm II\u0022, 17, 20, 1540 ),\n\t\t\tnew( GearSlot.Helm, 26, 327, \u0022Dragon Helm\u0022, 17, 21, 1770 ),\n\t\t\tnew( GearSlot.Helm, 27, 553, \u0022Dragon Helm II\u0022, 18, 22, 2030 ),\n\t\t\tnew( GearSlot.Helm, 28, 321, \u0022Demon Helm\u0022, 19, 22, 2340 ),\n\t\t\tnew( GearSlot.Helm, 29, 567, \u0022Demon Helm II\u0022, 19, 23, 2690 ),\n\t\t\tnew( GearSlot.Helm, 30, 325, \u0022Demon Helm III\u0022, 20, 23, 3090 ),\n\t\t\tnew( GearSlot.Helm, 31, 545, \u0022Titan Helm\u0022, 21, 24, 3550 ),\n\t\t\tnew( GearSlot.Helm, 32, 929, \u0022Titan Helm II\u0022, 21, 25, 4070 ),\n\t\t\tnew( GearSlot.Helm, 33, 927, \u0022Mythic Helm\u0022, 22, 25, 4680 ),\n\t\t\tnew( GearSlot.Helm, 34, 1491, \u0022Mythic Helm II\u0022, 23, 26, 5380 ),\n\t\t\tnew( GearSlot.Helm, 35, 537, \u0022Godly Helm\u0022, 23, 27, 6180 ),\n\t\t\tnew( GearSlot.Helm, 36, 1489, \u0022Godly Helm II\u0022, 24, 27, 7100 ),\n\t\t\tnew( GearSlot.Helm, 37, 535, \u0022Godly Helm III\u0022, 25, 28, 8150 ),\n\t\t},\n\t\tnew GearDef[] // Chest\n\t\t{\n\t\t\tnew( GearSlot.Chest, 0, 1437, \u0022Ragged Armour\u0022, 1, 8, 100 ),\n\t\t\tnew( GearSlot.Chest, 1, 1335, \u0022Ragged Armour II\u0022, 2, 10, 120 ),\n\t\t\tnew( GearSlot.Chest, 2, 867, \u0022Leather Armour\u0022, 3, 12, 150 ),\n\t\t\tnew( GearSlot.Chest, 3, 1337, \u0022Studded Armour\u0022, 4, 14, 190 ),\n\t\t\tnew( GearSlot.Chest, 4, 1333, \u0022Studded Armour II\u0022, 5, 16, 230 ),\n\t\t\tnew( GearSlot.Chest, 5, 873, \u0022Bronze Armour\u0022, 6, 18, 290 ),\n\t\t\tnew( GearSlot.Chest, 6, 471, \u0022Iron Armour\u0022, 7, 21, 370 ),\n\t\t\tnew( GearSlot.Chest, 7, 871, \u0022Iron Armour II\u0022, 8, 23, 460 ),\n\t\t\tnew( GearSlot.Chest, 8, 465, \u0022Chain Armour\u0022, 9, 25, 570 ),\n\t\t\tnew( GearSlot.Chest, 9, 475, \u0022Steel Armour\u0022, 10, 27, 720 ),\n\t\t\tnew( GearSlot.Chest, 10, 479, \u0022Steel Armour II\u0022, 11, 29, 900 ),\n\t\t\tnew( GearSlot.Chest, 11, 865, \u0022Legion Armour\u0022, 12, 31, 1120 ),\n\t\t\tnew( GearSlot.Chest, 12, 463, \u0022Knight\u0027s Armour\u0022, 13, 33, 1400 ),\n\t\t\tnew( GearSlot.Chest, 13, 1339, \u0022Gilded Armour\u0022, 14, 35, 1750 ),\n\t\t\tnew( GearSlot.Chest, 14, 477, \u0022Gilded Armour II\u0022, 15, 37, 2190 ),\n\t\t\tnew( GearSlot.Chest, 15, 483, \u0022Champion\u0027s Armour\u0022, 16, 39, 2730 ),\n\t\t\tnew( GearSlot.Chest, 16, 459, \u0022Dragon Armour\u0022, 17, 41, 3420 ),\n\t\t\tnew( GearSlot.Chest, 17, 481, \u0022Dragon Armour II\u0022, 18, 43, 4270 ),\n\t\t\tnew( GearSlot.Chest, 18, 467, \u0022Demon Armour\u0022, 19, 46, 5340 ),\n\t\t\tnew( GearSlot.Chest, 19, 457, \u0022Titan Armour\u0022, 20, 48, 6680 ),\n\t\t\tnew( GearSlot.Chest, 20, 869, \u0022Titan Armour II\u0022, 21, 50, 8350 ),\n\t\t\tnew( GearSlot.Chest, 21, 473, \u0022Mythic Armour\u0022, 22, 52, 10430 ),\n\t\t\tnew( GearSlot.Chest, 22, 469, \u0022Godly Armour\u0022, 23, 54, 13040 ),\n\t\t\tnew( GearSlot.Chest, 23, 461, \u0022Godly Armour II\u0022, 25, 56, 16300 ),\n\t\t},\n\t\tnew GearDef[] // Shoulders\n\t\t{\n\t\t\tnew( GearSlot.Shoulders, 0, 345, \u0022Ragged Pauldrons\u0022, 1, 3, 40 ),\n\t\t\tnew( GearSlot.Shoulders, 1, 341, \u0022Ragged Pauldrons II\u0022, 1, 4, 40 ),\n\t\t\tnew( GearSlot.Shoulders, 2, 381, \u0022Leather Pauldrons\u0022, 2, 4, 50 ),\n\t\t\tnew( GearSlot.Shoulders, 3, 343, \u0022Leather Pauldrons II\u0022, 3, 5, 70 ),\n\t\t\tnew( GearSlot.Shoulders, 4, 383, \u0022Studded Pauldrons\u0022, 4, 6, 80 ),\n\t\t\tnew( GearSlot.Shoulders, 5, 389, \u0022Bronze Pauldrons\u0022, 5, 6, 100 ),\n\t\t\tnew( GearSlot.Shoulders, 6, 339, \u0022Bronze Pauldrons II\u0022, 6, 7, 120 ),\n\t\t\tnew( GearSlot.Shoulders, 7, 349, \u0022Iron Pauldrons\u0022, 7, 8, 140 ),\n\t\t\tnew( GearSlot.Shoulders, 8, 391, \u0022Iron Pauldrons II\u0022, 8, 9, 170 ),\n\t\t\tnew( GearSlot.Shoulders, 9, 387, \u0022Chain Pauldrons\u0022, 9, 9, 210 ),\n\t\t\tnew( GearSlot.Shoulders, 10, 379, \u0022Steel Pauldrons\u0022, 10, 10, 260 ),\n\t\t\tnew( GearSlot.Shoulders, 11, 385, \u0022Steel Pauldrons II\u0022, 11, 11, 320 ),\n\t\t\tnew( GearSlot.Shoulders, 12, 353, \u0022Legion Pauldrons\u0022, 12, 11, 390 ),\n\t\t\tnew( GearSlot.Shoulders, 13, 347, \u0022Knight\u0027s Pauldrons\u0022, 13, 12, 470 ),\n\t\t\tnew( GearSlot.Shoulders, 14, 367, \u0022Knight\u0027s Pauldrons II\u0022, 13, 13, 570 ),\n\t\t\tnew( GearSlot.Shoulders, 15, 351, \u0022Gilded Pauldrons\u0022, 14, 13, 700 ),\n\t\t\tnew( GearSlot.Shoulders, 16, 375, \u0022Gilded Pauldrons II\u0022, 15, 14, 850 ),\n\t\t\tnew( GearSlot.Shoulders, 17, 377, \u0022Champion\u0027s Pauldrons\u0022, 16, 15, 1030 ),\n\t\t\tnew( GearSlot.Shoulders, 18, 359, \u0022Dragon Pauldrons\u0022, 17, 15, 1260 ),\n\t\t\tnew( GearSlot.Shoulders, 19, 355, \u0022Dragon Pauldrons II\u0022, 18, 16, 1530 ),\n\t\t\tnew( GearSlot.Shoulders, 20, 371, \u0022Demon Pauldrons\u0022, 19, 17, 1870 ),\n\t\t\tnew( GearSlot.Shoulders, 21, 369, \u0022Demon Pauldrons II\u0022, 20, 18, 2280 ),\n\t\t\tnew( GearSlot.Shoulders, 22, 365, \u0022Titan Pauldrons\u0022, 21, 18, 2770 ),\n\t\t\tnew( GearSlot.Shoulders, 23, 373, \u0022Mythic Pauldrons\u0022, 22, 19, 3380 ),\n\t\t\tnew( GearSlot.Shoulders, 24, 363, \u0022Mythic Pauldrons II\u0022, 23, 20, 4120 ),\n\t\t\tnew( GearSlot.Shoulders, 25, 357, \u0022Godly Pauldrons\u0022, 24, 20, 5020 ),\n\t\t\tnew( GearSlot.Shoulders, 26, 361, \u0022Godly Pauldrons II\u0022, 25, 21, 6110 ),\n\t\t},\n\t\tnew GearDef[] // Gauntlets\n\t\t{\n\t\t\tnew( GearSlot.Gauntlets, 0, 419, \u0022Ragged Gauntlets\u0022, 1, 3, 40 ),\n\t\t\tnew( GearSlot.Gauntlets, 1, 417, \u0022Ragged Gauntlets II\u0022, 2, 4, 50 ),\n\t\t\tnew( GearSlot.Gauntlets, 2, 329, \u0022Leather Gauntlets\u0022, 3, 5, 60 ),\n\t\t\tnew( GearSlot.Gauntlets, 3, 397, \u0022Studded Gauntlets\u0022, 4, 5, 70 ),\n\t\t\tnew( GearSlot.Gauntlets, 4, 337, \u0022Studded Gauntlets II\u0022, 5, 6, 90 ),\n\t\t\tnew( GearSlot.Gauntlets, 5, 415, \u0022Bronze Gauntlets\u0022, 6, 7, 110 ),\n\t\t\tnew( GearSlot.Gauntlets, 6, 423, \u0022Iron Gauntlets\u0022, 7, 8, 140 ),\n\t\t\tnew( GearSlot.Gauntlets, 7, 407, \u0022Iron Gauntlets II\u0022, 8, 8, 170 ),\n\t\t\tnew( GearSlot.Gauntlets, 8, 421, \u0022Chain Gauntlets\u0022, 9, 9, 210 ),\n\t\t\tnew( GearSlot.Gauntlets, 9, 335, \u0022Steel Gauntlets\u0022, 10, 10, 270 ),\n\t\t\tnew( GearSlot.Gauntlets, 10, 429, \u0022Steel Gauntlets II\u0022, 11, 11, 340 ),\n\t\t\tnew( GearSlot.Gauntlets, 11, 409, \u0022Legion Gauntlets\u0022, 12, 12, 420 ),\n\t\t\tnew( GearSlot.Gauntlets, 12, 413, \u0022Knight\u0027s Gauntlets\u0022, 13, 12, 520 ),\n\t\t\tnew( GearSlot.Gauntlets, 13, 399, \u0022Gilded Gauntlets\u0022, 14, 13, 660 ),\n\t\t\tnew( GearSlot.Gauntlets, 14, 411, \u0022Gilded Gauntlets II\u0022, 15, 14, 820 ),\n\t\t\tnew( GearSlot.Gauntlets, 15, 395, \u0022Champion\u0027s Gauntlets\u0022, 16, 15, 1020 ),\n\t\t\tnew( GearSlot.Gauntlets, 16, 405, \u0022Dragon Gauntlets\u0022, 17, 16, 1280 ),\n\t\t\tnew( GearSlot.Gauntlets, 17, 393, \u0022Dragon Gauntlets II\u0022, 18, 16, 1600 ),\n\t\t\tnew( GearSlot.Gauntlets, 18, 333, \u0022Demon Gauntlets\u0022, 19, 17, 2000 ),\n\t\t\tnew( GearSlot.Gauntlets, 19, 401, \u0022Titan Gauntlets\u0022, 20, 18, 2500 ),\n\t\t\tnew( GearSlot.Gauntlets, 20, 403, \u0022Titan Gauntlets II\u0022, 21, 19, 3130 ),\n\t\t\tnew( GearSlot.Gauntlets, 21, 331, \u0022Mythic Gauntlets\u0022, 22, 19, 3910 ),\n\t\t\tnew( GearSlot.Gauntlets, 22, 425, \u0022Godly Gauntlets\u0022, 23, 20, 4890 ),\n\t\t\tnew( GearSlot.Gauntlets, 23, 427, \u0022Godly Gauntlets II\u0022, 25, 21, 6110 ),\n\t\t},\n\t\tnew GearDef[] // Thighs\n\t\t{\n\t\t\tnew( GearSlot.Thighs, 0, 1415, \u0022Ragged Cuisses\u0022, 1, 3, 40 ),\n\t\t\tnew( GearSlot.Thighs, 1, 1373, \u0022Leather Cuisses\u0022, 3, 5, 60 ),\n\t\t\tnew( GearSlot.Thighs, 2, 1417, \u0022Bronze Cuisses\u0022, 6, 7, 110 ),\n\t\t\tnew( GearSlot.Thighs, 3, 1375, \u0022Chain Cuisses\u0022, 9, 9, 200 ),\n\t\t\tnew( GearSlot.Thighs, 4, 1425, \u0022Legion Cuisses\u0022, 11, 11, 350 ),\n\t\t\tnew( GearSlot.Thighs, 5, 1423, \u0022Knight\u0027s Cuisses\u0022, 14, 13, 620 ),\n\t\t\tnew( GearSlot.Thighs, 6, 1399, \u0022Champion\u0027s Cuisses\u0022, 17, 15, 1100 ),\n\t\t\tnew( GearSlot.Thighs, 7, 1379, \u0022Demon Cuisses\u0022, 19, 17, 1950 ),\n\t\t\tnew( GearSlot.Thighs, 8, 1427, \u0022Mythic Cuisses\u0022, 22, 19, 3460 ),\n\t\t\tnew( GearSlot.Thighs, 9, 1421, \u0022Godly Cuisses\u0022, 25, 21, 6110 ),\n\t\t},\n\t\tnew GearDef[] // Greaves\n\t\t{\n\t\t\tnew( GearSlot.Greaves, 0, 1387, \u0022Ragged Greaves\u0022, 1, 3, 40 ),\n\t\t\tnew( GearSlot.Greaves, 1, 1359, \u0022Leather Greaves\u0022, 2, 4, 50 ),\n\t\t\tnew( GearSlot.Greaves, 2, 1389, \u0022Studded Greaves\u0022, 4, 5, 70 ),\n\t\t\tnew( GearSlot.Greaves, 3, 1393, \u0022Bronze Greaves\u0022, 5, 6, 90 ),\n\t\t\tnew( GearSlot.Greaves, 4, 1381, \u0022Iron Greaves\u0022, 7, 8, 130 ),\n\t\t\tnew( GearSlot.Greaves, 5, 1363, \u0022Chain Greaves\u0022, 8, 9, 180 ),\n\t\t\tnew( GearSlot.Greaves, 6, 1383, \u0022Steel Greaves\u0022, 10, 10, 250 ),\n\t\t\tnew( GearSlot.Greaves, 7, 1361, \u0022Legion Greaves\u0022, 11, 11, 340 ),\n\t\t\tnew( GearSlot.Greaves, 8, 1385, \u0022Knight\u0027s Greaves\u0022, 13, 12, 470 ),\n\t\t\tnew( GearSlot.Greaves, 9, 1391, \u0022Gilded Greaves\u0022, 14, 13, 650 ),\n\t\t\tnew( GearSlot.Greaves, 10, 1401, \u0022Champion\u0027s Greaves\u0022, 16, 14, 890 ),\n\t\t\tnew( GearSlot.Greaves, 11, 1365, \u0022Dragon Greaves\u0022, 17, 15, 1230 ),\n\t\t\tnew( GearSlot.Greaves, 12, 1407, \u0022Demon Greaves\u0022, 19, 16, 1690 ),\n\t\t\tnew( GearSlot.Greaves, 13, 1403, \u0022Titan Greaves\u0022, 20, 18, 2330 ),\n\t\t\tnew( GearSlot.Greaves, 14, 1409, \u0022Mythic Greaves\u0022, 22, 19, 3220 ),\n\t\t\tnew( GearSlot.Greaves, 15, 1405, \u0022Godly Greaves\u0022, 23, 20, 4440 ),\n\t\t\tnew( GearSlot.Greaves, 16, 1411, \u0022Godly Greaves II\u0022, 25, 21, 6110 ),\n\t\t},\n\t\tnew GearDef[] // Boots\n\t\t{\n\t\t\tnew( GearSlot.Boots, 0, 449, \u0022Ragged Boots\u0022, 1, 2, 20 ),\n\t\t\tnew( GearSlot.Boots, 1, 1341, \u0022Ragged Boots II\u0022, 2, 3, 30 ),\n\t\t\tnew( GearSlot.Boots, 2, 443, \u0022Leather Boots\u0022, 3, 3, 40 ),\n\t\t\tnew( GearSlot.Boots, 3, 447, \u0022Studded Boots\u0022, 5, 4, 60 ),\n\t\t\tnew( GearSlot.Boots, 4, 445, \u0022Bronze Boots\u0022, 6, 5, 80 ),\n\t\t\tnew( GearSlot.Boots, 5, 1343, \u0022Iron Boots\u0022, 7, 5, 100 ),\n\t\t\tnew( GearSlot.Boots, 6, 455, \u0022Chain Boots\u0022, 9, 6, 130 ),\n\t\t\tnew( GearSlot.Boots, 7, 1345, \u0022Steel Boots\u0022, 10, 7, 180 ),\n\t\t\tnew( GearSlot.Boots, 8, 453, \u0022Legion Boots\u0022, 11, 7, 240 ),\n\t\t\tnew( GearSlot.Boots, 9, 435, \u0022Knight\u0027s Boots\u0022, 13, 8, 310 ),\n\t\t\tnew( GearSlot.Boots, 10, 431, \u0022Knight\u0027s Boots II\u0022, 14, 9, 420 ),\n\t\t\tnew( GearSlot.Boots, 11, 437, \u0022Gilded Boots\u0022, 15, 9, 550 ),\n\t\t\tnew( GearSlot.Boots, 12, 441, \u0022Champion\u0027s Boots\u0022, 17, 10, 740 ),\n\t\t\tnew( GearSlot.Boots, 13, 1349, \u0022Dragon Boots\u0022, 18, 11, 980 ),\n\t\t\tnew( GearSlot.Boots, 14, 1347, \u0022Demon Boots\u0022, 19, 11, 1300 ),\n\t\t\tnew( GearSlot.Boots, 15, 1351, \u0022Titan Boots\u0022, 21, 12, 1730 ),\n\t\t\tnew( GearSlot.Boots, 16, 439, \u0022Mythic Boots\u0022, 22, 13, 2300 ),\n\t\t\tnew( GearSlot.Boots, 17, 451, \u0022Godly Boots\u0022, 23, 13, 3060 ),\n\t\t\tnew( GearSlot.Boots, 18, 433, \u0022Godly Boots II\u0022, 25, 14, 4080 ),\n\t\t},\n\t\tnew GearDef[] // Shield\n\t\t{\n\t\t\tnew( GearSlot.Shield, 0, 934, \u0022Ragged Shield\u0022, 1, 6, 70 ),\n\t\t\tnew( GearSlot.Shield, 1, 311, \u0022Ragged Shield II\u0022, 2, 8, 90 ),\n\t\t\tnew( GearSlot.Shield, 2, 305, \u0022Leather Shield\u0022, 3, 9, 110 ),\n\t\t\tnew( GearSlot.Shield, 3, 940, \u0022Studded Shield\u0022, 4, 11, 150 ),\n\t\t\tnew( GearSlot.Shield, 4, 780, \u0022Studded Shield II\u0022, 5, 13, 180 ),\n\t\t\tnew( GearSlot.Shield, 5, 790, \u0022Bronze Shield\u0022, 6, 14, 230 ),\n\t\t\tnew( GearSlot.Shield, 6, 784, \u0022Iron Shield\u0022, 7, 16, 290 ),\n\t\t\tnew( GearSlot.Shield, 7, 307, \u0022Chain Shield\u0022, 8, 17, 370 ),\n\t\t\tnew( GearSlot.Shield, 8, 786, \u0022Chain Shield II\u0022, 9, 19, 470 ),\n\t\t\tnew( GearSlot.Shield, 9, 301, \u0022Steel Shield\u0022, 10, 21, 590 ),\n\t\t\tnew( GearSlot.Shield, 10, 531, \u0022Legion Shield\u0022, 11, 22, 740 ),\n\t\t\tnew( GearSlot.Shield, 11, 559, \u0022Knight\u0027s Shield\u0022, 13, 24, 940 ),\n\t\t\tnew( GearSlot.Shield, 12, 563, \u0022Knight\u0027s Shield II\u0022, 14, 26, 1180 ),\n\t\t\tnew( GearSlot.Shield, 13, 543, \u0022Gilded Shield\u0022, 15, 27, 1500 ),\n\t\t\tnew( GearSlot.Shield, 14, 942, \u0022Champion\u0027s Shield\u0022, 16, 29, 1890 ),\n\t\t\tnew( GearSlot.Shield, 15, 946, \u0022Champion\u0027s Shield II\u0022, 17, 31, 2390 ),\n\t\t\tnew( GearSlot.Shield, 16, 944, \u0022Dragon Shield\u0022, 18, 32, 3010 ),\n\t\t\tnew( GearSlot.Shield, 17, 309, \u0022Demon Shield\u0022, 19, 34, 3810 ),\n\t\t\tnew( GearSlot.Shield, 18, 299, \u0022Titan Shield\u0022, 20, 35, 4810 ),\n\t\t\tnew( GearSlot.Shield, 19, 549, \u0022Titan Shield II\u0022, 21, 37, 6070 ),\n\t\t\tnew( GearSlot.Shield, 20, 938, \u0022Mythic Shield\u0022, 22, 39, 7670 ),\n\t\t\tnew( GearSlot.Shield, 21, 303, \u0022Godly Shield\u0022, 23, 40, 9680 ),\n\t\t\tnew( GearSlot.Shield, 22, 936, \u0022Godly Shield II\u0022, 25, 42, 12230 ),\n\t\t},\n\t};\n}\n"},{"Ident":"farten.shoes_and_daggers","Path":"UI/LookEditor.razor","FileName":"LookEditor.razor","PackageType":"game","CodeKind":"Game","AssetVersionId":443203,"IsPrivate":false,"Code":"@using Sandbox.UI;\n\n@* No longer used (the creation screen has its own look rows). Safe to delete. *@\n\u003Croot\u003E\u003C/root\u003E\n"},{"Ident":"farten.shoes_and_daggers","Path":"World/SpritePuppet.cs","FileName":"SpritePuppet.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":443203,"IsPrivate":false,"Code":"using System;\n\n/// \u003Csummary\u003E\n/// A gladiator standing in the 3D world: every S\u0026amp;S2 picture on every bone is a flat sprite, lit by the\n/// scene\u0027s lights and casting a shadow. Draws a fight\u0027s \u003Csee cref=\u0022Gladiator\u0022/\u003E, or any look you give it.\n/// \u003Csee cref=\u0022World3D\u0022/\u003E tells it where its feet are and which way the camera is.\n/// \u003C/summary\u003E\npublic sealed class SpritePuppet : Component\n{\n\tpublic Gladiator Fighter { get; set; }\n\n\t/// \u003Csummary\u003EUsed when there\u0027s no fighter (town, shops, character creation, fitting room...).\u003C/summary\u003E\n\tpublic Func\u003CLook\u003E LookSource { get; set; }\n\tpublic string Anim { get; set; } = \u0022idle\u0022;\n\tpublic Mood FixedMood { get; set; } = Mood.Neutral;\n\n\t/// \u003Csummary\u003EMirror left/right (the fighter on the right faces left).\u003C/summary\u003E\n\tpublic bool Flip { get; set; }\n\n\t/// \u003Csummary\u003EWhere the soles are, which way is screen-right and which way is towards the camera.\u003C/summary\u003E\n\tpublic Vector3 Feet { get; set; }\n\tpublic Vector3 ScreenRight { get; set; } = Vector3.Right;\n\tpublic Vector3 ToCamera { get; set; } = Vector3.Forward;\n\tpublic float Size { get; set; } = 1f;\n\n\tconst float BoneDepth = 0.35f;  // world units between bones (towards the camera)\n\tconst float LayerDepth = 0.05f; // between the pictures stacked on one bone\n\n\tclass BoneSprites\n\t{\n\t\tpublic PuppetRig.Bone Bone;\n\t\tpublic int Order;\n\t\tpublic readonly List\u003CSpriteRenderer\u003E Sprites = new();\n\t\tpublic readonly List\u003Cstring\u003E Paths = new();\n\t}\n\n\treadonly Dictionary\u003Cstring, BoneSprites\u003E bones = new();\n\treadonly List\u003CPuppetLayer\u003E scratch = new();\n\tDictionary\u003Cstring, (Vector2 Pos, float Angle)\u003E solved;\n\tRealTimeSince clock = 0;\n\n\tstatic readonly Dictionary\u003Cstring, Texture\u003E textures = new();\n\tstatic readonly Dictionary\u003Cstring, Sprite\u003E sprites = new();\n\n\tpublic static Texture Tex( string path )\n\t{\n\t\tif ( textures.TryGetValue( path, out var t ) ) return t;\n\t\tt = Texture.Load( Catalog.Art \u002B path, false );\n\t\ttextures[path] = t;\n\t\treturn t;\n\t}\n\n\tpublic static Sprite SpriteFor( string path )\n\t{\n\t\tif ( sprites.TryGetValue( path, out var s ) ) return s;\n\t\tvar t = Tex( path );\n\t\ts = t is null ? null : Sprite.FromTexture( t );\n\t\tsprites[path] = s;\n\t\treturn s;\n\t}\n\n\t/// \u003Csummary\u003ETurns a flat sprite to face the camera (and round, if the art shows up mirrored).\u003C/summary\u003E\n\tpublic static Rotation Facing( Vector3 toCamera )\n\t{\n\t\tvar r = Rotation.LookAt( toCamera, Vector3.Up );\n\t\treturn World3D.TurnSprites ? r * Rotation.FromYaw( 180 ) : r;\n\t}\n\n\tprotected override void OnStart()\n\t{\n\t\tint order = 0;\n\t\tvar byName = PuppetRig.Bones.ToDictionary( b =\u003E b.Name );\n\t\tforeach ( var name in PuppetRig.DrawOrder )\n\t\t\tbones[name] = new BoneSprites { Bone = byName[name], Order = order\u002B\u002B };\n\t}\n\n\t/// \u003Csummary\u003EWhere a bone\u0027s joint is in the world right now (for chains, popups...).\u003C/summary\u003E\n\tpublic Vector3 BoneWorld( string bone )\n\t{\n\t\tif ( solved is null || !solved.TryGetValue( bone, out var b ) ) return Feet;\n\t\tfloat x = Flip ? -b.Pos.x : b.Pos.x;\n\t\tfloat u = World3D.U * Size;\n\t\treturn Feet \u002B ScreenRight * (x * u) \u002B Vector3.Up * (-b.Pos.y * u);\n\t}\n\n\tvoid HideAll()\n\t{\n\t\tforeach ( var b in bones.Values )\n\t\t\tforeach ( var s in b.Sprites ) s.Enabled = false;\n\t}\n\n\tprotected override void OnUpdate()\n\t{\n\t\tLook look;\n\t\tMood mood;\n\t\tstring anim;\n\t\tfloat time;\n\n\t\tif ( Fighter.IsValid() \u0026\u0026 !float.IsNaN( Fighter.DisplayX ) )\n\t\t{\n\t\t\tlook = Fighter.Look;\n\t\t\tmood = Fighter.CurrentMood;\n\t\t\t(anim, time) = Fighter.CurrentAnim;\n\t\t}\n\t\telse if ( !Fighter.IsValid() \u0026\u0026 LookSource is not null )\n\t\t{\n\t\t\tlook = LookSource();\n\t\t\tmood = FixedMood;\n\t\t\tanim = Anim;\n\t\t\ttime = clock;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tHideAll();\n\t\t\treturn;\n\t\t}\n\n\t\tsolved = PuppetRig.Solve( PuppetRig.Sample( anim, time ) );\n\t\tfloat u = World3D.U * Size;\n\t\tvar face = Facing( ToCamera );\n\n\t\tforeach ( var bs in bones.Values )\n\t\t{\n\t\t\tvar (pivot, ang) = solved[bs.Bone.Name];\n\t\t\tPuppetRig.Layers( bs.Bone, look, mood, scratch );\n\t\t\twhile ( bs.Sprites.Count \u003C scratch.Count ) AddSprite( bs );\n\n\t\t\tfor ( int i = 0; i \u003C bs.Sprites.Count; i\u002B\u002B )\n\t\t\t{\n\t\t\t\tvar sr = bs.Sprites[i];\n\t\t\t\tif ( i \u003E= scratch.Count ) { sr.Enabled = false; continue; }\n\n\t\t\t\tvar layer = scratch[i];\n\t\t\t\tvar tex = Tex( layer.Path );\n\t\t\t\tif ( tex is null || tex.Width \u003C= 0 ) { sr.Enabled = false; continue; }\n\t\t\t\tsr.Enabled = true;\n\n\t\t\t\tif ( bs.Paths[i] != layer.Path \u002B layer.Tint )\n\t\t\t\t{\n\t\t\t\t\tbs.Paths[i] = layer.Path \u002B layer.Tint;\n\t\t\t\t\tsr.Sprite = SpriteFor( layer.Path );\n\t\t\t\t\tsr.Color = layer.Tint is null ? Color.White : Color.Parse( layer.Tint ) ?? Color.White;\n\t\t\t\t}\n\n\t\t\t\t// centre of the picture, turned with its bone (clockwise, y down)\n\t\t\t\tfloat r = ang * (MathF.PI / 180f);\n\t\t\t\tfloat c = MathF.Cos( r ), s = MathF.Sin( r );\n\t\t\t\tfloat x = pivot.x \u002B layer.X * c - layer.Y * s;\n\t\t\t\tfloat y = pivot.y \u002B layer.X * s \u002B layer.Y * c;\n\t\t\t\tfloat turn = ang \u002B layer.Turn;\n\t\t\t\tbool mirror = layer.Mirror;\n\t\t\t\tif ( Flip ) { x = -x; turn = -turn; mirror = !mirror; }\n\n\t\t\t\tsr.Size = new Vector2( tex.Width * layer.Scale * u, tex.Height * layer.Scale * u );\n\t\t\t\tsr.FlipHorizontal = mirror;\n\t\t\t\tsr.WorldPosition = Feet \u002B ScreenRight * (x * u) \u002B Vector3.Up * (-y * u) \u002B ToCamera * (bs.Order * BoneDepth \u002B i * LayerDepth);\n\t\t\t\t// clockwise on screen = a negative turn about the axis pointing at the camera\n\t\t\t\tsr.WorldRotation = face * Rotation.FromAxis( Vector3.Forward, World3D.TurnSprites ? turn : -turn );\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid AddSprite( BoneSprites bs )\n\t{\n\t\tvar go = new GameObject( GameObject, true, $\u0022{bs.Bone.Name} {bs.Sprites.Count}\u0022 );\n\t\tgo.Flags |= GameObjectFlags.NotSaved;\n\t\tvar sr = go.AddComponent\u003CSpriteRenderer\u003E();\n\t\tsr.Billboard = SpriteRenderer.BillboardMode.None;\n\t\tsr.Lighting = true;\n\t\tsr.Shadows = true;\n\t\tsr.Opaque = true;\n\t\tsr.AlphaCutoff = 0.4f;\n\t\tbs.Sprites.Add( sr );\n\t\tbs.Paths.Add( null );\n\t}\n}\n"},{"Ident":"farten.shoes_and_daggers","Path":"Arena/FightAI.cs","FileName":"FightAI.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":443203,"IsPrivate":false,"Code":"using System;\n\n/// \u003Csummary\u003E\n/// Scores every move it could make and picks the best, with a bit of noise so it isn\u0027t predictable.\n/// \u003C/summary\u003E\npublic static class FightAI\n{\n\tstatic readonly FightAction[] All = Enum.GetValues\u003CFightAction\u003E();\n\n\tpublic static FightAction Choose( ArenaFight fight, Gladiator self )\n\t{\n\t\tvar opp = fight.OpponentOf( self );\n\t\tvar best = FightAction.Rest;\n\t\tfloat bestScore = float.MinValue;\n\n\t\tforeach ( var a in All )\n\t\t{\n\t\t\tif ( !fight.CanUse( self, a ) ) continue;\n\n\t\t\tfloat score = Score( fight, self, opp, a ) * Random.Shared.Float( 0.85f, 1.15f );\n\t\t\tif ( score \u003E bestScore )\n\t\t\t{\n\t\t\t\tbestScore = score;\n\t\t\t\tbest = a;\n\t\t\t}\n\t\t}\n\n\t\treturn best;\n\t}\n\n\tstatic float AvgHit( ArenaFight fight, Gladiator self, Gladiator opp, FightAction a )\n\t{\n\t\tvar (min, max) = ArenaFight.DamageRange( self, opp, a );\n\t\tfloat avg = (min \u002B max) * 0.5f;\n\t\tif ( !fight.IsDuel \u0026\u0026 self.Enchant != Enchant.None )\n\t\t{\n\t\t\tvar (emin, emax) = Catalog.EnchantRange( self.EnchantTier );\n\t\t\tavg \u002B= (emin \u002B emax) * 0.5f;\n\t\t}\n\t\treturn avg;\n\t}\n\n\t/// \u003Csummary\u003EHow much more punishment they can take (duel: armour only).\u003C/summary\u003E\n\tstatic float Life( ArenaFight fight, Gladiator g ) =\u003E fight.IsDuel ? Math.Max( 1, g.ArmourPoints ) : g.Health \u002B g.ArmourPoints;\n\n\tstatic float Score( ArenaFight fight, Gladiator self, Gladiator opp, FightAction a )\n\t{\n\t\tint energyAfter = Math.Max( 0, self.Energy - ArenaFight.Cost( a ) );\n\t\tfloat energyFrac = self.Energy / (float)self.MaxEnergy;\n\t\tfloat oppLife = Life( fight, opp );\n\t\tfloat myFrac = fight.IsDuel ? (self.MaxArmour \u003E 0 ? self.ArmourPoints / (float)self.MaxArmour : 0f) : self.Health / (float)self.MaxHealth;\n\t\tbool oppCanHitNext = fight.Distance \u003C= 2;\n\t\tfloat score = 0f;\n\n\t\tif ( ArenaFight.IsAttack( a ) )\n\t\t{\n\t\t\tfloat chance = fight.HitChance( self, opp, a );\n\t\t\tfloat avg = AvgHit( fight, self, opp, a );\n\t\t\tscore = chance * avg;\n\n\t\t\t// finishing blow: prefer whatever is most likely to land it\n\t\t\tif ( avg \u003E= oppLife ) score \u002B= chance * 60f;\n\t\t}\n\t\telse if ( ArenaFight.IsSpell( a ) )\n\t\t{\n\t\t\tvar s = ArenaFight.SpellOf( a );\n\t\t\tvar (min, max) = ArenaFight.SpellRange( self, s );\n\t\t\tfloat avg = (min \u002B max) * 0.5f;\n\t\t\tscore = s switch\n\t\t\t{\n\t\t\t\tSpell.Fireball or Spell.Lightning =\u003E avg * 0.95f \u002B (avg \u003E= oppLife ? 60f : 0f),\n\t\t\t\tSpell.Drain =\u003E avg * 1.3f \u002B (avg \u003E= oppLife ? 50f : 0f),\n\t\t\t\tSpell.Heal =\u003E myFrac \u003C 0.45f ? avg * 1.4f : 0.5f,\n\t\t\t\tSpell.Freeze =\u003E oppCanHitNext ? 14f * fight.FreezeChance( self, opp ) : 4f,\n\t\t\t\tSpell.Tremor =\u003E Math.Min( opp.ArmourPoints, avg * 2 ) * (fight.IsDuel ? 1f : 0.5f),\n\t\t\t\t_ =\u003E 0f\n\t\t\t};\n\t\t}\n\t\telse if ( ArenaFight.IsPotion( a ) )\n\t\t{\n\t\t\tscore = ArenaFight.PotionOf( a ) switch\n\t\t\t{\n\t\t\t\tPotion.Health =\u003E myFrac \u003C 0.3f ? 30f : 0f,\n\t\t\t\tPotion.Energy =\u003E energyFrac \u003C 0.2f ? 20f : 0f,\n\t\t\t\t_ =\u003E self.Magic \u003C 15 \u0026\u0026 self.SpellBits != 0 ? 10f : 0f\n\t\t\t};\n\t\t}\n\t\telse\n\t\t{\n\t\t\tswitch ( a )\n\t\t\t{\n\t\t\t\tcase FightAction.MoveForward:\n\t\t\t\t\tscore = fight.InRange ? 0 : 6f \u002B fight.Distance;\n\t\t\t\t\tif ( energyFrac \u003C 0.3f \u0026\u0026 fight.Distance \u003E 2 ) score *= 0.4f;\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase FightAction.Leap:\n\t\t\t\t\tscore = fight.Distance \u003E= 4 \u0026\u0026 energyAfter \u003E self.MaxEnergy * 0.4f ? 10f \u002B fight.Distance : 2f;\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase FightAction.MoveBack:\n\t\t\t\t\tscore = myFrac \u003C 0.25f \u0026\u0026 energyFrac \u003C 0.3f ? 7f : 0.5f;\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase FightAction.Rest:\n\t\t\t\t\tfloat missing = 1f - energyFrac;\n\t\t\t\t\tscore = missing * (fight.InRange ? 10f : 22f);\n\t\t\t\t\tif ( energyFrac \u003C 0.25f ) score \u002B= 12f;\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase FightAction.Taunt:\n\t\t\t\t\t{\n\t\t\t\t\t\tfloat chance = fight.TauntChance( self, opp );\n\t\t\t\t\t\tint drain = 6 \u002B self.Charisma * 2;\n\t\t\t\t\t\tscore = chance * (drain * 0.35f \u002B self.Charisma * 0.3f);\n\t\t\t\t\t\t// draining them to zero gives a free turn\n\t\t\t\t\t\tif ( drain \u003E= opp.Energy ) score \u002B= chance * 18f;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\tcase FightAction.Push:\n\t\t\t\t\t// shove them away when tired so there\u0027s room to rest\n\t\t\t\t\tscore = fight.PushRoom( opp ) \u0026\u0026 energyFrac \u003C 0.35f ? 14f * fight.PushChance( self ) : 1f;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// running dry while the enemy can reach you hands them a free hit\n\t\tif ( energyAfter \u003C= 0 \u0026\u0026 oppCanHitNext \u0026\u0026 a != FightAction.Rest \u0026\u0026 a != FightAction.DrinkEnergy )\n\t\t\tscore *= ArenaFight.IsAttack( a ) \u0026\u0026 AvgHit( fight, self, opp, a ) \u003E= oppLife ? 0.9f : 0.3f;\n\t\telse if ( energyAfter \u003C 15 \u0026\u0026 oppCanHitNext )\n\t\t\tscore *= 0.8f;\n\n\t\treturn score;\n\t}\n}\n"},{"Ident":"farten.shoes_and_daggers","Path":"UI/FighterCard.razor","FileName":"FighterCard.razor","PackageType":"game","CodeKind":"Game","AssetVersionId":443203,"IsPrivate":false,"Code":"@using System;\n@using Sandbox;\n@using Sandbox.UI;\n\n@* A fighter\u0027s S\u0026S2 flasks (energy, health, magicka) and armour shield, with their name banner. *@\n\u003Croot class=\u0022@(Flip ? \u0022flip\u0022 : \u0022\u0022)\u0022\u003E\n\t@if ( Fighter.IsValid() )\n\t{\n\t\t\u003Cdiv class=\u0022flasks\u0022\u003E\n\t\t\t@foreach ( var f in Flasks )\n\t\t\t{\n\t\t\t\t\u003Cdiv class=\u0022flask @f.Kind\u0022\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022empty\u0022\u003E\u003C/div\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022fill-clip\u0022 style=\u0022height: @(Pct( f.Value, f.Max ))%\u0022\u003E\u003Cdiv class=\u0022full\u0022\u003E\u003C/div\u003E\u003C/div\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022value\u0022\u003E@($\u0022{f.Value} / {f.Max}\u0022)\u003C/div\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022label\u0022\u003E@f.Label\u003C/div\u003E\n\t\t\t\t\u003C/div\u003E\n\t\t\t}\n\t\t\u003C/div\u003E\n\t\t\u003Cdiv class=\u0022banner\u0022\u003E@Fighter.DisplayName\u003C/div\u003E\n\t}\n\u003C/root\u003E\n\n@code\n{\n\tpublic Gladiator Fighter { get; set; }\n\tpublic bool Flip { get; set; }\n\tpublic bool Duel { get; set; }\n\n\trecord FlaskInfo( string Kind, int Value, int Max, string Label );\n\n\tIEnumerable\u003CFlaskInfo\u003E Flasks\n\t{\n\t\tget\n\t\t{\n\t\t\tvar g = Fighter;\n\t\t\tyield return new( \u0022energy\u0022, g.Energy, g.MaxEnergy, \u0022ENERGY\u0022 );\n\t\t\t// duels only hurt armour, so health doesn\u0027t matter there\n\t\t\tif ( !Duel ) yield return new( \u0022health\u0022, g.Health, g.MaxHealth, \u0022HEALTH\u0022 );\n\t\t\tif ( g.SpellBits != 0 ) yield return new( \u0022magic\u0022, g.Magic, g.MaxMagic, \u0022MAGICKA\u0022 );\n\t\t\tif ( g.MaxArmour \u003E 0 || Duel ) yield return new( \u0022armour\u0022, g.ArmourPoints, g.MaxArmour, \u0022ARMOUR\u0022 );\n\t\t}\n\t}\n\n\tstatic int Pct( int value, int max ) =\u003E max \u003C= 0 ? 0 : Math.Clamp( value * 100 / max, 0, 100 );\n\n\tprotected override int BuildHash() =\u003E HashCode.Combine( Fighter, Fighter?.Health, Fighter?.Energy, Fighter?.ArmourPoints, Fighter?.MaxArmour, Fighter?.Magic, Fighter?.DisplayName, HashCode.Combine( Flip, Duel ) );\n}\n"}]}