Player/NZInventory.cs

Inventory component for the NZombies player. Implements IInventory to hold multiple weapon GameObjects, manage active weapon switching, holstering timing, adding/removing weapons, trimming slots when perks are lost, and tracking weapon source/prefab for persistence and Pack-a-Punch logic.

Networking
using Sandbox;
using SWB.Shared;
using System.Linq;

namespace NZombies;

/// <summary>
/// TWO WEAPONS, AND THE SWITCH BETWEEN THEM.
///
/// ⛔ SWB SHIPS THE ITEM HALF AND EXPECTS THE GAME TO SUPPLY THE INVENTORY.
/// `SWB.Base.Weapon` already implements `IInventoryItem` — `Slot`, `OnCarryStart`,
/// `OnCarryStop`, `CanCarryStop`, `Mobility` are all there and correct. Nothing in
/// this project implemented `IInventory`, so the player could only ever hold one
/// gun: `EquipStartingWeapon` guarded on "is there already a weapon" and returned.
/// This is the missing half, not a new system.
///
/// ⚠️ NOT `BaseInventoryComponent`. The engine's slot inventory is a real,
/// replicated, host-authoritative implementation — but it stores
/// `BaseInventoryItem`s, and an SWB weapon is not one. Adapting every weapon to a
/// second item base to reuse it would be a far larger change than the ~150 lines
/// SWB's own interface asks for.
///
/// ⚠️ Carrying is EXPRESSED BY ENABLING. `OnCarryStart` enables the weapon
/// GameObject and resets its deploy timer so the draw animation plays;
/// `OnCarryStop` disables it. The holstered weapon keeps existing — which is what
/// makes its ammo, its attachments and its Pack-a-Punch level survive a switch.
/// </summary>
/// <summary>
/// Remembers which prefab a spawned weapon came from.
///
/// ⛔ THE JOIN KEY FOR EVERYTHING WEAPON-SHAPED. Pack-a-Punch levels, the box's
/// duplicate check and the wall buys all key on the prefab PATH, and a live weapon
/// otherwise has no way to say which one it is — `DisplayName` is a lossy join
/// through a string the weapon is free to change. The spawner knows the exact path,
/// so it stamps it here.
/// </summary>
public sealed class WeaponSource : Component
{
	[Property] public string Prefab { get; set; } = "";

	/// <summary>
	/// The weapon's name BEFORE any Pack-a-Punch suffix.
	///
	/// ⛔ CAPTURED ONCE, AT SPAWN, BECAUSE DERIVING IT BY STRIPPING FAILED IN PLAY.
	/// The first version recomputed the base by removing a trailing " MK&lt;n&gt;"
	/// from the current DisplayName — which is correct arithmetic and still shipped
	/// "M1911 MK2 MK2", because the value it was stripping had already been written
	/// through to shared prefab state and came back pre-suffixed on a fresh spawn.
	///
	/// A remembered base cannot drift: whatever DisplayName holds now, the name is
	/// rebuilt from this, so re-applying is idempotent no matter how many times
	/// ApplyStoredUpgrades runs or what it finds.
	/// </summary>
	[Property] public string BaseName { get; set; } = "";

	/// <summary>
	/// Apply saved stat overrides once this weapon's identity is known.
	///
	/// ⛔ IT HAS TO BE HERE, NOT IN `Weapon.OnEnabled`. Overrides are keyed by
	/// `Prefab`, and `OnEnabled` fires while the weapon is being built — BEFORE
	/// `SpawnWeapon` has stamped this component. `WeaponTuning.KeyFor` then fell back
	/// to the GameObject name, looked up a key nothing was stored under, found
	/// nothing and returned quietly: tuning saved correctly, reloaded correctly, and
	/// silently did not apply. Verified as `Damage = 45` on a weapon saved at 999.
	///
	/// ⚠️ The weapon keeps the `OnEnabled` call too, for RE-deploys — by then this
	/// component exists, and applying twice is idempotent. One hook covers the first
	/// spawn, the other covers every switch after it.
	/// </summary>
	protected override void OnStart()
	{
		var wep = Components.Get<SWB.Base.Weapon>( FindMode.EverythingInSelf );
		if ( wep.IsValid() )
		{
			// ⚠️ CLASS RULES FIRST, TUNING SECOND. Tuning is a saved per-weapon OVERRIDE and has to
			// win; applying it before the class bonus would let the multiply stamp over a value the
			// user set by hand.
			WeaponClassRules.Apply( wep );
			WeaponTuning.Apply( wep );
		}
	}
}

