/// <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 })
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); } } }
/// <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); }
// 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); }
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); }