context: using System;
  using System.Linq;
  using System.Collections.Generic;
  using System.Threading;
  
  using TerrariaApi.Server;
  using Terraria;
  using Wolfje.Plugins.SEconomy.Configuration.WorldConfiguration;
  using Wolfje.Plugins.SEconomy.Journal;
  using TShockAPI;
  
  namespace Wolfje.Plugins.SEconomy {
  
  	/// <summary>
  	/// World economy. Provides monetary gain and loss as a 
  	/// result of interaction in the world, including mobs 
  	/// and players.
  	/// </summary>
  	public class WorldEconomy : IDisposable {
  		protected SEconomy Parent { get; set; }
  
  		/// <summary>
  		/// Gets or sets a multiplier for mob kills and deaths
  		/// after calculation.
  		/// </summary>
  		public int CustomMultiplier { get; set; }
  
  		/// <summary>
  		/// Format for this dictionary:
  		/// Key: NPC
  		/// Value: A list of players who have done damage to the NPC
  		/// </summary>
  		private Dictionary<Terraria.NPC, List<PlayerDamage>> DamageDictionary = new Dictionary<Terraria.NPC, List<PlayerDamage>>();
  
  		/// <summary>
  		/// Format for this dictionary:
  		/// * key: Player ID
  		/// * value: Last player hit ID
  		/// </summary>
  		protected Dictionary<int, int> PVPDamage = new Dictionary<int, int>();
  
  		/// <summary>
  		/// synch object for access to the dictionary.  You MUST obtain 
  		/// a mutex through this object to access the dictionary member.
  		/// </summary>
  		protected readonly object __dictionaryMutex = new object();
  
  		/// <summary>
  		/// synch object for access to the pvp dictionary.  You MUST obtain
  		/// a mutex through this object to access the dictionary member.
  		/// </summary>
  		protected readonly object __pvpDictMutex = new object();
  
  		/// <summary>
  		/// Synch object for NPC damage, forcing NPC damages to be serialized
  		/// </summary>
  		protected static readonly object __NPCDamageMutex = new object();
  
  		/// <summary>
  		/// World configuration node, from TShock\SEconomy\SEconomy.WorldConfig.json
  		/// </summary>
  		public Configuration.WorldConfiguration.WorldConfig WorldConfiguration { get; private set; }
  
  
  		public WorldEconomy(SEconomy parent)
  		{
  			this.WorldConfiguration = Configuration.WorldConfiguration.WorldConfig.LoadConfigurationFromFile(
  				"tshock" + System.IO.Path.DirectorySeparatorChar + "SEconomy" + System.IO.Path.DirectorySeparatorChar + "SEconomy.WorldConfig.json");
  			this.Parent = parent;
  
  			ServerApi.Hooks.NetGetData.Register(Parent.PluginInstance, NetHooks_GetData);
  			ServerApi.Hooks.NetSendData.Register(Parent.PluginInstance, NetHooks_SendData);
  			ServerApi.Hooks.GameUpdate.Register(Parent.PluginInstance, Game_Update);
  
  			this.CustomMultiplier = 1;
  		}
  
  		public void Dispose()
  		{
  			this.Dispose(true);
  			GC.SuppressFinalize(this);
  		}
  
  		protected virtual void Dispose(bool disposing)
  		{
  			if (disposing) {
  				ServerApi.Hooks.NetGetData.Deregister(Parent.PluginInstance, NetHooks_GetData);
  				ServerApi.Hooks.NetSendData.Deregister(Parent.PluginInstance, NetHooks_SendData);
  				ServerApi.Hooks.GameUpdate.Deregister(Parent.PluginInstance, Game_Update);
  			}
  		}
  
  		protected void Game_Update(EventArgs args)
  		{
  			foreach (Terraria.NPC npc in Terraria.Main.npc) {
  				if (npc == null || npc.townNPC == true || npc.lifeMax == 0) {
  					continue;
  				}
  
  				if (npc.active == false) {
  					GiveRewardsForNPC(npc);
  				}
  			}
  		}
  
  		#region "NPC Reward handling"
  
  		/// <summary>
  		/// Adds damage done by a player to an NPC slot.  When the NPC dies the rewards for it will fill out.
  		/// </summary>
  		protected void AddNPCDamage(Terraria.NPC NPC, Terraria.Player Player, int Damage, bool crit = false)
  		{
  			List<PlayerDamage> damageList = null;
  			PlayerDamage playerDamage = null;
  			double dmg;
  
  
  			if (Player == null || NPC.active == false || NPC.life <= 0) {
  				return;
  			}
  
  			lock (__dictionaryMutex) {
  				if (DamageDictionary.ContainsKey(NPC)) {
  					damageList = DamageDictionary[NPC];
  				} else {
  					damageList = new List<PlayerDamage>(1);
  					DamageDictionary.Add(NPC, damageList);
  				}
  			}
  
  			lock (__NPCDamageMutex) {
  				if ((playerDamage = damageList.FirstOrDefault(i => i.Player == Player)) == null) {
  					playerDamage = new PlayerDamage() { Player = Player };
  					damageList.Add(playerDamage);
  				}
  
  				if ((dmg = (crit ? 2 : 1) * Main.CalculateDamage(Damage, NPC.ichor ? NPC.defense - 20 : NPC.defense)) > NPC.life) {
  					dmg = NPC.life;
  				}
  			}
  
  			playerDamage.Damage += dmg;
  		}
  
  		/// <summary>
  		/// Should occur when an NPC dies; gives rewards out to all the players that hit it.
  		/// </summary>
  		protected void GiveRewardsForNPC(Terraria.NPC NPC)
  		{
  			List<PlayerDamage> playerDamageList = null;
  			IBankAccount account;
  			TSPlayer player;
  			Money rewardMoney = 0L;
  
  			lock (__dictionaryMutex) {
  				if (DamageDictionary.ContainsKey(NPC)) {
  					playerDamageList = DamageDictionary[NPC];
  
  					if (DamageDictionary.Remove(NPC) == false) {
  						TShock.Log.ConsoleError("seconomy: world economy: Remove of NPC after reward failed.  This is an internal error.");
  					}
  				}
  			}
  
  			if (playerDamageList == null) {
  				return;
  			}
  
  			if ((NPC.boss && WorldConfiguration.MoneyFromBossEnabled) || (!NPC.boss && WorldConfiguration.MoneyFromNPCEnabled)) {
  				foreach (PlayerDamage damage in playerDamageList) {
  					if (damage.Player == null
  					    || (player = TShockAPI.TShock.Players.FirstOrDefault(i => i != null && i.Index == damage.Player.whoAmI)) == null
  					    || (account = Parent.GetBankAccount(player)) == null) {
  						continue;
  					}
  
  					rewardMoney = CustomMultiplier * Convert.ToInt64(Math.Round(Convert.ToDouble(WorldConfiguration.MoneyPerDamagePoint) * damage.Damage));
  
  					//load override by NPC type, this allows you to put a modifier on the base for a specific mob type.
  					Configuration.WorldConfiguration.NPCRewardOverride overrideReward = WorldConfiguration.Overrides.FirstOrDefault(i => i.NPCID == NPC.type);
  					if (overrideReward != null) {
  						rewardMoney = CustomMultiplier * Convert.ToInt64(Math.Round(Convert.ToDouble(overrideReward.OverridenMoneyPerDamagePoint) * damage.Damage));
  					}
  
  					if (rewardMoney <= 0 || player.Group.HasPermission("seconomy.world.mobgains") == false) {
  						continue;
  					}
  
  					Journal.CachedTransaction fund = new Journal.CachedTransaction() {
  						Aggregations = 1,
  						Amount = rewardMoney,
  						DestinationBankAccountK = account.BankAccountK,
  						Message = NPC.name,
  						SourceBankAccountK = Parent.WorldAccount.BankAccountK
  					};
  
  					if ((NPC.boss && WorldConfiguration.AnnounceBossKillGains) || (!NPC.boss && WorldConfiguration.AnnounceNPCKillGains)) {
  						fund.Options |= Journal.BankAccountTransferOptions.AnnounceToReceiver;
  					}
  
  					//commit it to the transaction cache
  					Parent.TransactionCache.AddCachedTransaction(fund);
  				}
  			}
  		}
  
  		#endregion
  
  		/// <summary>
  		/// Assigns the last player slot to a victim in PVP
  		/// </summary>
  		protected void PlayerHitPlayer(int HitterSlot, int VictimSlot)
  		{
  
gt: 			lock (__pvpDictMutex) {