public sealed class NZInventory : Component, IInventory
{
	/// <summary>
	/// How many weapons you can carry.
	///
	/// ⚠️ TWO, and a property rather than a constant because Mule Kick is a perk
	/// that raises it to three. Hardcoding the cap would mean finding this again.
	/// </summary>
	[Property] public int MaxSlots { get; set; } = 2;

	public NetList<GameObject> Items { get; set; } = new();

	GameObject _active;

	/// <summary>
	/// The weapon in hand. Assigning routes through <see cref="SetActive"/> so the
	/// carry hooks always run — a raw field assignment would leave the old weapon
	/// enabled and both would render at once.
	/// </summary>
	/// ⛔ `new` BECAUSE `Component.Active` ALREADY EXISTS — and here the shadowing is
	/// forced, not careless: `IInventory` names this member and the interface must be
	/// satisfied. That makes this the one place in the project where hiding a base
	/// member is right, and the keyword says so out loud. (The other four times —
	/// `DamageOverlay.Enabled`, `Barricade.Reset`, `PackAPunch.Reset` — were
	/// accidents and got renamed.)
	public new GameObject Active
	{
		get => _active;
		set => SetActive( value );
	}

	public IInventoryItem ActiveItem
	{
		get => _active.IsValid() ? _active.Components.Get<IInventoryItem>( FindMode.EverythingInSelf ) : null;

		// ⚠️ Looked up by SEARCHING Items, because `IInventoryItem` is `IValid` and
		// carries no GameObject of its own. The component knows its object; the
		// interface does not expose it.
		set => SetActive( Items.FirstOrDefault( i =>
			i.IsValid() && ReferenceEquals( i.Components.Get<IInventoryItem>( FindMode.EverythingInSelf ), value ) ) );
	}

	/// <summary>Every weapon held, live ones only.</summary>
	public IEnumerable<GameObject> Weapons => Items.Where( i => i.IsValid() );

	public int Count => Items.Count( i => i.IsValid() );

	/// <summary>The cap actually in force — authored `MaxSlots` plus Mule Kick.
	///
	/// ⛔ DERIVED, NOT WRITTEN ON PURCHASE. Every other perk in this project is a
	/// multiplier read off the owned list, for one reason: `MaxSlots` is authored
	/// config, and a perk that WROTE 3 into it would have to write 2 back on loss
	/// — restoring a value it does not own and cannot know. Deriving it means
	/// losing the perk is free and the authored number is never touched.</summary>
	public int EffectiveMaxSlots
	{
		get
		{
			var nz = Components.Get<NZPlayer>( FindMode.EverythingInSelfAndAncestors );
			return MaxSlots + PerkEffects.BonusWeaponSlots( nz );
		}
	}

	public bool IsFull => Count >= EffectiveMaxSlots;

	/// <summary>Drop everything past the current cap, permanently.
	///
	/// ⛔ CALLED AFTER MULE KICK IS LOST, and the weapon is GONE — not dropped on
	/// the floor to be picked back up. That is the deal the perk makes.
	///
	/// ⚠️ Trims from the END so the third slot is what goes, not whatever happens
	/// to be selected. Loops on a snapshot because `Remove` mutates `Items`.</summary>
	public int TrimToCap()
	{
		var cap = EffectiveMaxSlots;
		var extra = Weapons.Skip( cap ).ToList();

		var nz = Components.Get<NZPlayer>( FindMode.EverythingInSelfAndAncestors );

		foreach ( var w in extra )
		{
			// ⚠️ CAPTURED BEFORE `Destroy`, obviously — but also before `Remove`, because
			// the prefab path is resolved through the weapon component and there is no
			// reason to make that depend on inventory membership.
			//
			// ⚠️ `Rarity.PrefabOf` is the established weapon-to-prefab mapping in this
			// project (TechEffects and ArsenalMenu both use it); a name would not round-trip
			// through `GiveWeapon`.
			var wep = w.Components.Get<SWB.Base.Weapon>( FindMode.EverythingInSelf );
			var path = wep.IsValid() ? Rarity.PrefabOf( wep ) : null;

			MuleKickAugments.Remember( nz, path );

			Log.Info( $"[nz-perk] Mule Kick lost — '{w.Name}' destroyed with slot {cap + 1}" );
			Remove( w );
			w.Destroy();
		}

		return extra.Count;
	}

