context: using System;
  using System.Collections.Generic;
  
  using ACE.Common;
  using ACE.Entity;
  using ACE.Entity.Enum;
  using ACE.Entity.Enum.Properties;
  using ACE.Entity.Models;
  using ACE.Server.Entity;
  using ACE.Server.Entity.Actions;
  using ACE.Server.Managers;
  using ACE.Server.Network.GameMessages.Messages;
  
  namespace ACE.Server.WorldObjects
  {
      public partial class Chest : Container, Lock
      {
          /// <summary>
          /// This is used for things like Mana Forge Chests
          /// </summary>
          public bool ChestRegenOnClose
          {
              get
              {
                  if (ChestResetInterval <= 5)
                      return true;
  
                  return GetProperty(PropertyBool.ChestRegenOnClose) ?? false;
              }
              set { if (!value) RemoveProperty(PropertyBool.ChestRegenOnClose); else SetProperty(PropertyBool.ChestRegenOnClose, value); }
          }
  
          /// <summary>
          /// This is used for things like Dirty Old Crate
          /// </summary>
          public bool ChestClearedWhenClosed
          {
              get => GetProperty(PropertyBool.ChestClearedWhenClosed) ?? false;
              set { if (!value) RemoveProperty(PropertyBool.ChestClearedWhenClosed); else SetProperty(PropertyBool.ChestClearedWhenClosed, value); }
          }
  
          /// <summary>
          /// This is the default setup for resetting chests
          /// </summary>
          public double ChestResetInterval
          {
              get
              {
                  var chestResetInterval = ResetInterval ?? Default_ChestResetInterval;
  
                  if (chestResetInterval < 15)
                      chestResetInterval = Default_ChestResetInterval;
  
                  return chestResetInterval;
              }
          }
  
          public virtual double Default_ChestResetInterval => 120;
  
          /// <summary>
          /// A new biota be created taking all of its values from weenie.
          /// </summary>
          public Chest(Weenie weenie, ObjectGuid guid) : base(weenie, guid)
          {
              SetEphemeralValues();
          }
  
          /// <summary>
          /// Restore a WorldObject from the database.
          /// </summary>
          public Chest(Biota biota) : base(biota)
          {
              SetEphemeralValues();
          }
  
          private void SetEphemeralValues()
          {
              ContainerCapacity = ContainerCapacity ?? 10;
              ItemCapacity = ItemCapacity ?? 120;
  
              ActivationResponse |= ActivationResponse.Use;   // todo: fix broken data
  
              CurrentMotionState = motionClosed;              // do any chests default to open?
  
              if (IsLocked)
                  DefaultLocked = true;
  
              if (DefaultLocked) // ignore regen interval, only regen on relock
                  NextGeneratorRegenerationTime = double.MaxValue;
          }
  
          protected static readonly Motion motionOpen = new Motion(MotionStance.NonCombat, MotionCommand.On);
          protected static readonly Motion motionClosed = new Motion(MotionStance.NonCombat, MotionCommand.Off);
  
          public override ActivationResult CheckUseRequirements(WorldObject activator)
          {
              var baseRequirements = base.CheckUseRequirements(activator);
              if (!baseRequirements.Success)
                  return baseRequirements;
  
              if (!(activator is Player player))
                  return new ActivationResult(false);
  
              if (IsLocked)
              {
                  EnqueueBroadcast(new GameMessageSound(Guid, Sound.OpenFailDueToLock, 1.0f));
                  return new ActivationResult(false);
              }
  
              if (UseLockTimestamp != null && activator.Guid.Full != LastUnlocker)
              {
                  var currentTime = Time.GetUnixTime();
  
                  // prevent ninja looting
                  if (UseLockTimestamp.Value + PropertyManager.GetDouble("unlocker_window").Item > currentTime)
                  {
                      player.SendTransientError(InUseMessage);
                      return new ActivationResult(false);
                  }
              }
  
              if (IsOpen)
              {
                  if (Viewer == player.Guid.Full)
                  {
                      // current player has this chest open, close it
                      Close(player);
                  }
                  else
                  {
                      // another player has this chest open -- ensure they are within range
                      var currentViewer = CurrentLandblock.GetObject(Viewer) as Player;
  
                      if (currentViewer == null)
                          Close(null);    // current viewer not found, close it
                      else
                          player.SendTransientError(InUseMessage);
                  }
  
                  return new ActivationResult(false);
              }
  
              // handle quest requirements
              if (Quest != null)
              {
                  if (!player.QuestManager.HasQuest(Quest))
                      EmoteManager.OnQuest(player);
                  else
                  {
                      if (player.QuestManager.CanSolve(Quest))
                      {
                          EmoteManager.OnQuest(player);
                      }
                      else
                      {
                          player.QuestManager.HandleSolveError(Quest);
                          return new ActivationResult(false);
                      }
                  }
              }
  
              return new ActivationResult(true);
          }
  
          /// <summary>
          /// This is raised by Player.HandleActionUseItem.<para />
          /// The item does not exist in the players possession.<para />
          /// If the item was outside of range, the player will have been commanded to move using DoMoveTo before ActOnUse is called.<para />
          /// When this is called, it should be assumed that the player is within range.
          /// </summary>
          public override void ActOnUse(WorldObject wo)
          {
              if (!(wo is Player player))
                  return;
  
              // open chest
              Open(player);
          }
  
          public override void Open(Player player)
          {
              base.Open(player);
  
              if (!ResetMessagePending && !double.IsPositiveInfinity(ChestResetInterval))
              {
                  var resetTimestamp = ResetTimestamp;
  
                  var actionChain = new ActionChain();
                  actionChain.AddDelaySeconds(ChestResetInterval);
                  actionChain.AddAction(this, () => Reset(resetTimestamp));
                  actionChain.EnqueueChain();
  
                  ResetMessagePending = true;
              }
  
              UseLockTimestamp = null;
          }
  
          public override void Close(Player player)
          {
              Close(player);
          }
  
          /// <summary>
          /// Called when a chest is closed, or walked away from
          /// </summary>
          public void Close(Player player, bool tryReset = true)
          {
              base.Close(player);
  
              if (ChestRegenOnClose && tryReset)
                  Reset(ResetTimestamp);
          }
  
          public override void FinishClose(Player player)
          {
              base.FinishClose(player);
  
              if (ChestClearedWhenClosed && InitCreate > 0)
              {
                  var removeQueueTotal = 0;
                  foreach (var generator in GeneratorProfiles)
                      removeQueueTotal += generator.RemoveQueue.Count;
  
                  if ((CurrentCreate - removeQueueTotal) == 0)
                      FadeOutAndDestroy(); // Chest's complete generated inventory count has been wiped out
                      //Destroy(); // Chest's complete generated inventory count has been wiped out
              }
          }
  
          public void Reset(double? resetTimestamp)
          {
              if (resetTimestamp != ResetTimestamp)
                  return;     // already cleared by previous reset
  
              // TODO: if 'ResetInterval' style, do we want to ensure a minimum amount of time for the last viewer?
  
              // should only be an edge case with reload-landblock
              if (CurrentLandblock == null)
                  return;
  
              var player = CurrentLandblock.GetObject(Viewer) as Player;
  
              if (IsOpen)
                  Close(player, false);
  
              if (DefaultLocked && !IsLocked)
              {
                  IsLocked = true;
                  EnqueueBroadcast(new GameMessagePublicUpdatePropertyBool(this, PropertyBool.Locked, IsLocked));
              }
  
              if (IsGenerator)
              {
                  ResetGenerator();
                  if (InitCreate > 0)
                      Generator_Regeneration();
              }
  
              ResetTimestamp = Time.GetUnixTime();
              ResetMessagePending = false;
          }
  
          public override void ResetGenerator()
          {
              foreach (var generator in GeneratorProfiles)
              {
                  var profileReset = false;
  
                  foreach (var rNode in generator.Spawned.Values)
                  {
                      var wo = rNode.TryGetWorldObject();
  
                      if (wo != null)
                      {
                          if (TryRemoveFromInventory(wo.Guid)) // only affect contained items.
                          {
                              wo.Destroy();
                          }
  
                          if (!(wo is Creature))
                              profileReset = true;
                      }
                  }
  
                  if (profileReset)
                  {
                      generator.Spawned.Clear();
                      generator.SpawnQueue.Clear();
                  }
              }
  
              if (GeneratedTreasureItem)
              {
                  var items = new List<WorldObject>();
                  foreach (var item in Inventory.Values)
                      items.Add(item);
                  foreach (var item in items)
                  {
                      if (TryRemoveFromInventory(item.Guid))
                          item.Destroy();
                  }
                  GeneratedTreasureItem = false;
              }
          }
  
          protected override float DoOnOpenMotionChanges()
          {
              if (MotionTableId != 0)
                  return ExecuteMotion(motionOpen);
              else
                  return 0;
          }
  
          protected override float DoOnCloseMotionChanges()
          {
              if (MotionTableId != 0)
                  return ExecuteMotion(motionClosed);
              else
                  return 0;
          }
  
          public string LockCode
          {
              get => GetProperty(PropertyString.LockCode);
              set { if (value == null) RemoveProperty(PropertyString.LockCode); else SetProperty(PropertyString.LockCode, value); }
          }
  
          /// <summary>
          /// Used for unlocking a chest via lockpick, so contains a skill check
          /// player.Skills[Skill.Lockpick].Current should be sent for the skill check
          /// </summary>
          public UnlockResults Unlock(uint unlockerGuid, uint playerLockpickSkillLvl, ref int difficulty)
          {
              var result = LockHelper.Unlock(this, playerLockpickSkillLvl, ref difficulty);
  
  
gt:             if (result == UnlockResults.UnlockSuccess)