Fork from OpenRA/OpenRA with one-click launch script (start-ra.cmd) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
323 lines
8.9 KiB
C#
323 lines
8.9 KiB
C#
#region Copyright & License Information
|
|
/*
|
|
* Copyright (c) The OpenRA Developers and Contributors
|
|
* This file is part of OpenRA, which is free software. It is made
|
|
* available to you 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. For more
|
|
* information, see COPYING.
|
|
*/
|
|
#endregion
|
|
|
|
using System;
|
|
using System.Collections.Frozen;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Linq;
|
|
using OpenRA.Mods.Common.Traits;
|
|
using OpenRA.Primitives;
|
|
using OpenRA.Support;
|
|
using OpenRA.Traits;
|
|
|
|
namespace OpenRA.Mods.Common.MapGenerator
|
|
{
|
|
public abstract class MapGeneratorOption
|
|
{
|
|
[FieldLoader.Ignore]
|
|
public readonly string Id;
|
|
public readonly string Label = null;
|
|
public readonly int Priority = 0;
|
|
|
|
protected MapGeneratorOption(string id, MiniYaml yaml)
|
|
{
|
|
Id = id;
|
|
FieldLoader.Load(this, yaml);
|
|
}
|
|
|
|
public abstract IReadOnlyCollection<MiniYamlNode> GetSettings(ITerrainInfo terrainInfo, int playerCount);
|
|
|
|
public virtual IEnumerable<string> GetFluentReferences()
|
|
{
|
|
if (Label != null)
|
|
yield return Label;
|
|
}
|
|
}
|
|
|
|
public class MapGeneratorBooleanOption : MapGeneratorOption
|
|
{
|
|
[FieldLoader.Require]
|
|
public readonly string Parameter = null;
|
|
public readonly bool Default = false;
|
|
public bool Value;
|
|
|
|
public MapGeneratorBooleanOption(string id, MiniYaml yaml)
|
|
: base(id, yaml)
|
|
{
|
|
Value = Default;
|
|
}
|
|
|
|
public override IReadOnlyCollection<MiniYamlNode> GetSettings(ITerrainInfo terrainInfo, int playerCount)
|
|
{
|
|
return [new MiniYamlNode(Parameter, FieldSaver.FormatValue(Value))];
|
|
}
|
|
}
|
|
|
|
public class MapGeneratorIntegerOption : MapGeneratorOption
|
|
{
|
|
[FieldLoader.Require]
|
|
public readonly string Parameter = null;
|
|
public readonly int Default = 0;
|
|
public int Value;
|
|
|
|
public MapGeneratorIntegerOption(string id, MiniYaml yaml)
|
|
: base(id, yaml)
|
|
{
|
|
Value = Default;
|
|
}
|
|
|
|
public override IReadOnlyCollection<MiniYamlNode> GetSettings(ITerrainInfo terrainInfo, int playerCount)
|
|
{
|
|
return [new MiniYamlNode(Parameter, FieldSaver.FormatValue(Value))];
|
|
}
|
|
}
|
|
|
|
public class MapGeneratorMultiChoiceOption : MapGeneratorOption
|
|
{
|
|
public class MapGeneratorDropdownChoice
|
|
{
|
|
public readonly string Label = null;
|
|
public readonly ImmutableArray<string> Tileset = default;
|
|
public readonly ImmutableArray<int> Players = default;
|
|
|
|
[FieldLoader.LoadUsing(nameof(LoadSettings))]
|
|
[FieldLoader.Require]
|
|
public readonly ImmutableArray<MiniYamlNode> Settings = default;
|
|
|
|
static object LoadSettings(MiniYaml yaml)
|
|
{
|
|
return yaml.NodeWithKey("Settings").Value.Nodes.ToImmutableArray();
|
|
}
|
|
}
|
|
|
|
[FieldLoader.LoadUsing(nameof(LoadChoices))]
|
|
public readonly Dictionary<string, MapGeneratorDropdownChoice> Choices = null;
|
|
|
|
static Dictionary<string, MapGeneratorDropdownChoice> LoadChoices(MiniYaml yaml)
|
|
{
|
|
var ret = new Dictionary<string, MapGeneratorDropdownChoice>();
|
|
foreach (var node in yaml.Nodes)
|
|
{
|
|
var split = node.Key.Split('@');
|
|
if (split.Length == 2 && split[0] == "Choice")
|
|
ret.Add(split[1], FieldLoader.Load<MapGeneratorDropdownChoice>(node.Value));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
public readonly ImmutableArray<string> Default = default;
|
|
string value = null;
|
|
|
|
public MapGeneratorMultiChoiceOption(string id, MiniYaml yaml)
|
|
: base(id, yaml) { }
|
|
|
|
public override IReadOnlyCollection<MiniYamlNode> GetSettings(ITerrainInfo terrainInfo, int playerCount)
|
|
{
|
|
var validChoices = ValidChoices(terrainInfo, playerCount);
|
|
if (validChoices.Contains(value))
|
|
return Choices[value].Settings;
|
|
|
|
string fallback = null;
|
|
if (Default != null)
|
|
fallback = Default.FirstOrDefault(validChoices.Contains);
|
|
fallback ??= validChoices.FirstOrDefault();
|
|
|
|
return fallback != null ? Choices[fallback].Settings : [];
|
|
}
|
|
|
|
public string Value
|
|
{
|
|
get => value;
|
|
set
|
|
{
|
|
if (value != null && !Choices.ContainsKey(value))
|
|
throw new ArgumentException($"{value} is not in the list of valid choices");
|
|
|
|
this.value = value;
|
|
}
|
|
}
|
|
|
|
public List<string> ValidChoices(ITerrainInfo terrainInfo, int playerCount)
|
|
{
|
|
return Choices
|
|
.Where(kv =>
|
|
(kv.Value.Tileset == null || kv.Value.Tileset.Contains(terrainInfo.Id)) &&
|
|
(kv.Value.Players == null || kv.Value.Players.Contains(playerCount)))
|
|
.Select(kv => kv.Key)
|
|
.ToList();
|
|
}
|
|
|
|
public override IEnumerable<string> GetFluentReferences()
|
|
{
|
|
if (Label != null)
|
|
yield return Label;
|
|
|
|
foreach (var c in Choices.Values)
|
|
{
|
|
if (c.Label == null)
|
|
continue;
|
|
|
|
yield return c.Label + ".label";
|
|
|
|
// Descriptions are optional
|
|
if (FluentProvider.TryGetMessage(c.Label + ".description", out _))
|
|
yield return c.Label + ".description";
|
|
}
|
|
}
|
|
}
|
|
|
|
public class MapGeneratorMultiIntegerChoiceOption : MapGeneratorOption
|
|
{
|
|
[FieldLoader.Require]
|
|
public readonly string Parameter = null;
|
|
|
|
[FieldLoader.Require]
|
|
public readonly ImmutableArray<int> Choices = default;
|
|
|
|
public readonly int? Default = null;
|
|
int value;
|
|
|
|
public MapGeneratorMultiIntegerChoiceOption(string id, MiniYaml yaml)
|
|
: base(id, yaml)
|
|
{
|
|
Value = Default ?? (Choices != null ? Choices[0] : 0);
|
|
}
|
|
|
|
public int Value
|
|
{
|
|
get => value;
|
|
set
|
|
{
|
|
if (!Choices.Contains(value))
|
|
throw new ArgumentException($"{value} is not in the list of valid choices");
|
|
|
|
this.value = value;
|
|
}
|
|
}
|
|
|
|
public override IReadOnlyCollection<MiniYamlNode> GetSettings(ITerrainInfo terrainInfo, int playerCount)
|
|
{
|
|
return [new MiniYamlNode(Parameter, FieldSaver.FormatValue(Value))];
|
|
}
|
|
}
|
|
|
|
public sealed class MapGeneratorSettings : IMapGeneratorSettings
|
|
{
|
|
sealed class MapGenerationArgsWithOptions : MapGenerationArgs
|
|
{
|
|
public FrozenDictionary<string, string> Options = FrozenDictionary<string, string>.Empty;
|
|
}
|
|
|
|
readonly IMapGeneratorInfo generatorInfo;
|
|
|
|
public MapGeneratorSettings(IMapGeneratorInfo generatorInfo, MiniYaml yaml)
|
|
{
|
|
this.generatorInfo = generatorInfo;
|
|
|
|
var options = new List<MapGeneratorOption>();
|
|
foreach (var node in yaml.Nodes)
|
|
{
|
|
var split = node.Key.Split('@');
|
|
if (split.Length != 2)
|
|
continue;
|
|
|
|
if (split[0] == "BooleanOption")
|
|
options.Add(new MapGeneratorBooleanOption(split[1], node.Value));
|
|
else if (split[0] == "IntegerOption")
|
|
options.Add(new MapGeneratorIntegerOption(split[1], node.Value));
|
|
else if (split[0] == "MultiIntegerChoiceOption")
|
|
options.Add(new MapGeneratorMultiIntegerChoiceOption(split[1], node.Value));
|
|
else if (split[0] == "MultiChoiceOption")
|
|
options.Add(new MapGeneratorMultiChoiceOption(split[1], node.Value));
|
|
}
|
|
|
|
Options = options.ToImmutableArray();
|
|
}
|
|
|
|
public ImmutableArray<MapGeneratorOption> Options { get; } = [];
|
|
|
|
public void Randomize(MersenneTwister random)
|
|
{
|
|
if (Options.FirstOrDefault(o => o.Id == "Seed") is MapGeneratorIntegerOption seed)
|
|
seed.Value = random.Next();
|
|
}
|
|
|
|
public int PlayerCount
|
|
{
|
|
get
|
|
{
|
|
var o = Options.FirstOrDefault(o => o.Id == "Players");
|
|
if (o is MapGeneratorIntegerOption io)
|
|
return io.Value;
|
|
|
|
if (o is MapGeneratorMultiIntegerChoiceOption mio)
|
|
return mio.Value;
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public void Initialize(MapGenerationArgs args)
|
|
{
|
|
if (args is MapGenerationArgsWithOptions optionArgs)
|
|
{
|
|
foreach (var o in Options)
|
|
{
|
|
if (!optionArgs.Options.TryGetValue(o.Id, out var value))
|
|
continue;
|
|
|
|
switch (o)
|
|
{
|
|
case MapGeneratorBooleanOption bo: bo.Value = FieldLoader.GetValue<bool>(o.Id, value); break;
|
|
case MapGeneratorIntegerOption io: io.Value = FieldLoader.GetValue<int>(o.Id, value); break;
|
|
case MapGeneratorMultiIntegerChoiceOption mio: mio.Value = FieldLoader.GetValue<int>(o.Id, value); break;
|
|
case MapGeneratorMultiChoiceOption mo: mo.Value = value; break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public MapGenerationArgs Compile(ITerrainInfo terrainInfo, Size size)
|
|
{
|
|
// Apply the choices in their canonical order.
|
|
var playerCount = PlayerCount;
|
|
var layers = Options
|
|
.OrderBy(option => option.Priority)
|
|
.Select(o => o.GetSettings(terrainInfo, playerCount));
|
|
|
|
var options = new Dictionary<string, string>();
|
|
foreach (var o in Options)
|
|
{
|
|
switch (o)
|
|
{
|
|
case MapGeneratorBooleanOption bo: options[o.Id] = FieldSaver.FormatValue(bo.Value); break;
|
|
case MapGeneratorIntegerOption io: options[o.Id] = FieldSaver.FormatValue(io.Value); break;
|
|
case MapGeneratorMultiIntegerChoiceOption mio: options[o.Id] = FieldSaver.FormatValue(mio.Value); break;
|
|
case MapGeneratorMultiChoiceOption mo: options[o.Id] = mo.Value; break;
|
|
}
|
|
}
|
|
|
|
return new MapGenerationArgsWithOptions()
|
|
{
|
|
Generator = generatorInfo.Type,
|
|
Tileset = terrainInfo.Id,
|
|
Size = size,
|
|
Title = FluentProvider.GetMessage(generatorInfo.MapTitle),
|
|
Author = FluentProvider.GetMessage(generatorInfo.Name),
|
|
Settings = new MiniYaml(null, MiniYaml.Merge(layers)),
|
|
Options = options.ToFrozenDictionary()
|
|
};
|
|
}
|
|
}
|
|
}
|