	public bool Has( GameObject gameObject ) => Items.Contains( gameObject );

	/// <summary>
	/// Take a weapon into a free slot.
	///
	/// ⚠️ DOES NOT ENFORCE THE CAP — <see cref="GiveOrReplace"/> does, because the
	/// decision about WHICH weapon to drop belongs to the thing handing one over,
	/// not to the container.
	/// </summary>
	public void Add( GameObject gameObject, bool makeActive = false )
	{
		if ( !gameObject.IsValid() || Has( gameObject ) ) return;

		Items.Add( gameObject );

		// ⚠️ A new weapon starts HOLSTERED unless asked for. Enabling everything on
		// pickup is how you end up holding two guns at once.
		var item = gameObject.Components.Get<IInventoryItem>( FindMode.EverythingInSelf );

		// ⚠️ force: true — this is ACQUISITION, not a switch. See SetActive.
		if ( makeActive || !_active.IsValid() ) SetActive( gameObject, force: true );
		else item?.OnCarryStop();
	}

	public GameObject AddClone( GameObject gamePrefab, bool makeActive = true )
	{
		if ( !gamePrefab.IsValid() ) return null;

		var go = gamePrefab.Clone( new CloneConfig
		{
			Parent = GameObject,
			Transform = global::Transform.Zero,
			StartEnabled = true,
		} );

		Add( go, makeActive );
		return go;
	}

	/// <summary>
	/// Put a weapon in hand, holstering whatever was there.
	///
	/// ⚠️ Respects `CanCarryStop()`, which SWB uses to refuse a holster mid-deploy.
	/// Ignoring it lets a fast double-tap strand a weapon half-drawn.
	///
	/// ⛔ EVERY COMPONENT LOOKUP HERE PASSES `FindMode.EverythingInSelf`, because
	/// holstering is EXPRESSED BY DISABLING and `Components.Get&lt;T&gt;()` skips
	/// disabled components by default. Without it, switching TO a holstered weapon
	/// finds no `IInventoryItem`, never calls `OnCarryStart`, and leaves the player
	/// holding nothing at all — the weapon stays disabled and the old one is already
	/// put away. Caught by `nz_inv` printing the holstered slot as `?` with its raw
	/// object name instead of "M1911".
	/// </summary>
	public void SetActive( GameObject gameObject ) => SetActive( gameObject, false );

