Beispiel #1
0
        /// <summary>
        /// Resolves a template by either loading it from a file or by picking a sub-template from the current context.
        /// Logs a warning and returns null if the specified template could not be resolved.
        /// </summary>
        private MapTemplate ResolveTemplate(string mapPath, string templateName, InstantiationContext context)
        {
            if (mapPath != null)
            {
                Logger.Verbose($"Resolving map template '{mapPath}'.");

                // We support both absolute and relative paths. Relative paths are relative to the map that a template is being inserted into.
                if (!Path.IsPathRooted(mapPath))
                {
                    mapPath = Path.Combine(context.CurrentWorkingDirectory, mapPath);
                }

                try
                {
                    // TODO: If no extension is specified, use a certain preferential order (.rmf, .map, ...)? ...
                    return(GetMapTemplate(mapPath, context.Globals));
                }
                catch (Exception ex)
                {
                    Logger.Warning($"Failed to load map template '{mapPath}':", ex);
                    return(null);
                }
            }
            else if (templateName != null)
            {
                Logger.Verbose($"Resolving sub-template '{templateName}'.");

                // We'll look for sub-templates in the closest parent context whose template has been loaded from a map file.
                // If there are multiple matches, we'll pick one at random. If there are no matches, we'll fall through and return null.
                var matchingSubTemplates = context.SubTemplates
                                           .Where(subTemplate => context.EvaluateInterpolatedString(subTemplate.Name) == templateName)
                                           .Select(subTemplate => new {
                    SubTemplate = subTemplate,
                    Weight      = double.TryParse(context.EvaluateInterpolatedString(subTemplate.SelectionWeightExpression), NumberStyles.Float, CultureInfo.InvariantCulture, out var weight) ? weight : 0
                })
Beispiel #2
0
        public InstantiationContext(
            MapTemplate template,
            ILogger logger,
            Transform transform = null,
            IDictionary <string, string> insertionEntityProperties = null,
            InstantiationContext parentContext   = null,
            string workingDirectory              = null,
            IDictionary <string, object> globals = null,
            int sequenceNumber = 0)
        {
            // Every context uses its own PRNG. Seeding is done automatically, but can be done explicitly
            // by adding a 'random_seed' attribute to the inserting entity (or to the map properties, for the root context).
            // NOTE: even with explicit seeding, a random value is always obtained from the parent context.
            //       This ensures that switching between explicit and implicit seeding does not result in 'sibling' contexts
            //       getting different seed values.
            var randomSeed = parentContext?._random.Next() ?? 0;

            if (insertionEntityProperties?.GetNumericProperty(Attributes.RandomSeed) is double seed)
            {
                randomSeed = (int)seed;
            }
            _random = new Random(randomSeed);
            _logger = logger;
            _insertionEntityProperties = insertionEntityProperties;
            _parentContext             = parentContext;

            ID                      = GetRootContext()._nextID++;
            SequenceNumber          = sequenceNumber;
            RecursionDepth          = (parentContext?.RecursionDepth ?? -1) + 1;
            Template                = template;
            CurrentWorkingDirectory = workingDirectory ?? Path.GetDirectoryName(GetNearestMapFileContext().Template.Name);
            SubTemplates            = GetNearestMapFileContext().Template.SubTemplates;

            Transform = transform ?? Transform.Identity;
            Globals   = globals ?? parentContext?.Globals ?? new Dictionary <string, object>();

            var outerEvaluationContext      = Evaluation.ContextFromProperties(insertionEntityProperties, ID, SequenceNumber, _random, Globals, _logger);
            var evaluatedTemplateProperties = template.Map.Properties.ToDictionary(
                kv => Evaluation.EvaluateInterpolatedString(kv.Key, outerEvaluationContext),
                kv => PropertyExtensions.ParseProperty(Evaluation.EvaluateInterpolatedString(kv.Value, outerEvaluationContext)));

            _evaluationContext = new EvaluationContext(evaluatedTemplateProperties, outerEvaluationContext);

            // Every instantiation is written to the same map, but with a different transform:
            OutputMap = parentContext?.OutputMap;
            if (OutputMap == null)
            {
                // Copy original map properties:
                OutputMap = new Map();
                foreach (var kv in evaluatedTemplateProperties)
                {
                    OutputMap.Properties[kv.Key] = Interpreter.Print(kv.Value);
                }
            }
        }
Beispiel #3
0
        /// <summary>
        /// Loads the specified map file, expands any macro entities within, and returns the resulting map.
        /// The given path must be absolute.
        /// </summary>
        public static Map ExpandMacros(string path, ExpansionSettings settings, Logger logger)
        {
            // TODO: Verify that 'path' is absolute! Either that, or document the behavior for relative paths! (relative to cwd?)

            var globals      = new Dictionary <string, object>();
            var expander     = new MacroExpander(settings, logger);
            var mainTemplate = expander.GetMapTemplate(path, globals);

            var context = new InstantiationContext(
                mainTemplate,
                insertionEntityProperties: settings.Variables.ToDictionary(kv => kv.Key, kv => Interpreter.Print(kv.Value)),
                workingDirectory: settings.Directory,
                globals: globals);

            expander.CreateInstance(context);

            return(context.OutputMap);
        }
Beispiel #4
0
        // TODO: If 'angles' and 'scale' are missing, that could cause issues...? But what if we always insert them,
        //       could that also be problematic in different situations?
        /// <summary>
        /// Creates a copy of this entity. The copy is scaled, rotated and translated,
        /// and by default any expressions in its properties will be evaluated.
        /// If it contains 'angles' and 'scale' properties, then these will be updated according to how the entity has been transformed.
        /// Ignores VIS-group and group relationships.
        /// </summary>
        public static Entity Copy(this Entity entity, InstantiationContext context, bool applyTransform = true, bool evaluateExpressions = true)
        {
            var transform = applyTransform ? context.Transform : Transform.Identity;
            var copy      = new Entity(entity.Brushes.Select(brush => brush.Copy(transform)));

            if (evaluateExpressions)
            {
                foreach (var kv in entity.Properties)
                {
                    copy.Properties[context.EvaluateInterpolatedString(kv.Key)] = context.EvaluateInterpolatedString(kv.Value);
                }

                UpdateSpawnFlags(copy);
            }
            else
            {
                foreach (var kv in entity.Properties)
                {
                    copy.Properties[kv.Key] = kv.Value;
                }
            }

            if (applyTransform)
            {
                // TODO: Also check whether maybe the angles/scale keys do exist, but contain invalid values!
                if (copy.Angles is Angles angles)
                {
                    copy.Angles = (angles.ToMatrix() * context.Transform.Rotation).ToAngles();
                }

                if (copy.Scale is double scale)
                {
                    copy.Scale = scale * context.Transform.Scale;
                }

                if (copy.IsPointBased)
                {
                    copy.Origin = ApplyTransform(copy.Origin, context.Transform);
                }
            }

            return(copy);
        }
Beispiel #5
0
        private InstantiationContext(InstantiationContext parentContext, int sequenceNumber)
        {
            _random = parentContext._random;
            _logger = parentContext._logger;
            _insertionEntityProperties = parentContext._insertionEntityProperties;
            _parentContext             = parentContext;

            ID                      = parentContext.ID;
            SequenceNumber          = sequenceNumber;
            RecursionDepth          = parentContext.RecursionDepth;
            Template                = parentContext.Template;
            CurrentWorkingDirectory = parentContext.CurrentWorkingDirectory;
            SubTemplates            = parentContext.SubTemplates;

            Transform = parentContext.Transform;
            Globals   = parentContext.Globals;

            OutputMap = parentContext.OutputMap;

            _evaluationContext = Evaluation.ContextFromProperties(_insertionEntityProperties, ID, SequenceNumber, _random, Globals, _logger, parentContext._evaluationContext);
        }