/*
 * Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

using Framework.Constants;
using Framework.Database;
using Framework.GameMath;
using Game.AI;
using Game.BattleGrounds;
using Game.Chat;
using Game.Conditions;
using Game.DataStorage;
using Game.Entities;
using Game.Groups;
using Game.Guilds;
using Game.Maps;
using Game.Movement;
using Game.PvP;
using Game.Spells;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Reflection;

namespace Game.Scripting
{
    // Manages registration, loading, and execution of Scripts.
    public class ScriptManager : Singleton<ScriptManager>
    {
        ScriptManager() { }

        //Initialization
        public void Initialize()
        {
            uint oldMSTime = Time.GetMSTime();

            LoadDatabase();

            Log.outInfo(LogFilter.ServerLoading, "Loading C# scripts");

            FillSpellSummary();

            if (LoadScripts())
                Log.outInfo(LogFilter.ServerLoading, "Loaded {0} C# scripts in {1} ms", GetScriptCount(), Time.GetMSTimeDiffToNow(oldMSTime));
        }

        public bool LoadScripts(bool reload = false)
        {
            if (!File.Exists("Scripts.dll"))
            {
                Log.outError(LogFilter.ServerLoading, "Cant find file {0}, Scripts will not be loaded.");
                return false;
            }

            Assembly asm = Assembly.LoadFile(Path.GetFullPath("Scripts.dll"));
            if (asm == null)
            {
                Log.outError(LogFilter.ServerLoading, "Error Loading Scripts.dll, Scripts will not be loaded.");
                return false;
            }

            foreach (var type in asm.GetTypes())
            {
                var attributes = (ScriptAttribute[])type.GetCustomAttributes<ScriptAttribute>(true);
                if (attributes.Empty())
                {
                    var baseType = type.BaseType;
                    while (baseType != null)
                    {
                        if (baseType == typeof(ScriptObject))
                            Log.outWarn(LogFilter.Server, "Script {0} does not have ScriptAttribute", type.Name);

                        baseType = baseType.BaseType;
                    }
                }

                var constructors = type.GetConstructors();
                if (constructors.Length == 0)
                {
                    Log.outError(LogFilter.Scripts, "Type: {0} contains no Public Constructors. Can't load script.", type.Name);
                    break;
                }

                foreach (var attribute in attributes)
                {
                    if (!constructors.Any(p => p.GetParameters().Length == attribute.Args.Length))
                    {
                        Log.outError(LogFilter.Scripts, "Type: {0} has ScriptAttribute that does not match paramter count: {1} Can't load script.", type.Name, attribute.Args.Length);
                        continue;
                    }
                    Activator.CreateInstance(type, attribute.Args);
                }
            }

            return true;
        }

        public void LoadDatabase()
        {
            LoadScriptWaypoints();
            LoadScriptSplineChains();
        }
        void LoadScriptWaypoints()
        {
            uint oldMSTime = Time.GetMSTime();

            // Drop Existing Waypoint list

            m_mPointMoveMap.Clear();

            ulong uiCreatureCount = 0;

            // Load Waypoints
            SQLResult result = DB.World.Query("SELECT COUNT(entry) FROM script_waypoint GROUP BY entry");
            if (!result.IsEmpty())
                uiCreatureCount = result.Read<uint>(0);

            Log.outInfo(LogFilter.ServerLoading, "Loading Script Waypoints for {0} creature(s)...", uiCreatureCount);

            //                                0       1         2           3           4           5
            result = DB.World.Query("SELECT entry, pointid, location_x, location_y, location_z, waittime FROM script_waypoint ORDER BY pointid");

            if (result.IsEmpty())
            {
                Log.outInfo(LogFilter.ServerLoading, "Loaded 0 Script Waypoints. DB table `script_waypoint` is empty.");
                return;
            }

            uint count = 0;

            do
            {
                ScriptPointMove temp = new ScriptPointMove();

                temp.uiCreatureEntry = result.Read<uint>(0);
                uint uiEntry = temp.uiCreatureEntry;
                temp.uiPointId = result.Read<uint>(1);
                temp.fX = result.Read<float>(2);
                temp.fY = result.Read<float>(3);
                temp.fZ = result.Read<float>(4);
                temp.uiWaitTime = result.Read<uint>(5);

                CreatureTemplate pCInfo = Global.ObjectMgr.GetCreatureTemplate(temp.uiCreatureEntry);

                if (pCInfo == null)
                {
                    Log.outError(LogFilter.Sql, "TSCR: DB table script_waypoint has waypoint for non-existant creature entry {0}", temp.uiCreatureEntry);
                    continue;
                }

                if (pCInfo.ScriptID == 0)
                    Log.outError(LogFilter.Sql, "TSCR: DB table script_waypoint has waypoint for creature entry {0}, but creature does not have ScriptName defined and then useless.", temp.uiCreatureEntry);

                m_mPointMoveMap.Add(uiEntry, temp);
                ++count;
            }
            while (result.NextRow());

            Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Script Waypoint nodes in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));

        }
        void LoadScriptSplineChains()
        {
            uint oldMSTime = Time.GetMSTime();

            m_mSplineChainsMap.Clear();

            //                                                     0       1        2             3               4
            SQLResult resultMeta = DB.World.Query("SELECT entry, chainId, splineId, expectedDuration, msUntilNext FROM script_spline_chain_meta ORDER BY entry asc, chainId asc, splineId asc");
            //                                                  0       1         2       3   4  5  6
            SQLResult resultWP = DB.World.Query("SELECT entry, chainId, splineId, wpId, x, y, z FROM script_spline_chain_waypoints ORDER BY entry asc, chainId asc, splineId asc, wpId asc");
            if (resultMeta.IsEmpty() || resultWP.IsEmpty())
            {
                Log.outInfo(LogFilter.ServerLoading, "Loaded spline chain data for 0 chains, consisting of 0 splines with 0 waypoints. DB tables `script_spline_chain_meta` and `script_spline_chain_waypoints` are empty.");
            }
            else
            {
                uint chainCount = 0, splineCount = 0, wpCount = 0;
                do
                {
                    uint entry = resultMeta.Read<uint>(0);
                    ushort chainId = resultMeta.Read<ushort>(1);
                    byte splineId = resultMeta.Read<byte>(2);

                    var key = Tuple.Create(entry, chainId);
                    if (!m_mSplineChainsMap.ContainsKey(key))
                        m_mSplineChainsMap[key] = new List<SplineChainLink>();

                    var chain = m_mSplineChainsMap[Tuple.Create(entry,chainId)];
                    if (splineId != chain.Count)
                    {
                        Log.outWarn(LogFilter.ServerLoading, "Creature #{0}: Chain {1} has orphaned spline {2}, skipped.", entry, chainId, splineId);
                        continue;
                    }

                    uint expectedDuration = resultMeta.Read<uint>(3), msUntilNext = resultMeta.Read<uint>(4);
                    chain.Add(new SplineChainLink(expectedDuration, msUntilNext));

                    if (splineId == 0)
                        ++chainCount;
                    ++splineCount;
                } while (resultMeta.NextRow());

                do
                {
                    uint entry = resultWP.Read<uint>(0);
                    ushort chainId = resultWP.Read<ushort>(1);
                    byte splineId = resultWP.Read<byte>(2);
                    byte wpId = resultWP.Read<byte>(3);
                    float posX = resultWP.Read<float>(4);
                    float posY = resultWP.Read<float>(5);
                    float posZ = resultWP.Read<float>(6);
                    var chain = m_mSplineChainsMap.LookupByKey(Tuple.Create(entry,chainId));
                    if (chain == null)
                    {
                        Log.outWarn(LogFilter.ServerLoading, "Creature #{0} has waypoint data for spline chain {1}. No such chain exists - entry skipped.", entry, chainId);
                        continue;
                    }

                    if (splineId >= chain.Count)
                    {
                        Log.outWarn(LogFilter.ServerLoading, "Creature #{0} has waypoint data for spline ({1},{2}). The specified chain does not have a spline with this index - entry skipped.", entry, chainId, splineId);
                        continue;
                    }
                    SplineChainLink spline = chain[splineId];
                    if (wpId != spline.Points.Count)
                    {
                        Log.outWarn(LogFilter.ServerLoading, "Creature #{0} has orphaned waypoint data in spline ({1},{2}) at index {3}. Skipped.", entry, chainId, splineId, wpId);
                        continue;
                    }
                    spline.Points.Add(new Vector3(posX, posY, posZ));
                    ++wpCount;
                } while (resultWP.NextRow());

                Log.outInfo(LogFilter.ServerLoading, "Loaded spline chain data for {0} chains, consisting of {1} splines with {2} waypoints in {3} ms", chainCount, splineCount, wpCount, Time.GetMSTimeDiffToNow(oldMSTime));
            }
        }
        public void FillSpellSummary()
        {
            UnitAI.FillAISpellInfo();

            SpellInfo pTempSpell;
            var spellStorage = Global.SpellMgr.GetSpellInfoStorage();
            foreach (var i in spellStorage.Keys)
            {
                spellSummaryStorage[i] = new SpellSummary();

                pTempSpell = spellStorage.LookupByKey(i);
                // This spell doesn't exist.
                if (pTempSpell == null)
                    continue;

                foreach (SpellEffectInfo effect in pTempSpell.GetEffectsForDifficulty(Difficulty.None))
                {
                    if (effect == null)
                        continue;

                    // Spell targets self.
                    if (effect.TargetA.GetTarget() == Targets.UnitCaster)
                        spellSummaryStorage[i].Targets |= 1 << ((int)SelectTargetType.Self - 1);

                    // Spell targets a single enemy.
                    if (effect.TargetA.GetTarget() == Targets.UnitEnemy || effect.TargetA.GetTarget() == Targets.DestEnemy)
                        spellSummaryStorage[i].Targets |= 1 << ((int)SelectTargetType.SingleEnemy - 1);

                    // Spell targets AoE at enemy.
                    if (effect.TargetA.GetTarget() == Targets.UnitSrcAreaEnemy || effect.TargetA.GetTarget() == Targets.UnitDestAreaEnemy ||
                        effect.TargetA.GetTarget() == Targets.SrcCaster || effect.TargetA.GetTarget() == Targets.DestDynobjEnemy)
                        spellSummaryStorage[i].Targets |= 1 << ((int)SelectTargetType.AoeEnemy - 1);

                    // Spell targets an enemy.
                    if (effect.TargetA.GetTarget() == Targets.UnitEnemy || effect.TargetA.GetTarget() == Targets.DestEnemy ||
                        effect.TargetA.GetTarget() == Targets.UnitSrcAreaEnemy || effect.TargetA.GetTarget() == Targets.UnitDestAreaEnemy ||
                        effect.TargetA.GetTarget() == Targets.SrcCaster || effect.TargetA.GetTarget() == Targets.DestDynobjEnemy)
                        spellSummaryStorage[i].Targets |= 1 << ((int)SelectTargetType.AnyEnemy - 1);

                    // Spell targets a single friend (or self).
                    if (effect.TargetA.GetTarget() == Targets.UnitCaster || effect.TargetA.GetTarget() == Targets.UnitAlly ||
                        effect.TargetA.GetTarget() == Targets.UnitParty)
                        spellSummaryStorage[i].Targets |= 1 << ((int)SelectTargetType.SingleFriend - 1);

                    // Spell targets AoE friends.
                    if (effect.TargetA.GetTarget() == Targets.UnitCasterAreaParty || effect.TargetA.GetTarget() == Targets.UnitLastareaParty ||
                        effect.TargetA.GetTarget() == Targets.SrcCaster)
                        spellSummaryStorage[i].Targets |= 1 << ((int)SelectTargetType.AoeFriend - 1);

                    // Spell targets any friend (or self).
                    if (effect.TargetA.GetTarget() == Targets.UnitCaster || effect.TargetA.GetTarget() == Targets.UnitAlly ||
                        effect.TargetA.GetTarget() == Targets.UnitParty || effect.TargetA.GetTarget() == Targets.UnitCasterAreaParty ||
                        effect.TargetA.GetTarget() == Targets.UnitLastareaParty || effect.TargetA.GetTarget() == Targets.SrcCaster)
                        spellSummaryStorage[i].Targets |= 1 << ((int)SelectTargetType.AnyFriend - 1);

                    // Make sure that this spell includes a damage effect.
                    if (effect.Effect == SpellEffectName.SchoolDamage || effect.Effect == SpellEffectName.Instakill ||
                        effect.Effect == SpellEffectName.EnvironmentalDamage || effect.Effect == SpellEffectName.HealthLeech)
                        spellSummaryStorage[i].Effects |= 1 << ((int)SelectEffect.Damage - 1);

                    // Make sure that this spell includes a healing effect (or an apply aura with a periodic heal).
                    if (effect.Effect == SpellEffectName.Heal || effect.Effect == SpellEffectName.HealMaxHealth ||
                        effect.Effect == SpellEffectName.HealMechanical || (effect.Effect == SpellEffectName.ApplyAura && effect.ApplyAuraName == AuraType.PeriodicHeal))
                        spellSummaryStorage[i].Effects |= 1 << ((int)SelectEffect.Healing - 1);

                    // Make sure that this spell applies an aura.
                    if (effect.Effect == SpellEffectName.ApplyAura)
                        spellSummaryStorage[i].Effects |= 1 << ((int)SelectEffect.Aura - 1);
                }
            }
        }

        public List<SplineChainLink> GetSplineChain(Creature who, ushort chainId)
        {
            return GetSplineChain(who.GetEntry(), chainId);
        }
        List<SplineChainLink> GetSplineChain(uint entry, ushort chainId)
        {
            return m_mSplineChainsMap.LookupByKey(Tuple.Create(entry, chainId));
        }

        public string ScriptsVersion() { return "Integrated Cypher Scripts"; }

        public void IncrementScriptCount() { ++_ScriptCount; }
        public uint GetScriptCount() { return _ScriptCount; }

        //Reloading
        public void Reload()
        {

        }

        //Unloading
        public void Unload()
        {
            foreach (ScriptRegistry<ScriptObject> scr in ScriptStorage)
                scr.Unload();
        }

        //SpellScriptLoader
        public List<SpellScript> CreateSpellScripts(uint spellId, Spell invoker)
        {
            var scriptList = new List<SpellScript>();
            var bounds = Global.ObjectMgr.GetSpellScriptsBounds(spellId);

            var reg = GetScriptRegistry<SpellScriptLoader>();
            if (reg == null)
                return scriptList;

            foreach (var id in bounds)
            {
                var tmpscript = reg.GetScriptById(id);
                if (tmpscript == null)
                    continue;

                SpellScript script = tmpscript.GetSpellScript();
                if (script == null)
                    continue;

                script._Init(tmpscript.GetName(), spellId);
                if (!script._Load(invoker))
                    continue;

                scriptList.Add(script);
            }

            return scriptList;
        }
        public List<AuraScript> CreateAuraScripts(uint spellId, Aura invoker)
        {
            var scriptList = new List<AuraScript>();
            var bounds = Global.ObjectMgr.GetSpellScriptsBounds(spellId);

            var reg = GetScriptRegistry<SpellScriptLoader>();
            if (reg == null)
                return scriptList;

            foreach (var id in bounds)
            {
                var tmpscript = reg.GetScriptById(id);
                if (tmpscript == null)
                    continue;

                AuraScript script = tmpscript.GetAuraScript();
                if (script == null)
                    continue;

                script._Init(tmpscript.GetName(), spellId);
                if (!script._Load(invoker))
                    continue;

                scriptList.Add(script);
            }

            return scriptList;
        }
        public Dictionary<SpellScriptLoader, uint> CreateSpellScriptLoaders(uint spellId)
        {
            var scriptDic = new Dictionary<SpellScriptLoader, uint>();
            var bounds = Global.ObjectMgr.GetSpellScriptsBounds(spellId);

            var reg = GetScriptRegistry<SpellScriptLoader>();
            if (reg == null)
                return scriptDic;

            foreach (var id in bounds)
            {
                var tmpscript = reg.GetScriptById(id);
                if (tmpscript == null)
                    continue;

                scriptDic.Add(tmpscript, id);
            }

            return scriptDic;
        }

        //WorldScript
        public void OnOpenStateChange(bool open)
        {
            ForEach<WorldScript>(p => p.OnOpenStateChange(open));
        }
        public void OnConfigLoad(bool reload)
        {
            ForEach<WorldScript>(p => p.OnConfigLoad(reload));
        }
        public void OnMotdChange(string newMotd)
        {
            ForEach<WorldScript>(p => p.OnMotdChange(newMotd));
        }
        public void OnShutdownInitiate(ShutdownExitCode code, ShutdownMask mask)
        {
            ForEach<WorldScript>(p => p.OnShutdownInitiate(code, mask));
        }
        public void OnShutdownCancel()
        {
            ForEach<WorldScript>(p => p.OnShutdownCancel());
        }
        public void OnWorldUpdate(uint diff)
        {
            ForEach<WorldScript>(p => p.OnUpdate(diff));
        }

        //FormulaScript
        public void OnHonorCalculation(float honor, uint level, float multiplier)
        {
            ForEach<FormulaScript>(p => p.OnHonorCalculation(honor, level, multiplier));
        }
        public void OnGrayLevelCalculation(uint grayLevel, uint playerLevel)
        {
            ForEach<FormulaScript>(p => p.OnGrayLevelCalculation(grayLevel, playerLevel));
        }
        public void OnColorCodeCalculation(XPColorChar color, uint playerLevel, uint mobLevel)
        {
            ForEach<FormulaScript>(p => p.OnColorCodeCalculation(color, playerLevel, mobLevel));
        }
        public void OnZeroDifferenceCalculation(uint diff, uint playerLevel)
        {
            ForEach<FormulaScript>(p => p.OnZeroDifferenceCalculation(diff, playerLevel));
        }
        public void OnBaseGainCalculation(uint gain, uint playerLevel, uint mobLevel)
        {
            ForEach<FormulaScript>(p => p.OnBaseGainCalculation(gain, playerLevel, mobLevel));
        }
        public void OnGainCalculation(uint gain, Player player, Unit unit)
        {
            Contract.Assert(player != null);
            Contract.Assert(unit != null);

            ForEach<FormulaScript>(p => p.OnGainCalculation(gain, player, unit));
        }
        public void OnGroupRateCalculation(float rate, uint count, bool isRaid)
        {
            ForEach<FormulaScript>(p => p.OnGroupRateCalculation(rate, count, isRaid));
        }

        //MapScript
        public void OnCreateMap(Map map)
        {
            Contract.Assert(map != null);
            var record = map.GetEntry();

            if (record != null && record.IsWorldMap())
                ForEach<WorldMapScript>(p => p.OnCreate(map));

            if (record != null && record.IsDungeon())
                ForEach<InstanceMapScript>(p => p.OnCreate(map.ToInstanceMap()));

            if (record != null && record.IsBattleground())
                ForEach<BattlegroundMapScript>(p => p.OnCreate(map.ToBattlegroundMap()));
        }
        public void OnDestroyMap(Map map)
        {
            Contract.Assert(map != null);
            var record = map.GetEntry();

            if (record != null && record.IsWorldMap())
                ForEach<WorldMapScript>(p => p.OnDestroy(map));

            if (record != null && record.IsDungeon())
                ForEach<InstanceMapScript>(p => p.OnDestroy(map.ToInstanceMap()));

            if (record != null && record.IsBattleground())
                ForEach<BattlegroundMapScript>(p => p.OnDestroy(map.ToBattlegroundMap()));
        }
        public void OnLoadGridMap(Map map, GridMap gmap, uint gx, uint gy)
        {
            Contract.Assert(map != null);
            Contract.Assert(gmap != null);
            var record = map.GetEntry();

            if (record != null && record.IsWorldMap())
                ForEach<WorldMapScript>(p => p.OnLoadGridMap(map, gmap, gx, gy));

            if (record != null && record.IsDungeon())
                ForEach<InstanceMapScript>(p => p.OnLoadGridMap(map.ToInstanceMap(), gmap, gx, gy));

            if (record != null && record.IsBattleground())
                ForEach<BattlegroundMapScript>(p => p.OnLoadGridMap(map.ToBattlegroundMap(), gmap, gx, gy));

        }
        public void OnUnloadGridMap(Map map, GridMap gmap, uint gx, uint gy)
        {
            Contract.Assert(map != null);
            Contract.Assert(gmap != null);
            var record = map.GetEntry();

            if (record != null && record.IsWorldMap())
                ForEach<WorldMapScript>(p => p.OnUnloadGridMap(map, gmap, gx, gy));

            if (record != null && record.IsDungeon())
                ForEach<InstanceMapScript>(p => p.OnUnloadGridMap(map.ToInstanceMap(), gmap, gx, gy));

            if (record != null && record.IsBattleground())
                ForEach<BattlegroundMapScript>(p => p.OnUnloadGridMap(map.ToBattlegroundMap(), gmap, gx, gy));
        }
        public void OnPlayerEnterMap(Map map, Player player)
        {
            Contract.Assert(map != null);
            Contract.Assert(player != null);

            ForEach<PlayerScript>(p => p.OnMapChanged(player));

            var record = map.GetEntry();

            if (record != null && record.IsWorldMap())
                ForEach<WorldMapScript>(p => p.OnPlayerEnter(map, player));

            if (record != null && record.IsDungeon())
                ForEach<InstanceMapScript>(p => p.OnPlayerEnter(map.ToInstanceMap(), player));

            if (record != null && record.IsBattleground())
                ForEach<BattlegroundMapScript>(p => p.OnPlayerEnter(map.ToBattlegroundMap(), player));
        }
        public void OnPlayerLeaveMap(Map map, Player player)
        {
            Contract.Assert(map != null);
            var record = map.GetEntry();

            if (record != null && record.IsWorldMap())
                ForEach<WorldMapScript>(p => p.OnPlayerLeave(map, player));

            if (record != null && record.IsDungeon())
                ForEach<InstanceMapScript>(p => p.OnPlayerLeave(map.ToInstanceMap(), player));

            if (record != null && record.IsBattleground())
                ForEach<BattlegroundMapScript>(p => p.OnPlayerLeave(map.ToBattlegroundMap(), player));
        }
        public void OnMapUpdate(Map map, uint diff)
        {
            Contract.Assert(map != null);
            var record = map.GetEntry();

            if (record != null && record.IsWorldMap())
                ForEach<WorldMapScript>(p => p.OnUpdate(map, diff));

            if (record != null && record.IsDungeon())
                ForEach<InstanceMapScript>(p => p.OnUpdate(map.ToInstanceMap(), diff));

            if (record != null && record.IsBattleground())
                ForEach<BattlegroundMapScript>(p => p.OnUpdate(map.ToBattlegroundMap(), diff));
        }

        //InstanceMapScript
        public InstanceScript CreateInstanceData(InstanceMap map)
        {
            Contract.Assert(map != null);

            return RunScriptRet<InstanceMapScript, InstanceScript>(p => p.GetInstanceScript(map), map.GetScriptId(), null);
        }

        //ItemScript
        public bool OnDummyEffect(Unit caster, uint spellId, uint effIndex, Item target)
        {
            Contract.Assert(caster != null);
            Contract.Assert(target != null);

            return RunScriptRet<ItemScript>(p => p.OnDummyEffect(caster, spellId, effIndex, target), target.GetScriptId());
        }
        public bool OnQuestAccept(Player player, Item item, Quest quest)
        {
            Contract.Assert(player != null);
            Contract.Assert(item != null);
            Contract.Assert(quest != null);

            return RunScriptRet<ItemScript>(p => p.OnQuestAccept(player, item, quest), item.GetScriptId());
        }
        public bool OnItemUse(Player player, Item item, SpellCastTargets targets, ObjectGuid castId)
        {
            Contract.Assert(player);
            Contract.Assert(item);

            return RunScriptRet<ItemScript>(p => p.OnUse(player, item, targets, castId), item.GetScriptId());
        }
        public bool OnItemExpire(Player player, ItemTemplate proto)
        {
            Contract.Assert(player);
            Contract.Assert(proto != null);

            return RunScriptRet<ItemScript>(p => p.OnExpire(player, proto), proto.ScriptId);
        }

        //CreatureScript
        public bool OnDummyEffect(Unit caster, uint spellId, uint effIndex, Creature target)
        {
            Contract.Assert(caster);
            Contract.Assert(target);

            return RunScriptRet<CreatureScript>(p => p.OnDummyEffect(caster, spellId, effIndex, target), target.GetScriptId());
        }
        public bool OnGossipHello(Player player, Creature creature)
        {
            Contract.Assert(player);
            Contract.Assert(creature);

            return RunScriptRet<CreatureScript>(p => p.OnGossipHello(player, creature), creature.GetScriptId());
        }
        public bool OnGossipSelect(Player player, Creature creature, uint sender, uint action)
        {
            Contract.Assert(player != null);
            Contract.Assert(creature != null);

            return RunScriptRet<CreatureScript>(p => p.OnGossipSelect(player, creature, sender, action), creature.GetScriptId());
        }
        public bool OnGossipSelectCode(Player player, Creature creature, uint sender, uint action, string code)
        {
            Contract.Assert(player != null);
            Contract.Assert(creature != null);
            Contract.Assert(code != null);

            return RunScriptRet<CreatureScript>(p => p.OnGossipSelectCode(player, creature, sender, action, code), creature.GetScriptId());
        }
        public bool OnQuestAccept(Player player, Creature creature, Quest quest)
        {
            Contract.Assert(player != null);
            Contract.Assert(creature != null);
            Contract.Assert(quest != null);

            return RunScriptRet<CreatureScript>(p => p.OnQuestAccept(player, creature, quest), creature.GetScriptId());
        }
        public bool OnQuestSelect(Player player, Creature creature, Quest quest)
        {
            Contract.Assert(player != null);
            Contract.Assert(creature != null);
            Contract.Assert(quest != null);

            return RunScriptRet<CreatureScript>(p => p.OnQuestSelect(player, creature, quest), creature.GetScriptId());
        }
        public bool OnQuestComplete(Player player, Creature creature, Quest quest)
        {
            Contract.Assert(player != null);
            Contract.Assert(creature != null);
            Contract.Assert(quest != null);

            return RunScriptRet<CreatureScript>(p => p.OnQuestComplete(player, creature, quest), creature.GetScriptId());
        }
        public bool OnQuestReward(Player player, Creature creature, Quest quest, uint opt)
        {
            Contract.Assert(player != null);
            Contract.Assert(creature != null);
            Contract.Assert(quest != null);

            return RunScriptRet<CreatureScript>(p => p.OnQuestReward(player, creature, quest, opt), creature.GetScriptId());
        }
        public uint GetDialogStatus(Player player, Creature creature)
        {
            Contract.Assert(player != null);
            Contract.Assert(creature != null);

            player.PlayerTalkClass.ClearMenus();
            return RunScriptRet<CreatureScript, uint>(p => p.GetDialogStatus(player, creature), creature.GetScriptId(), (uint)QuestGiverStatus.ScriptedNoStatus);
        }
        public bool CanSpawn(ulong spawnId, uint entry, CreatureTemplate actTemplate, CreatureData cData, Map map)
        {
            Contract.Assert(actTemplate != null);

            CreatureTemplate baseTemplate = Global.ObjectMgr.GetCreatureTemplate(entry);
            if (baseTemplate == null)
                baseTemplate = actTemplate;
            return RunScriptRet<CreatureScript, bool>(p => p.CanSpawn(spawnId, entry, baseTemplate, actTemplate, cData, map), cData != null ? cData.ScriptId : baseTemplate.ScriptID, true);
        }
        public CreatureAI GetCreatureAI(Creature creature)
        {
            Contract.Assert(creature != null);

            return RunScriptRet<CreatureScript, CreatureAI>(p => p.GetAI(creature), creature.GetScriptId());
        }
        public void OnCreatureUpdate(Creature creature, uint diff)
        {
            Contract.Assert(creature != null);

            RunScript<CreatureScript>(p => p.OnUpdate(creature, diff), creature.GetScriptId());
        }

        //GameObjectScript
        public bool OnDummyEffect(Unit caster, uint spellId, uint effIndex, GameObject target)
        {
            Contract.Assert(caster != null);
            Contract.Assert(target != null);

            return RunScriptRet<GameObjectScript>(p => p.OnDummyEffect(caster, spellId, effIndex, target), target.GetScriptId());
        }
        public bool OnGossipHello(Player player, GameObject go)
        {
            Contract.Assert(player != null);
            Contract.Assert(go != null);

            player.PlayerTalkClass.ClearMenus();
            return RunScriptRet<GameObjectScript>(p => p.OnGossipHello(player, go), go.GetScriptId());
        }
        public bool OnGossipSelect(Player player, GameObject go, uint sender, uint action)
        {
            Contract.Assert(player != null);
            Contract.Assert(go != null);

            return RunScriptRet<GameObjectScript>(p => p.OnGossipSelect(player, go, sender, action), go.GetScriptId());
        }
        public bool OnGossipSelectCode(Player player, GameObject go, uint sender, uint action, string code)
        {
            Contract.Assert(player != null);
            Contract.Assert(go != null);
            Contract.Assert(code != null);

            return RunScriptRet<GameObjectScript>(p => p.OnGossipSelectCode(player, go, sender, action, code), go.GetScriptId());
        }
        public bool OnQuestAccept(Player player, GameObject go, Quest quest)
        {
            Contract.Assert(player != null);
            Contract.Assert(go != null);
            Contract.Assert(quest != null);

            return RunScriptRet<GameObjectScript>(p => p.OnQuestAccept(player, go, quest), go.GetScriptId());
        }
        public bool OnQuestReward(Player player, GameObject go, Quest quest, uint opt)
        {
            Contract.Assert(player != null);
            Contract.Assert(go != null);
            Contract.Assert(quest != null);

            return RunScriptRet<GameObjectScript>(p => p.OnQuestReward(player, go, quest, opt), go.GetScriptId());
        }
        public uint GetDialogStatus(Player player, GameObject go)
        {
            Contract.Assert(player != null);
            Contract.Assert(go != null);

            return RunScriptRet<GameObjectScript, uint>(p => p.GetDialogStatus(player, go), go.GetScriptId(), (uint)QuestGiverStatus.ScriptedNoStatus);
        }
        public void OnGameObjectDestroyed(GameObject go, Player player)
        {
            Contract.Assert(go != null);

            RunScript<GameObjectScript>(p => p.OnDestroyed(go, player), go.GetScriptId());
        }
        public void OnGameObjectDamaged(GameObject go, Player player)
        {
            Contract.Assert(go != null);

            RunScript<GameObjectScript>(p => p.OnDamaged(go, player), go.GetScriptId());
        }
        public void OnGameObjectLootStateChanged(GameObject go, uint state, Unit unit)
        {
            Contract.Assert(go != null);

            RunScript<GameObjectScript>(p => p.OnLootStateChanged(go, state, unit), go.GetScriptId());
        }
        public void OnGameObjectStateChanged(GameObject go, GameObjectState state)
        {
            Contract.Assert(go != null);

            RunScript<GameObjectScript>(p => p.OnGameObjectStateChanged(go, state), go.GetScriptId());
        }
        public void OnGameObjectUpdate(GameObject go, uint diff)
        {
            Contract.Assert(go != null);

            RunScript<GameObjectScript>(p => p.OnUpdate(go, diff), go.GetScriptId());
        }
        public GameObjectAI GetGameObjectAI(GameObject go)
        {
            Contract.Assert(go != null);

            return RunScriptRet<GameObjectScript, GameObjectAI>(p => p.GetAI(go), go.GetScriptId());
        }

        //AreaTriggerScript
        public bool OnAreaTrigger(Player player, AreaTriggerRecord trigger, bool entered)
        {
            Contract.Assert(player != null);
            Contract.Assert(trigger != null);

            return RunScriptRet<AreaTriggerScript>(p => p.OnTrigger(player, trigger, entered), Global.ObjectMgr.GetAreaTriggerScriptId(trigger.Id));
        }

        //BattlegroundScript
        public Battleground CreateBattleground(BattlegroundTypeId typeId)
        {
            // @todo Implement script-side Battlegrounds.
            Contract.Assert(false);
            return null;
        }

        // OutdoorPvPScript
        public OutdoorPvP CreateOutdoorPvP(OutdoorPvPData data)
        {
            Contract.Assert(data != null);
            return RunScriptRet<OutdoorPvPScript, OutdoorPvP>(p => p.GetOutdoorPvP(), data.ScriptId, null);
        }

        // WeatherScript
        public void OnWeatherChange(Weather weather, WeatherState state, float grade)
        {
            Contract.Assert(weather != null);
            RunScript<WeatherScript>(p => p.OnChange(weather, state, grade), weather.GetScriptId());
        }
        public void OnWeatherUpdate(Weather weather, uint diff)
        {
            Contract.Assert(weather != null);
            RunScript<WeatherScript>(p => p.OnUpdate(weather, diff), weather.GetScriptId());
        }

        // AuctionHouseScript
        public void OnAuctionAdd(AuctionHouseObject ah, AuctionEntry entry)
        {
            Contract.Assert(ah != null);
            Contract.Assert(entry != null);
            ForEach<AuctionHouseScript>(p => p.OnAuctionAdd(ah, entry));
        }
        public void OnAuctionRemove(AuctionHouseObject ah, AuctionEntry entry)
        {
            Contract.Assert(ah != null);
            Contract.Assert(entry != null);
            ForEach<AuctionHouseScript>(p => p.OnAuctionRemove(ah, entry));
        }
        public void OnAuctionSuccessful(AuctionHouseObject ah, AuctionEntry entry)
        {
            Contract.Assert(ah != null);
            Contract.Assert(entry != null);
            ForEach<AuctionHouseScript>(p => p.OnAuctionSuccessful(ah, entry));
        }
        public void OnAuctionExpire(AuctionHouseObject ah, AuctionEntry entry)
        {
            Contract.Assert(ah != null);
            Contract.Assert(entry != null);
            ForEach<AuctionHouseScript>(p => p.OnAuctionExpire(ah, entry));
        }

        // ConditionScript
        public bool OnConditionCheck(Condition condition, ConditionSourceInfo sourceInfo)
        {
            Contract.Assert(condition != null);

            return RunScriptRet<ConditionScript>(p => p.OnConditionCheck(condition, sourceInfo), condition.ScriptId, true);
        }

        // VehicleScript
        public void OnInstall(Vehicle veh)
        {
            Contract.Assert(veh != null);
            Contract.Assert(veh.GetBase().IsTypeId(TypeId.Unit));

            RunScript<VehicleScript>(p => p.OnInstall(veh), veh.GetBase().ToCreature().GetScriptId());
        }
        public void OnUninstall(Vehicle veh)
        {
            Contract.Assert(veh != null);
            Contract.Assert(veh.GetBase().IsTypeId(TypeId.Unit));

            RunScript<VehicleScript>(p => p.OnUninstall(veh), veh.GetBase().ToCreature().GetScriptId());
        }
        public void OnReset(Vehicle veh)
        {
            Contract.Assert(veh != null);
            Contract.Assert(veh.GetBase().IsTypeId(TypeId.Unit));

            RunScript<VehicleScript>(p => p.OnReset(veh), veh.GetBase().ToCreature().GetScriptId());
        }
        public void OnInstallAccessory(Vehicle veh, Creature accessory)
        {
            Contract.Assert(veh != null);
            Contract.Assert(veh.GetBase().IsTypeId(TypeId.Unit));
            Contract.Assert(accessory != null);

            RunScript<VehicleScript>(p => p.OnInstallAccessory(veh, accessory), veh.GetBase().ToCreature().GetScriptId());
        }
        public void OnAddPassenger(Vehicle veh, Unit passenger, sbyte seatId)
        {
            Contract.Assert(veh != null);
            Contract.Assert(veh.GetBase().IsTypeId(TypeId.Unit));
            Contract.Assert(passenger != null);

            RunScript<VehicleScript>(p => p.OnAddPassenger(veh, passenger, seatId), veh.GetBase().ToCreature().GetScriptId());
        }
        public void OnRemovePassenger(Vehicle veh, Unit passenger)
        {
            Contract.Assert(veh != null);
            Contract.Assert(veh.GetBase().IsTypeId(TypeId.Unit));
            Contract.Assert(passenger != null);

            RunScript<VehicleScript>(p => p.OnRemovePassenger(veh, passenger), veh.GetBase().ToCreature().GetScriptId());
        }

        // DynamicObjectScript
        public void OnDynamicObjectUpdate(DynamicObject dynobj, uint diff)
        {
            Contract.Assert(dynobj != null);

            ForEach<DynamicObjectScript>(p => p.OnUpdate(dynobj, diff));
        }

        // TransportScript
        public void OnAddPassenger(Transport transport, Player player)
        {
            Contract.Assert(transport != null);
            Contract.Assert(player != null);

            RunScript<TransportScript>(p => p.OnAddPassenger(transport, player), transport.GetScriptId());
        }
        public void OnAddCreaturePassenger(Transport transport, Creature creature)
        {
            Contract.Assert(transport != null);
            Contract.Assert(creature != null);

            RunScript<TransportScript>(p => p.OnAddCreaturePassenger(transport, creature), transport.GetScriptId());
        }
        public void OnRemovePassenger(Transport transport, Player player)
        {
            Contract.Assert(transport != null);
            Contract.Assert(player != null);

            RunScript<TransportScript>(p => p.OnRemovePassenger(transport, player), transport.GetScriptId());
        }
        public void OnTransportUpdate(Transport transport, uint diff)
        {
            Contract.Assert(transport != null);

            RunScript<TransportScript>(p => p.OnUpdate(transport, diff), transport.GetScriptId());
        }
        public void OnRelocate(Transport transport, uint waypointId, uint mapId, float x, float y, float z)
        {
            RunScript<TransportScript>(p => p.OnRelocate(transport, waypointId, mapId, x, y, z), transport.GetScriptId());
        }

        // AchievementCriteriaScript
        public bool OnCriteriaCheck(uint ScriptId, Player source, Unit target)
        {
            Contract.Assert(source != null);
            // target can be NULL.

            return RunScriptRet<AchievementCriteriaScript>(p => p.OnCheck(source, target), ScriptId);
        }

        // PlayerScript
        public void OnPVPKill(Player killer, Player killed)
        {
            ForEach<PlayerScript>(p => p.OnPVPKill(killer, killed));
        }
        public void OnCreatureKill(Player killer, Creature killed)
        {
            ForEach<PlayerScript>(p => p.OnCreatureKill(killer, killed));
        }
        public void OnPlayerKilledByCreature(Creature killer, Player killed)
        {
            ForEach<PlayerScript>(p => p.OnPlayerKilledByCreature(killer, killed));
        }
        public void OnPlayerLevelChanged(Player player, byte oldLevel)
        {
            ForEach<PlayerScript>(p => p.OnLevelChanged(player, oldLevel));
        }
        public void OnPlayerFreeTalentPointsChanged(Player player, uint newPoints)
        {
            ForEach<PlayerScript>(p => p.OnFreeTalentPointsChanged(player, newPoints));
        }
        public void OnPlayerTalentsReset(Player player, bool noCost)
        {
            ForEach<PlayerScript>(p => p.OnTalentsReset(player, noCost));
        }
        public void OnPlayerMoneyChanged(Player player, long amount)
        {
            ForEach<PlayerScript>(p => p.OnMoneyChanged(player, amount));
        }
        public void OnGivePlayerXP(Player player, uint amount, Unit victim)
        {
            ForEach<PlayerScript>(p => p.OnGiveXP(player, amount, victim));
        }
        public void OnPlayerReputationChange(Player player, uint factionID, int standing, bool incremental)
        {
            ForEach<PlayerScript>(p => p.OnReputationChange(player, factionID, standing, incremental));
        }
        public void OnPlayerDuelRequest(Player target, Player challenger)
        {
            ForEach<PlayerScript>(p => p.OnDuelRequest(target, challenger));
        }
        public void OnPlayerDuelStart(Player player1, Player player2)
        {
            ForEach<PlayerScript>(p => p.OnDuelStart(player1, player2));
        }
        public void OnPlayerDuelEnd(Player winner, Player loser, DuelCompleteType type)
        {
            ForEach<PlayerScript>(p => p.OnDuelEnd(winner, loser, type));
        }
        public void OnPlayerChat(Player player, ChatMsg type, Language lang, string msg)
        {
            ForEach<PlayerScript>(p => p.OnChat(player, type, lang, msg));
        }
        public void OnPlayerChat(Player player, ChatMsg type, Language lang, string msg, Player receiver)
        {
            ForEach<PlayerScript>(p => p.OnChat(player, type, lang, msg, receiver));
        }
        public void OnPlayerChat(Player player, ChatMsg type, Language lang, string msg, Group group)
        {
            ForEach<PlayerScript>(p => p.OnChat(player, type, lang, msg, group));
        }
        public void OnPlayerChat(Player player, ChatMsg type, Language lang, string msg, Guild guild)
        {
            ForEach<PlayerScript>(p => p.OnChat(player, type, lang, msg, guild));
        }
        public void OnPlayerChat(Player player, ChatMsg type, Language lang, string msg, Channel channel)
        {
            ForEach<PlayerScript>(p => p.OnChat(player, type, lang, msg, channel));
        }
        public void OnPlayerClearEmote(Player player)
        {
            ForEach<PlayerScript>(p => p.OnClearEmote(player));
        }
        public void OnPlayerTextEmote(Player player, uint textEmote, uint emoteNum, ObjectGuid guid)
        {
            ForEach<PlayerScript>(p => p.OnTextEmote(player, textEmote, emoteNum, guid));
        }
        public void OnPlayerSpellCast(Player player, Spell spell, bool skipCheck)
        {
            ForEach<PlayerScript>(p => p.OnSpellCast(player, spell, skipCheck));
        }
        public void OnPlayerLogin(Player player)
        {
            ForEach<PlayerScript>(p => p.OnLogin(player));
        }
        public void OnPlayerLogout(Player player)
        {
            ForEach<PlayerScript>(p => p.OnLogout(player));
        }
        public void OnPlayerCreate(Player player)
        {
            ForEach<PlayerScript>(p => p.OnCreate(player));
        }
        public void OnPlayerDelete(ObjectGuid guid)
        {
            ForEach<PlayerScript>(p => p.OnDelete(guid));
        }
        public void OnPlayerSave(Player player)
        {
            ForEach<PlayerScript>(p => p.OnSave(player));
        }
        public void OnPlayerBindToInstance(Player player, Difficulty difficulty, uint mapid, bool permanent, BindExtensionState extendState)
        {
            ForEach<PlayerScript>(p => p.OnBindToInstance(player, difficulty, mapid, permanent, extendState));
        }
        public void OnPlayerUpdateZone(Player player, uint newZone, uint newArea)
        {
            ForEach<PlayerScript>(p => p.OnUpdateZone(player, newZone, newArea));
        }
        public void OnQuestStatusChange(Player player, uint questId)
        {
            ForEach<PlayerScript>(p => p.OnQuestStatusChange(player, questId));
        }
        public void OnMovieComplete(Player player, uint movieId)
        {
            ForEach<PlayerScript>(p => p.OnMovieComplete(player, movieId));
        }

        // GuildScript
        public void OnGuildAddMember(Guild guild, Player player, byte plRank)
        {
            ForEach<GuildScript>(p => p.OnAddMember(guild, player, plRank));
        }
        public void OnGuildRemoveMember(Guild guild, Player player, bool isDisbanding, bool isKicked)
        {
            ForEach<GuildScript>(p => p.OnRemoveMember(guild, player, isDisbanding, isKicked));
        }
        public void OnGuildMOTDChanged(Guild guild, string newMotd)
        {
            ForEach<GuildScript>(p => p.OnMOTDChanged(guild, newMotd));
        }
        public void OnGuildInfoChanged(Guild guild, string newInfo)
        {
            ForEach<GuildScript>(p => p.OnInfoChanged(guild, newInfo));
        }
        public void OnGuildCreate(Guild guild, Player leader, string name)
        {
            ForEach<GuildScript>(p => p.OnCreate(guild, leader, name));
        }
        public void OnGuildDisband(Guild guild)
        {
            ForEach<GuildScript>(p => p.OnDisband(guild));
        }
        public void OnGuildMemberWitdrawMoney(Guild guild, Player player, ulong amount, bool isRepair)
        {
            ForEach<GuildScript>(p => p.OnMemberWitdrawMoney(guild, player, amount, isRepair));
        }
        public void OnGuildMemberDepositMoney(Guild guild, Player player, ulong amount)
        {
            ForEach<GuildScript>(p => p.OnMemberDepositMoney(guild, player, amount));
        }
        public void OnGuildItemMove(Guild guild, Player player, Item pItem, bool isSrcBank, byte srcContainer, byte srcSlotId, bool isDestBank, byte destContainer, byte destSlotId)
        {
            ForEach<GuildScript>(p => p.OnItemMove(guild, player, pItem, isSrcBank, srcContainer, srcSlotId, isDestBank, destContainer, destSlotId));
        }
        public void OnGuildEvent(Guild guild, byte eventType, ulong playerGuid1, ulong playerGuid2, byte newRank)
        {
            ForEach<GuildScript>(p => p.OnEvent(guild, eventType, playerGuid1, playerGuid2, newRank));
        }
        public void OnGuildBankEvent(Guild guild, byte eventType, byte tabId, ulong playerGuid, uint itemOrMoney, ushort itemStackCount, byte destTabId)
        {
            ForEach<GuildScript>(p => p.OnBankEvent(guild, eventType, tabId, playerGuid, itemOrMoney, itemStackCount, destTabId));
        }

        // GroupScript
        public void OnGroupAddMember(Group group, ObjectGuid guid)
        {
            Contract.Assert(group);
            ForEach<GroupScript>(p => p.OnAddMember(group, guid));
        }
        public void OnGroupInviteMember(Group group, ObjectGuid guid)
        {
            Contract.Assert(group);
            ForEach<GroupScript>(p => p.OnInviteMember(group, guid));
        }
        public void OnGroupRemoveMember(Group group, ObjectGuid guid, RemoveMethod method, ObjectGuid kicker, string reason)
        {
            Contract.Assert(group);
            ForEach<GroupScript>(p => p.OnRemoveMember(group, guid, method, kicker, reason));
        }
        public void OnGroupChangeLeader(Group group, ObjectGuid newLeaderGuid, ObjectGuid oldLeaderGuid)
        {
            Contract.Assert(group);
            ForEach<GroupScript>(p => p.OnChangeLeader(group, newLeaderGuid, oldLeaderGuid));
        }
        public void OnGroupDisband(Group group)
        {
            Contract.Assert(group);
            ForEach<GroupScript>(p => p.OnDisband(group));
        }

        // UnitScript
        public void OnHeal(Unit healer, Unit reciever, ref uint gain)
        {
            uint dmg = gain;
            ForEach<UnitScript>(p => p.OnHeal(healer, reciever, ref dmg));
            gain = dmg;
        }
        public void OnDamage(Unit attacker, Unit victim, ref uint damage)
        {
            uint dmg = damage;
            ForEach<UnitScript>(p => p.OnDamage(attacker, victim, ref dmg));
            damage = dmg;
        }
        public void ModifyPeriodicDamageAurasTick(Unit target, Unit attacker, ref uint damage)
        {
            uint dmg = damage;
            ForEach<UnitScript>(p => p.ModifyPeriodicDamageAurasTick(target, attacker, ref dmg));
            damage = dmg;
        }
        public void ModifyMeleeDamage(Unit target, Unit attacker, ref uint damage)
        {
            uint dmg = damage;
            ForEach<UnitScript>(p => p.ModifyMeleeDamage(target, attacker, ref dmg));
            damage = dmg;
        }
        public void ModifySpellDamageTaken(Unit target, Unit attacker, ref int damage)
        {
            int dmg = damage;
            ForEach<UnitScript>(p => p.ModifySpellDamageTaken(target, attacker, ref dmg));
            damage = dmg;
        }

        // AreaTriggerEntityScript
        public AreaTriggerAI GetAreaTriggerAI(AreaTrigger areaTrigger)
        {
            Contract.Assert(areaTrigger);

            return RunScriptRet<AreaTriggerEntityScript, AreaTriggerAI>(p => p.GetAI(areaTrigger), areaTrigger.GetScriptId(), null);
        }

        //SceneScript
        public void OnSceneStart(Player player, uint sceneInstanceID, SceneTemplate sceneTemplate)
        {
            Contract.Assert(player);
            Contract.Assert(sceneTemplate != null);

            RunScript<SceneScript>(script => script.OnSceneStart(player, sceneInstanceID, sceneTemplate), sceneTemplate.ScriptId);
        }
        public void OnSceneTrigger(Player player, uint sceneInstanceID, SceneTemplate sceneTemplate, string triggerName)
        {
            Contract.Assert(player);
            Contract.Assert(sceneTemplate != null);

            RunScript<SceneScript>(script => script.OnSceneTriggerEvent(player, sceneInstanceID, sceneTemplate, triggerName), sceneTemplate.ScriptId);
        }
        public void OnSceneCancel(Player player, uint sceneInstanceID, SceneTemplate sceneTemplate)
        {
            Contract.Assert(player);
            Contract.Assert(sceneTemplate != null);

            RunScript<SceneScript>(script => script.OnSceneCancel(player, sceneInstanceID, sceneTemplate), sceneTemplate.ScriptId);
        }
        public void OnSceneComplete(Player player, uint sceneInstanceID, SceneTemplate sceneTemplate)
        {
            Contract.Assert(player);
            Contract.Assert(sceneTemplate != null);

            RunScript<SceneScript>(script => script.OnSceneComplete(player, sceneInstanceID, sceneTemplate), sceneTemplate.ScriptId);
        }

        public void ForEach<T>(Action<T> a) where T : ScriptObject
        {
            var reg = GetScriptRegistry<T>();
            if (reg == null || reg.Empty())
                return;

            foreach (var script in reg.GetStorage())
                a.Invoke(script);
        }
        public bool RunScriptRet<T>(Func<T, bool> func, uint id, bool ret = false) where T : ScriptObject
        {
            return RunScriptRet<T, bool>(func, id, ret);
        }
        public U RunScriptRet<T, U>(Func<T, U> func, uint id, U ret = default(U)) where T : ScriptObject
        {
            var reg = GetScriptRegistry<T>();
            if (reg == null || reg.Empty())
                return ret;

            var script = reg.GetScriptById(id);
            if (script == null)
                return ret;

            return func.Invoke(script);
        }
        public void RunScript<T>(Action<T> a, uint id) where T : ScriptObject
        {
            var reg = GetScriptRegistry<T>();
            if (reg == null || reg.Empty())
                return;

            var script = reg.GetScriptById(id);
            if (script != null)
                a.Invoke(script);
        }
        public void AddScript<T>(T script) where T : ScriptObject
        {
            Contract.Assert(script != null);

            if (!ScriptStorage.ContainsKey(typeof(T)))
                ScriptStorage[typeof(T)] = new ScriptRegistry<T>();

            GetScriptRegistry<T>().AddScript(script);
        }

        public List<ScriptPointMove> GetPointMoveList(uint creatureEntry)
        {
            return m_mPointMoveMap.LookupByKey(creatureEntry);
        }

        ScriptRegistry<T> GetScriptRegistry<T>() where T : ScriptObject
        {
            if (ScriptStorage.ContainsKey(typeof(T)))
                return (ScriptRegistry<T>)ScriptStorage[typeof(T)];

            return null;
        }

        uint _ScriptCount;
        public Dictionary<uint, SpellSummary> spellSummaryStorage = new Dictionary<uint, SpellSummary>();
        Hashtable ScriptStorage = new Hashtable();

        MultiMap<uint, ScriptPointMove> m_mPointMoveMap = new MultiMap<uint, ScriptPointMove>();

             // creature entry + chain ID
        MultiMap<Tuple<uint, ushort>, SplineChainLink> m_mSplineChainsMap = new MultiMap<Tuple<uint, ushort>, SplineChainLink>(); // spline chains
    }

    public class ScriptRegistry<TValue> where TValue : ScriptObject
    {
        public void AddScript(TValue script)
        {
            Contract.Assert(script != null);

            if (!script.IsDatabaseBound())
            {
                // We're dealing with a code-only script; just add it.
                ScriptMap[_scriptIdCounter++] = script;
                Global.ScriptMgr.IncrementScriptCount();
                return;
            }

            // Get an ID for the script. An ID only exists if it's a script that is assigned in the database
            // through a script name (or similar).
            uint id = Global.ObjectMgr.GetScriptId(script.GetName());
            if (id != 0)
            {
                // Try to find an existing script.
                bool existing = false;
                foreach (var it in ScriptMap)
                {
                    if (it.Value.GetName() == script.GetName())
                    {
                        existing = true;
                        break;
                    }
                }

                // If the script isn't assigned . assign it!
                if (!existing)
                {
                    ScriptMap[id] = script;
                    Global.ScriptMgr.IncrementScriptCount();
                }
                else
                {
                    // If the script is already assigned . delete it!
                    Log.outError(LogFilter.Scripts, "Script '{0}' already assigned with the same script name, so the script can't work.", script.GetName());

                    Contract.Assert(false); // Error that should be fixed ASAP.
                }
            }
            else
            {
                // The script uses a script name from database, but isn't assigned to anything.
                Log.outError(LogFilter.Sql, "Script named '{0}' does not have a script name assigned in database.", script.GetName());
                return;
            }
        }

        // Gets a script by its ID (assigned by ObjectMgr).
        public TValue GetScriptById(uint id)
        {
            return ScriptMap.LookupByKey(id);
        }

        public bool Empty()
        {
            return ScriptMap.Empty();
        }

        public List<TValue> GetStorage()
        {
            return ScriptMap.Values.ToList();
        }

        public void Unload()
        {
            foreach (var key in ScriptMap.Keys)
                ScriptMap.Remove(key);
        }

        // Counter used for code-only scripts.
        uint _scriptIdCounter;
        Dictionary<uint, TValue> ScriptMap = new Dictionary<uint, TValue>();
    }

    public class ScriptPointMove
    {
        public uint uiCreatureEntry;
        public uint uiPointId;
        public float fX;
        public float fY;
        public float fZ;
        public uint uiWaitTime;
    }

    public class SpellSummary
    {
        public byte Targets;                                          // set of enum SelectTarget
        public byte Effects;                                          // set of enum SelectEffect
    }

    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
    public class ScriptAttribute : Attribute
    {
        public ScriptAttribute(params object[] args)
        {
            Args = args;
        }

        public object[] Args { get; private set; }
    }
}