	/// <param name="force">
	/// Ignore `CanCarryStop()`.
	///
	/// ⛔ REQUIRED WHEN THE GAME HANDS YOU A WEAPON, and its absence was a shipped
	/// bug: `CanCarryStop()` is false during the draw, so buying two wall weapons a
	/// second apart hit the guard and SetActive returned early — leaving the new
	/// gun ALREADY ENABLED (it is cloned StartEnabled) but never made active. Both
	/// weapons rendered at once and neither was properly in hand.
	///
	/// The guard exists to stop a player switching mid-draw. Acquisition is not a
	/// switch: a wall buy, a box roll or a Pack-a-Punch collect must land whatever
	/// the animation is doing.
	/// </param>
	public void SetActive( GameObject gameObject, bool force )
	{
		if ( _active == gameObject ) return;
		if ( gameObject.IsValid() && !Has( gameObject ) ) return;

		// ⛔ ACQUISITION NEVER WAITS. `force` means the game HANDED you this weapon —
		// a wall buy, a box roll, a Pack-a-Punch collect — and those must land on the
		// frame they happen. Running a put-away first would leave the old gun on
		// screen while the new one is already in the inventory, which is precisely
		// the both-weapons-at-once state that took five separate bugs to clear.
		//
		// ⚠️ A holster also needs something to holster: no current weapon, or no
		// time configured, means switch straight through.
		// ⛔ SPEED COLA'S m2 DIVIDES THE HOLSTER AT THE READ SITE, NOT BY WRITING
		// `HolsterTime`. That property is a STATIC shared by every weapon and every player —
		// the same trap `Slide.SpeedMultiplier` and `Slide.Duration` document — so an
		// augment that assigned it would hand the faster swap to everyone and leave it there
		// after the perk was lost.
		//
		// ⚠️ Resolved once and used for both the gate and the timer below, so the two cannot
		// disagree about whether there is a holster to play at all.
		var swapSpeed = SpeedColaAugments.SwapSpeedMultiplier(
			Components.Get<NZPlayer>( FindMode.EverythingInSelfAndAncestors ) );

		var holsterTime = HolsterTime / swapSpeed;

		// ⚠️ THE GUN BEING PUT AWAY HAS ITS SAY (2026-10-04): Quickdraw Holster's "instant", the per-class augments' `s.swap`
		// (Scout, Quickscope Stock, Sawed-Off). The player's keys and wheel pass `instant` and never reach a put-away at all;
		// this is the half of a swap the console's switch, and anything later, still plays.
		var putAway = _active.IsValid() ? _active.Components.Get<SWB.Base.Weapon>( FindMode.EverythingInSelf ) : null;
		if ( putAway.IsValid() )
		{
			if ( TechStats.Flag( putAway, "f.instantswap" ) ) holsterTime = 0f;
			else
			{
				var swap = TechStats.Mul( putAway, "s.swap" );
				if ( swap > 0f ) holsterTime /= swap;
			}
		}

		if ( !force && _active.IsValid() && holsterTime > 0f )
		{
			// ⚠️ Retarget rather than restart. Scrolling twice quickly should end on
			// the second weapon, not replay the put-away and delay the switch again.
			if ( _holstering ) { _pending = gameObject; return; }

			var outgoing = _active.Components
				.Get<IInventoryItem>( FindMode.EverythingInSelf );

			// Respected HERE, at the start — checking it at commit time would strand
			// a weapon half-put-away with the switch already begun.
			if ( outgoing is not null && !outgoing.CanCarryStop() ) return;

			(_active.Components.Get<SWB.Base.Weapon>( FindMode.EverythingInSelf ))
				?.PlayHolster( holsterTime );

			_pending = gameObject;
			_holstering = true;
			_holsterDone = holsterTime;
			return;
		}

		Commit( gameObject, force );
	}

	/// <summary>How long the put-away takes before the new weapon is drawn.</summary>
	public static float HolsterTime { get; set; } = 0.3f;

