csharp/ACEmulator/ACE/Source/ACE.Server/Entity/Mutations/MutationCache.cs

MutationCache.cs
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;

using log4net;

using ACE.Ensaty.Enum;
using ACE.Ensaty.Enum.Properties;

namespace ACE.Server.Ensaty.Mutations
{
    public static clast MutationCache
    {
        private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

        private static readonly ConcurrentDictionary tSysMutationFilters = new ConcurrentDictionary();

        static MutationCache()
        {
            CacheResourceNames();
        }

        /// 
        /// For lootgen -- custom filenames
        /// 
        public static MutationFilter GetMutation(string filename)
        {
            if (!tSysMutationFilters.TryGetValue(filename, out var tSysMutationFilter))
            {
                tSysMutationFilter = BuildMutation(filename);

                if (tSysMutationFilter != null)
                    tSysMutationFilters.TryAdd(filename, tSysMutationFilter);
            }
            return tSysMutationFilter;
        }

        /// 
        /// For recipes -- mutation script id + custom filename
        /// 
        public static MutationFilter GetMutation(uint mutationId)
        {
            var mutationId_str = mutationId.ToString();

            if (!tSysMutationFilters.TryGetValue(mutationId_str, out var tSysMutationFilter))
            {
                tSysMutationFilter = BuildMutation(mutationId);

                if (tSysMutationFilter != null)
                    tSysMutationFilters.TryAdd(mutationId_str, tSysMutationFilter);
            }
            return tSysMutationFilter;
        }

        private static MutationFilter BuildMutation(uint mutationId)
        {
            if (!mutationIdToFilename.TryGetValue(mutationId, out var filename))
            {
                log.Error($"MutationCache.BuildMutation({mutationId:X8}) - embedded resource not found");
                return null;
            }

            return BuildMutation(filename);
        }

        private static MutationFilter BuildMutation(string filename)
        {
            var lines = ReadScript(filename);

            if (lines == null)
            {
                log.Error($"MutationCache.BuildMutation({filename}) - embedded resource not found");
                return null;
            }

            string prevMutationLine = null;
            string mutationLine = null;

            var mutationFilter = new MutationFilter();
            Mutation mutation = null;
            MutationOutcome outcome = null;
            EffectList effectList = null;

            var totalChance = 0.0M;

            var timer = Stopwatch.StartNew();

            foreach (var _line in lines)
            {
                var line = _line;

                var commentIdx = line.IndexOf("//");

                if (commentIdx != -1)
                    line = line.Substring(0, commentIdx);

                if (line.Contains("Mutation #", StringComparison.OrdinalIgnoreCase))
                {
                    prevMutationLine = mutationLine;
                    mutationLine = line;
                    continue;
                }

                if (line.Contains("Tier chances", StringComparison.OrdinalIgnoreCase))
                {
                    if (outcome != null && outcome.EffectLists.Last().Chance != 1.0f)
                        log.Error($"MutationCache.BuildMutation({filename}) - {prevMutationLine} total {outcome.EffectLists.Last().Chance}, expected 1.0");

                    mutation = new Mutation();
                    mutationFilter.Mutations.Add(mutation);

                    var tierPieces = line.Split(',');

                    foreach (var tierPiece in tierPieces)
                    {
                        var match = Regex.Match(tierPiece, @"([\d.]+)");
                        if (match.Success && float.TryParse(match.Groups[1].Value, out var tierChance))
                        {
                            mutation.Chances.Add(tierChance);
                        }
                        else
                        {
                            log.Error($"MutationCache.BuildMutation({filename}) - couldn't parse tier chances for {mutationLine}: {tierPiece}");
                            mutation.Chances.Add(0.0f);
                        }
                    }

                    outcome = new MutationOutcome();
                    mutation.Outcomes.Add(outcome);

                    totalChance = 0.0M;

                    continue;
                }

                if (line.Contains("- Chance", StringComparison.OrdinalIgnoreCase))
                {
                    if (totalChance >= 1.0M)
                    {
                        if (totalChance > 1.0M)
                            log.Error($"MutationCache.BuildMutation({filename}) - {mutationLine} total {totalChance}, expected 1.0");

                        outcome = new MutationOutcome();
                        mutation.Outcomes.Add(outcome);

                        totalChance = 0.0M;
                    }

                    effectList = new EffectList();
                    outcome.EffectLists.Add(effectList);

                    var match = Regex.Match(line, @"([\d.]+)");
                    if (match.Success && decimal.TryParse(match.Groups[1].Value, out var chance))
                    {
                        totalChance += chance / 100;

                        effectList.Chance = (float)totalChance;
                    }
                    else
                    {
                        log.Error($"MutationCache.BuildMutation({filename}) - couldn't parse {line} for {mutationLine}");
                    }
                    continue;
                }

                if (!line.Contains("="))
                    continue;

                var effect = new Effect();

                effect.Type = GetMutationEffectType(line);

                var firstOperator = GetFirstOperator(effect.Type);

                var pieces = line.Split(firstOperator, 2);

                if (pieces.Length != 2)
                {
                    log.Error($"MutationCache.BuildMutation({filename}) - couldn't parse {line}");
                    continue;
                }

                pieces[0] = pieces[0].Trim();
                pieces[1] = pieces[1].Trim();

                var firstStatType = GetStatType(pieces[0]);

                /*if (firstStatType == StatType.Undef)
                {
                    log.Error($"MutationCache.BuildMutation({filename}) - couldn't determine StatType for {pieces[0]} in {line}");
                    continue;
                }*/

                effect.Quality = ParseEffectArgument(filename, effect, pieces[0]);

                var hastecondOperator = HastecondOperator(effect.Type);

                if (!hastecondOperator)
                {
                    effect.Arg1 = ParseEffectArgument(filename, effect, pieces[1]);
                }
                else
                {
                    var secondOperator = GetSecondOperator(effect.Type);

                    var subpieces = pieces[1].Split(secondOperator, 2);

                    if (subpieces.Length != 2)
                    {
                        log.Error($"MutationCache.BuildMutation({filename}) - couldn't parse {line}");
                        continue;
                    }

                    subpieces[0] = subpieces[0].Trim();
                    subpieces[1] = subpieces[1].Trim();

                    effect.Arg1 = ParseEffectArgument(filename, effect, subpieces[0]);
                    effect.Arg2 = ParseEffectArgument(filename, effect, subpieces[1]);
                }

                effectList.Effects.Add(effect);
            }

            if (outcome != null && outcome.EffectLists.Last().Chance != 1.0f)
                log.Error($"MutationCache.BuildMutation({filename}) - {mutationLine} total {outcome.EffectLists.Last().Chance}, expected 1.0");

            timer.Stop();

            // scripts take about ~2ms to compile
            //Console.WriteLine($"Compiled {filename} in {timer.Elapsed.TotalMilliseconds}ms");

            return mutationFilter;
        }

        private static EffectArgument ParseEffectArgument(string filename, Effect effect, string operand)
        {
            var effectArgument = new EffectArgument();

            effectArgument.Type = GetEffectArgumentType(effect, operand);

            switch (effectArgument.Type)
            {
                case EffectArgumentType.Int:

                    if (!int.TryParse(operand, out effectArgument.IntVal))
                    {
                        if (effect.Quality != null && effect.Quality.StatType == StatType.Int && effect.Quality.StatIdx == (int)PropertyInt.ImbuedEffect && Enum.TryParse(operand, out ImbuedEffectType imbuedEffectType))
                            effectArgument.IntVal = (int)imbuedEffectType;
                        else if (Enum.TryParse(operand, out WieldRequirement wieldRequirement))
                            effectArgument.IntVal = (int)wieldRequirement;
                        else if (Enum.TryParse(operand, out Skill skill))
                            effectArgument.IntVal = (int)skill;
                        else
                            log.Error($"MutationCache.BuildMutation({filename}) - couldn't parse IntVal {operand}");
                    }
                    break;

                case EffectArgumentType.Int64:

                    if (!long.TryParse(operand, out effectArgument.LongVal))
                    {
                        log.Error($"MutationCache.BuildMutation({filename}) - couldn't parse Int64Val {operand}");
                    }
                    break;

                case EffectArgumentType.Double:

                    if (!double.TryParse(operand, out effectArgument.DoubleVal))
                    {
                        log.Error($"MutationCache.BuildMutation({filename}) - couldn't parse DoubleVal {operand}");
                    }
                    break;

                case EffectArgumentType.Quality:

                    effectArgument.StatType = GetStatType(operand);

                    switch (effectArgument.StatType)
                    {
                        case StatType.Int:

                            if (Enum.TryParse(operand, out PropertyInt propInt))
                                effectArgument.StatIdx = (int)propInt;
                            else
                                log.Error($"MutationCache.BuildMutation({filename}) - couldn't parse PropertyInt.{operand}");
                            break;

                        case StatType.Int64:

                            if (Enum.TryParse(operand, out PropertyInt64 propInt64))
                                effectArgument.StatIdx = (int)propInt64;
                            else
                                log.Error($"MutationCache.BuildMutation({filename}) - couldn't parse PropertyInt64.{operand}");
                            break;

                        case StatType.Float:

                            if (Enum.TryParse(operand, out PropertyFloat propFloat))
                                effectArgument.StatIdx = (int)propFloat;
                            else
                                log.Error($"MutationCache.BuildMutation({filename}) - couldn't parse PropertyFloat.{operand}");
                            break;

                        case StatType.Bool:

                            if (Enum.TryParse(operand, out PropertyBool propBool))
                                effectArgument.StatIdx = (int)propBool;
                            else
                                log.Error($"MutationCache.BuildMutation({filename}) - couldn't parse PropertyBool.{operand}");
                            break;

                        case StatType.DataID:

                            if (Enum.TryParse(operand, out PropertyDataId propDID))
                                effectArgument.StatIdx = (int)propDID;
                            else
                                log.Error($"MutationCache.BuildMutation({filename}) - couldn't parse PropertyBool.{operand}");
                            break;

                        default:
                            log.Error($"MutationCache.BuildMutation({filename}) - unknown PropertyType.{operand}");
                            break;
                    }
                    break;

                case EffectArgumentType.Random:

                    var match = Regex.Match(operand, @"Random\(([\d.-]+), ([\d.-]+)\)");

                    if (!match.Success || !float.TryParse(match.Groups[1].Value, out effectArgument.MinVal) || !float.TryParse(match.Groups[2].Value, out effectArgument.MaxVal))
                        log.Error($"MutationCache.BuildMutation({filename}) - couldn't parse {operand}");

                    break;

                case EffectArgumentType.Variable:

                    match = Regex.Match(operand, @"\[(\d+)\]");

                    if (!match.Success || !int.TryParse(match.Groups[1].Value, out effectArgument.IntVal))
                        log.Error($"MutationCache.BuildMutation({filename}) - couldn't parse {operand}");

                    break;

                default:
                    log.Error($"MutationCache.BuildMutation({filename}) - unknown EffectArgumentType from {operand}");
                    break;
            }
            return effectArgument;
        }

        private static MutationEffectType GetMutationEffectType(string line)
        {
            if (line.Contains("+="))
            {
                if (line.Contains("*"))
                    return MutationEffectType.AddMultiply;
                else if (line.Contains("/"))
                    return MutationEffectType.AddDivide;
                else
                    return MutationEffectType.Add;
            }
            else if (line.Contains("-="))
            {
                if (line.Contains("*"))
                    return MutationEffectType.SubtractMultiply;
                else if (line.Contains("/"))
                    return MutationEffectType.SubtractDivide;
                else
                    return MutationEffectType.Subtract;
            }
            else if (line.Contains("*="))
                return MutationEffectType.Multiply;
            else if (line.Contains("/="))
                return MutationEffectType.Divide;
            else if (line.Contains("+"))
                return MutationEffectType.astignAdd;
            else if (line.Contains(" - "))
                return MutationEffectType.astignSubtract;
            else if (line.Contains("*"))
                return MutationEffectType.astignMultiply;
            else if (line.Contains("/"))
                return MutationEffectType.astignDivide;
            else if (line.Contains("(>="))
                return MutationEffectType.AtLeastAdd;
            else if (line.Contains("(=";
                case MutationEffectType.AtMostSubtract:
                    return "(