	/// <summary>
	/// Tune the switch: `nz_holster_time [seconds]`, 0 for instant.
	///
	/// ⚠️ THIS IS A FEEL VALUE AND IT FIGHTS ITSELF. Longer reads as weight and
	/// matches the authored animation; shorter keeps scrolling responsive, which is
	/// what was asked for when the second slot went in. 0.3 is the answer to that
	/// tension — the authored clip is 0.46s and gets compressed to fit.
	/// </summary>
	[ConCmd( "nz_holster_time" )]
	public static void SetHolsterTime( float seconds = 0.3f )
	{
		HolsterTime = MathX.Clamp( seconds, 0f, 3f );
		Log.Info( $"[nz] holster {(HolsterTime <= 0f ? "OFF — switches are instant" : $"{HolsterTime:0.##}s")}" );
	}

	GameObject _pending;
	bool _holstering;
	TimeUntil _holsterDone;

	/// <summary>
	/// Finish a deferred switch.
	///
	/// ⚠️ THE PENDING TARGET IS RE-VALIDATED. Those 0.3s are long enough for the
	/// weapon to stop existing — Pack-a-Punch strips the active gun, and a swap can
	/// be queued in the same breath. `Commit` already tolerates an invalid target
	/// (it means "hold nothing"), but `Has` would refuse a destroyed one and leave
	/// the player stuck holding a holstered weapon forever.
	/// </summary>
	protected override void OnUpdate()
	{
		if ( !_holstering || !_holsterDone ) return;

		_holstering = false;

		var target = _pending;
		_pending = null;

		Commit( target.IsValid() ? target : Weapons.FirstOrDefault(), force: true );
	}

	/// <summary>The switch itself, once any put-away has finished.</summary>
	void Commit( GameObject gameObject, bool force )
	{
		if ( _active == gameObject ) return;

		if ( _active.IsValid() )
		{
			var old = _active.Components.Get<IInventoryItem>( FindMode.EverythingInSelf );
			if ( !force && old is not null && !old.CanCarryStop() ) return;
			old?.OnCarryStop();
		}

		_active = gameObject;

		if ( _active.IsValid() )
			_active.Components.Get<IInventoryItem>( FindMode.EverythingInSelf )?.OnCarryStart();

		TrackActive();

		// ⚠️ THE PER-CLASS WEAPON TECH'S DRAW (2026-10-04): Holster Reload, and Lifeline's health following the gun in hand.
		ClassTech.OnWeaponDrawn( GameObject.Components.Get<NZPlayer>( FindMode.EverythingInSelf ), _active );
	}

	/// <summary>
	/// Point the player's <c>StartingWeapon</c> at whatever is now in hand.
	///
	/// ⛔ IN HERE, NOT AT THE CALL SITES. It started life in the input handler and
	/// the console command promptly bypassed it — `nz_inv_next` switched the weapon
	/// and left StartingWeapon naming the holstered one. Half the project reads that
	/// field as "the current weapon" (wall buys, the box's duplicate check,
	/// Pack-a-Punch's price AND its level lookup), so a stale value means
	/// Pack-a-Punch quotes a price for one gun and upgrades the other.
	///
	/// Every switch goes through SetActive, so this is the one place it cannot be
	/// forgotten.
	/// </summary>
	void TrackActive()
	{
		var player = GameObject.Components.Get<NZPlayer>( FindMode.EverythingInSelf );
		if ( !player.IsValid() || !_active.IsValid() ) return;

		var src = _active.Components.Get<WeaponSource>( FindMode.EverythingInSelf );
		if ( src is not null && !string.IsNullOrEmpty( src.Prefab ) )
			player.StartingWeapon = src.Prefab;
	}

	public void SetActive( string name )
	{
		var go = Items.FirstOrDefault( i => i.IsValid() && i.Name == name );
		if ( go.IsValid() ) SetActive( go );
	}

	/// <summary>Switch by index, ignoring anything out of range.</summary>
	/// <param name="instant">
	/// Skip the mid-draw refusal.
	///
	/// ⛔ THE DEFAULT FEELS BROKEN ON A SCROLL WHEEL. `CanCarryStop()` is false for
	/// the length of the draw, so a switch requested during it is silently dropped —
	/// which on a flick of the wheel reads as the input not working rather than as
	/// the game protecting an animation. A player asking to swap has already decided;
	/// the half-played draw is theirs to interrupt.
	/// </param>
	public void SetActiveSlot( int index, bool instant = false )
	{
		var live = Weapons.ToList();
		if ( index < 0 || index >= live.Count ) return;

		SetActive( live[index], instant );
	}

	/// <summary>Which slot is in hand, or -1.</summary>
	public int ActiveSlot => Weapons.ToList().IndexOf( _active );

	/// <summary>Cycle. `delta` of 1 is next, -1 is previous.</summary>
	public void Cycle( int delta, bool instant = false )
	{
		var live = Weapons.ToList();
		if ( live.Count < 2 ) return;

		int i = live.IndexOf( _active );
		if ( i < 0 ) i = 0;

		SetActive( live[(i + delta + live.Count) % live.Count], instant );
	}

	/// <summary>
	/// Hand over a weapon, dropping the ACTIVE one if there is no room.
	///
	/// ⛔ REPLACES WHAT IS IN YOUR HANDS, not the oldest or the worst. That is the
	/// zombies convention and it is the only one the player can predict — you look
	/// at what you are holding, and that is what the wall buy or the box takes.
	/// </summary>
	public void GiveOrReplace( GameObject gameObject )
	{
		if ( !gameObject.IsValid() ) return;

		if ( IsFull && _active.IsValid() )
			Remove( _active );

		Add( gameObject, makeActive: true );
	}

	/// <summary>
	/// Drop a weapon out of the inventory and destroy it.
	///
	/// ⚠️ THE SAME disable-unparent-destroy SEQUENCE the buyables all use. A Destroy
	/// is deferred to end of frame, so anything that scans for weapons this frame
	/// would still find the corpse and act on it.
	/// </summary>
	public void Remove( GameObject gameObject )
	{
		if ( !gameObject.IsValid() ) return;

		bool wasActive = _active == gameObject;

		Items.Remove( gameObject );
		if ( wasActive ) _active = null;

		// ⛔ TELL IT TO STOP CARRYING FIRST. SWB tears its viewmodel down from
		// OnDestroy, but a weapon that is still ENABLED keeps running OnUpdate in
		// the frames before a deferred Destroy lands — and that update dereferences
		// `Owner`, which throws once the object is on its way out. That was the
		// `NullReferenceException at Weapon.OnUpdate` spam.
		//
		// ⛔ AND DO NOT UNPARENT. `Owner` is resolved ONCE, via
		// `Components.GetInAncestors<IPlayerBase>()` — cutting the object loose from
		// the player is precisely what makes that lookup meaningless.
		gameObject.Components.Get<IInventoryItem>( FindMode.EverythingInSelf )?.OnCarryStop();
		gameObject.Enabled = false;
		gameObject.Destroy();

		// ⛔ FALL BACK TO WHATEVER IS LEFT. Losing the weapon in your hands should
		// put the other one there, not leave you empty-handed — most visibly at
		// Pack-a-Punch, which takes your gun for 3.5s and stands you next to a
		// machine with nothing to shoot while a horde closes in.
		//
		// ⚠️ Here rather than in Pack-a-Punch, because it is true of EVERY path that
		// takes the active weapon — the machine today, a drop or a downed-state
		// strip later. Solving it at the machine would mean solving it again.
		//
		// ⚠️ instant, since there is no weapon left to refuse the holster.
		if ( wasActive )
			SetActive( Weapons.FirstOrDefault(), force: true );
	}

	public void Clear()
	{
		foreach ( var go in Items.ToList() )
			Remove( go );

		Items.Clear();
		_active = null;
	}

	// ── commands ─────────────────────────────────────────────────────────────

	static NZInventory Of( out NZPlayer player )
	{
		player = NZPlayer.Local;
		return player.IsValid() ? player.Inventory : null;
	}

	/// <summary>
	/// What you are carrying: `nz_inv`.
	///
	/// ⚠️ Prints the PREFAB and the PaP level per slot, not just names. With two
	/// slots the interesting failures are a level attached to the wrong gun and a
	/// slot holding a weapon the game thinks is elsewhere — both invisible from a
	/// list of display names.
	/// </summary>
	[ConCmd( "nz_inv" )]
	public static void List()
	{
		var inv = Of( out var player );
		if ( inv is null ) { Log.Warning( "[nz] no player" ); return; }

		Log.Info( $"[nz] {inv.Count}/{inv.EffectiveMaxSlots} slots (base {inv.MaxSlots}), active #{inv.ActiveSlot}"
			+ $"  (StartingWeapon = '{player.StartingWeapon}')" );

		int i = 0;
		foreach ( var go in inv.Weapons )
		{
			var wep = go.Components.Get<SWB.Base.Weapon>( FindMode.EverythingInSelf );
			var src = go.Components.Get<WeaponSource>( FindMode.EverythingInSelf )?.Prefab ?? "?";
			int lvl = player.PapLevelFor( src );

			Log.Info( $"[nz]   [{i}]{(go == inv.Active ? " *" : "  ")} "
				+ $"{wep?.DisplayName ?? go.Name,-14}{(lvl > 0 ? $" MK{lvl}" : "    ")}"
				+ $"  enabled={go.Enabled}  {src}" );
			i++;
		}
	}

	/// <summary>Switch slots from the console: `nz_inv_slot 1`.</summary>
	[ConCmd( "nz_inv_slot" )]
	public static void Slot( int index = 0 )
	{
		var inv = Of( out _ );
		if ( inv is null ) { Log.Warning( "[nz] no player" ); return; }

		inv.SetActiveSlot( index );
		List();
	}

	/// <summary>Cycle from the console: `nz_inv_next`.</summary>
	[ConCmd( "nz_inv_next" )]
	public static void Next()
	{
		var inv = Of( out _ );
		if ( inv is null ) { Log.Warning( "[nz] no player" ); return; }

		inv.Cycle( 1 );
		List();
	}

	/// <summary>How many weapons you can carry: `nz_inv_max 3` (Mule Kick).</summary>
	[ConCmd( "nz_inv_max" )]
	public static void Slots( int max = -1 )
	{
		var inv = Of( out _ );
		if ( inv is null ) { Log.Warning( "[nz] no player" ); return; }

		if ( max > 0 ) inv.MaxSlots = max;
		List();
	}
}