Initial commit: OpenRA game engine
Fork from OpenRA/OpenRA with one-click launch script (start-ra.cmd) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
579
OpenRA.Mods.Cnc/UtilityCommands/ImportGen2MapCommand.cs
Normal file
579
OpenRA.Mods.Cnc/UtilityCommands/ImportGen2MapCommand.cs
Normal file
@@ -0,0 +1,579 @@
|
||||
#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.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Mods.Cnc.FileFormats;
|
||||
using OpenRA.Mods.Common;
|
||||
using OpenRA.Mods.Common.FileFormats;
|
||||
using OpenRA.Mods.Common.Terrain;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.UtilityCommands
|
||||
{
|
||||
public abstract class ImportGen2MapCommand
|
||||
{
|
||||
// The top few map rows on the original games are visible but not part of the interactable map area.
|
||||
// These are imported as being outside the map bounds but within the shroud visible margin,
|
||||
// which requires some extra padding to make the PPos calculations work.
|
||||
// The first value is added to the left, right, and bottom edges. The second value is added to the top.
|
||||
// There are 6 tiles in the top uninteractable region (3 in og TS, double for OpenRA).
|
||||
// So far testing hasn't shown the need for a left/right/bottom uninteractable margin except in cases of odd elevation, which affects us but not the original games.
|
||||
protected virtual int2 UninteractableMargin { get; } = new int2(0, 6);
|
||||
|
||||
// Having the top cordon equal the maximum possible elevation for the mod is important!
|
||||
// It needs to be equal to the top row's maximum elevation because there may be an actor there that will later crash the game.
|
||||
// But since we need to create the map's canvas before importing tile data (like elevation), we opt for the maximum possible elevation to be safe.
|
||||
protected virtual (int Left, int Top, int Right, int Bottom) Cordon { get; } = (1, 16, 1, 4);
|
||||
|
||||
protected abstract Dictionary<byte, string> OverlayToActor { get; }
|
||||
|
||||
protected abstract Dictionary<byte, Size> OverlayShapes { get; }
|
||||
|
||||
protected abstract Dictionary<byte, DamageState> OverlayToHealth { get; }
|
||||
|
||||
protected abstract Dictionary<byte, byte[]> ResourceFromOverlay { get; }
|
||||
|
||||
protected abstract Dictionary<string, string> DeployableActors { get; }
|
||||
|
||||
protected abstract string[] LampActors { get; }
|
||||
|
||||
protected abstract string[] CreepActors { get; }
|
||||
|
||||
protected void Run(Utility utility, string[] args)
|
||||
{
|
||||
// HACK: The engine code assumes that Game.modData is set.
|
||||
Game.ModData = utility.ModData;
|
||||
|
||||
var filename = args[1];
|
||||
var author = args.Length > 2
|
||||
? args[2]
|
||||
: "Westwood Studios";
|
||||
|
||||
var file = new IniFile(File.Open(args[1], FileMode.Open));
|
||||
var basic = file.GetSection("Basic");
|
||||
var mapSection = file.GetSection("Map");
|
||||
var tileset = mapSection.GetValue("Theater", "");
|
||||
var iniSize = mapSection.GetValue("Size", "0, 0, 0, 0").Split(',').Select(int.Parse).ToArray();
|
||||
var iniBounds = mapSection.GetValue("LocalSize", "0, 0, 0, 0").Split(',').Select(int.Parse).ToArray();
|
||||
|
||||
if (!utility.ModData.DefaultTerrainInfo.TryGetValue(tileset, out var terrainInfo))
|
||||
throw new InvalidDataException($"Unknown tileset {tileset}");
|
||||
|
||||
var usedAreaSize = new Size(iniSize[2], 2 * iniSize[3]);
|
||||
var mapCanvasSize = new Size(usedAreaSize.Width + Cordon.Left + Cordon.Right, usedAreaSize.Height + Cordon.Top + Cordon.Bottom);
|
||||
|
||||
var map = new Map(Game.ModData, terrainInfo, mapCanvasSize)
|
||||
{
|
||||
Title = basic.GetValue("Name", Path.GetFileNameWithoutExtension(filename)),
|
||||
Author = author,
|
||||
RequiresMod = utility.ModData.Manifest.Id
|
||||
};
|
||||
|
||||
var fullSize = new int2(iniSize[2], iniSize[3]);
|
||||
ReadTiles(map, file, fullSize);
|
||||
ReadActors(map, file, "Structures", fullSize);
|
||||
ReadActors(map, file, "Units", fullSize);
|
||||
ReadActors(map, file, "Infantry", fullSize);
|
||||
ReadTerrainActors(map, file, fullSize);
|
||||
ReadWaypoints(map, file, fullSize);
|
||||
ReadOverlay(map, file, fullSize);
|
||||
ReadLighting(map, file);
|
||||
ReadLamps(map, file);
|
||||
|
||||
var spawnCount = map.ActorDefinitions.Count(n => n.Value.Value == "mpspawn");
|
||||
var mapPlayers = new MapPlayers(map.Rules, spawnCount);
|
||||
map.PlayerDefinitions = mapPlayers.ToMiniYaml();
|
||||
|
||||
// This needs to be called after ReadTiles because it depends on terrain elevation.
|
||||
SetInteractableBounds(map, iniBounds);
|
||||
|
||||
var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap";
|
||||
map.Save(ZipFileLoader.Create(dest));
|
||||
Console.WriteLine(dest + " saved.");
|
||||
}
|
||||
|
||||
protected virtual void ReadTiles(Map map, IniFile file, int2 fullSize)
|
||||
{
|
||||
var terrainInfo = (ITemplatedTerrainInfo)Game.ModData.DefaultTerrainInfo[map.Tileset];
|
||||
var mapSection = file.GetSection("IsoMapPack5");
|
||||
|
||||
var data = Convert.FromBase64String(string.Concat(mapSection.Select(kvp => kvp.Value)));
|
||||
var cells = (fullSize.X * 2 - 1) * fullSize.Y;
|
||||
var lzoPackSize = cells * 11 + 4; // The last 4 bytes contain a LZO pack header saying no more data is left.
|
||||
var isoMapPack = new byte[lzoPackSize];
|
||||
UnpackLZO(data, isoMapPack);
|
||||
|
||||
var mf = new MemoryStream(isoMapPack);
|
||||
for (var i = 0; i < cells; i++)
|
||||
{
|
||||
var rx = mf.ReadUInt16();
|
||||
var ry = mf.ReadUInt16();
|
||||
var tilenum = mf.ReadUInt16();
|
||||
mf.ReadInt16(); // zero1
|
||||
var subtile = mf.ReadUInt8();
|
||||
var z = mf.ReadUInt8();
|
||||
mf.ReadUInt8(); // zero2
|
||||
|
||||
var uv = ToMPos(rx, ry, fullSize.X);
|
||||
|
||||
if (map.Tiles.Contains(uv))
|
||||
{
|
||||
if (!terrainInfo.Templates.ContainsKey(tilenum))
|
||||
tilenum = subtile = 0;
|
||||
|
||||
map.Tiles[uv] = new TerrainTile(tilenum, subtile);
|
||||
map.Height[uv] = z;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void ReadOverlay(Map map, IniFile file, int2 fullSize)
|
||||
{
|
||||
var overlaySection = file.GetSection("OverlayPack");
|
||||
var overlayCompressed = Convert.FromBase64String(string.Concat(overlaySection.Select(kvp => kvp.Value)));
|
||||
var overlayPack = new byte[1 << 18];
|
||||
var temp = new byte[1 << 18];
|
||||
UnpackLCW(overlayCompressed, overlayPack, temp);
|
||||
|
||||
var overlayDataSection = file.GetSection("OverlayDataPack");
|
||||
var overlayDataCompressed = Convert.FromBase64String(string.Concat(overlayDataSection.Select(kvp => kvp.Value)));
|
||||
var overlayDataPack = new byte[1 << 18];
|
||||
UnpackLCW(overlayDataCompressed, overlayDataPack, temp);
|
||||
|
||||
var overlayIndex = new CellLayer<int>(map);
|
||||
overlayIndex.Clear(0xFF);
|
||||
|
||||
for (var y = 0; y < fullSize.Y; y++)
|
||||
{
|
||||
for (var x = fullSize.X * 2 - 2; x >= 0; x--)
|
||||
{
|
||||
var dx = (ushort)x;
|
||||
var dy = (ushort)(y * 2 + x % 2);
|
||||
|
||||
var uv = ToMPos(dx, dy);
|
||||
var rx = (ushort)((dx + dy) / 2 + 1);
|
||||
var ry = (ushort)(dy - rx + fullSize.X + 1);
|
||||
|
||||
if (!map.Resources.Contains(uv))
|
||||
continue;
|
||||
|
||||
overlayIndex[uv] = rx + 512 * ry;
|
||||
}
|
||||
}
|
||||
|
||||
var nodes = new List<MiniYamlNode>();
|
||||
foreach (var cell in map.AllCells)
|
||||
{
|
||||
var overlayType = overlayPack[overlayIndex[cell]];
|
||||
if (overlayType == 0xFF)
|
||||
continue;
|
||||
|
||||
if (TryHandleOverlayToActorInner(cell, overlayPack, overlayIndex, overlayType, out var ar))
|
||||
{
|
||||
if (ar != null)
|
||||
nodes.Add(new MiniYamlNode("Actor" + (map.ActorDefinitions.Count + nodes.Count), ar.Save()));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TryHandleResourceFromOverlayInner(overlayType, overlayDataPack[overlayIndex[cell]], out var resourceTile))
|
||||
{
|
||||
map.Resources[cell] = resourceTile;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TryHandleOtherOverlayInner(map, cell, overlayDataPack, overlayIndex, overlayType))
|
||||
continue;
|
||||
|
||||
Console.WriteLine($"Cell {cell}: unknown overlay {overlayType}");
|
||||
}
|
||||
|
||||
map.ActorDefinitions = map.ActorDefinitions.Concat(nodes).ToArray();
|
||||
}
|
||||
|
||||
protected virtual void ReadWaypoints(Map map, IniFile file, int2 fullSize)
|
||||
{
|
||||
var nodes = new List<MiniYamlNode>();
|
||||
var waypointsSection = file.GetSection("Waypoints", true);
|
||||
foreach (var kv in waypointsSection)
|
||||
{
|
||||
var pos = Exts.ParseInt32Invariant(kv.Value);
|
||||
var ry = pos / 1000;
|
||||
var rx = pos - ry * 1000;
|
||||
var cell = ToMPos(rx, ry, fullSize.X).ToCPos(map);
|
||||
|
||||
var ar = new ActorReference((!int.TryParse(kv.Key, out var wpindex) || wpindex > 7) ? "waypoint" : "mpspawn")
|
||||
{
|
||||
new LocationInit(cell),
|
||||
new OwnerInit("Neutral")
|
||||
};
|
||||
|
||||
nodes.Add(new MiniYamlNode("Actor" + (map.ActorDefinitions.Count + nodes.Count), ar.Save()));
|
||||
}
|
||||
|
||||
map.ActorDefinitions = map.ActorDefinitions.Concat(nodes).ToArray();
|
||||
}
|
||||
|
||||
protected virtual void ReadTerrainActors(Map map, IniFile file, int2 fullSize)
|
||||
{
|
||||
var nodes = new List<MiniYamlNode>();
|
||||
var terrainSection = file.GetSection("Terrain", true);
|
||||
foreach (var kv in terrainSection)
|
||||
{
|
||||
var pos = Exts.ParseInt32Invariant(kv.Key);
|
||||
var ry = pos / 1000;
|
||||
var rx = pos - ry * 1000;
|
||||
var cell = ToMPos(rx, ry, fullSize.X).ToCPos(map);
|
||||
var name = kv.Value.ToLowerInvariant();
|
||||
|
||||
var ar = new ActorReference(name)
|
||||
{
|
||||
new LocationInit(cell),
|
||||
new OwnerInit("Neutral")
|
||||
};
|
||||
|
||||
if (!map.Rules.Actors.ContainsKey(name))
|
||||
Console.WriteLine($"Ignoring unknown actor type: `{name}`");
|
||||
else
|
||||
nodes.Add(new MiniYamlNode("Actor" + (map.ActorDefinitions.Count + nodes.Count), ar.Save()));
|
||||
}
|
||||
|
||||
map.ActorDefinitions = map.ActorDefinitions.Concat(nodes).ToArray();
|
||||
}
|
||||
|
||||
protected virtual void ReadActors(Map map, IniFile file, string type, int2 fullSize)
|
||||
{
|
||||
var nodes = new List<MiniYamlNode>();
|
||||
var structuresSection = file.GetSection(type, true);
|
||||
foreach (var kv in structuresSection)
|
||||
{
|
||||
var isDeployed = false;
|
||||
var entries = kv.Value.Split(',');
|
||||
|
||||
var name = entries[1].ToLowerInvariant();
|
||||
|
||||
if (DeployableActors.TryGetValue(name, out var n))
|
||||
{
|
||||
name = n;
|
||||
isDeployed = true;
|
||||
}
|
||||
|
||||
var health = Exts.ParseInt16Invariant(entries[2]);
|
||||
var rx = Exts.ParseInt32Invariant(entries[3]);
|
||||
var ry = Exts.ParseInt32Invariant(entries[4]);
|
||||
var facing = (byte)(224 - Exts.ParseByteInvariant(entries[type == "Infantry" ? 7 : 5]));
|
||||
|
||||
var cell = ToMPos(rx, ry, fullSize.X).ToCPos(map);
|
||||
|
||||
var ar = new ActorReference(name)
|
||||
{
|
||||
new LocationInit(cell),
|
||||
new OwnerInit(CreepActors.Contains(entries[1]) ? "Creeps" : "Neutral")
|
||||
};
|
||||
|
||||
if (type == "Infantry")
|
||||
{
|
||||
var subcell = 0;
|
||||
switch (Exts.ParseByteInvariant(entries[5]))
|
||||
{
|
||||
case 2: subcell = 3; break;
|
||||
case 3: subcell = 1; break;
|
||||
case 4: subcell = 2; break;
|
||||
}
|
||||
|
||||
if (subcell != 0)
|
||||
ar.Add(new SubCellInit((SubCell)subcell));
|
||||
}
|
||||
|
||||
if (health != 256)
|
||||
ar.Add(new HealthInit(100 * health / 256));
|
||||
|
||||
ar.Add(new FacingInit(WAngle.FromFacing(facing)));
|
||||
|
||||
if (isDeployed)
|
||||
ar.Add(new DeployStateInit(DeployState.Deployed));
|
||||
|
||||
if (!map.Rules.Actors.ContainsKey(name))
|
||||
Console.WriteLine($"Ignoring unknown actor type: `{name}`");
|
||||
else
|
||||
nodes.Add(new MiniYamlNode("Actor" + (map.ActorDefinitions.Count + nodes.Count), ar.Save()));
|
||||
}
|
||||
|
||||
map.ActorDefinitions = map.ActorDefinitions.Concat(nodes).ToArray();
|
||||
}
|
||||
|
||||
protected virtual void ReadLighting(Map map, IniFile file)
|
||||
{
|
||||
var lightingTypes = new Dictionary<string, string>()
|
||||
{
|
||||
{ "Red", "RedTint" },
|
||||
{ "Green", "GreenTint" },
|
||||
{ "Blue", "BlueTint" },
|
||||
{ "Ambient", "Intensity" },
|
||||
{ "Level", "HeightStep" },
|
||||
{ "Ground", null }
|
||||
};
|
||||
|
||||
var lightingSection = file.GetSection("Lighting");
|
||||
var parsed = new Dictionary<string, float>();
|
||||
var lightingNodes = new List<MiniYamlNode>();
|
||||
|
||||
foreach (var kv in lightingSection)
|
||||
{
|
||||
if (lightingTypes.ContainsKey(kv.Key))
|
||||
parsed[kv.Key] = FieldLoader.GetValue<float>(kv.Key, kv.Value);
|
||||
else
|
||||
Console.WriteLine($"Ignoring unknown lighting type: `{kv.Key}`");
|
||||
}
|
||||
|
||||
// Merge Ground into Ambient
|
||||
if (parsed.TryGetValue("Ground", out var ground))
|
||||
{
|
||||
if (!parsed.ContainsKey("Ambient"))
|
||||
parsed["Ambient"] = 1f;
|
||||
parsed["Ambient"] -= ground;
|
||||
}
|
||||
|
||||
foreach (var node in lightingTypes)
|
||||
{
|
||||
if (node.Value != null && parsed.TryGetValue(node.Key, out var val) && ((node.Key == "Level" && val != 0) || (node.Key != "Level" && val != 1.0f)))
|
||||
lightingNodes.Add(new MiniYamlNode(node.Value, FieldSaver.FormatValue(val)));
|
||||
}
|
||||
|
||||
if (lightingNodes.Count > 0)
|
||||
{
|
||||
map.RuleDefinitions = map.RuleDefinitions.WithNodesAppended(
|
||||
[
|
||||
new MiniYamlNode("^BaseWorld", new MiniYaml("",
|
||||
[
|
||||
new MiniYamlNode("TerrainLighting", new MiniYaml("", lightingNodes))
|
||||
]))
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void ReadLamps(Map map, IniFile file)
|
||||
{
|
||||
var lightingTypes = new Dictionary<string, string>()
|
||||
{
|
||||
{ "LightIntensity", "Intensity" },
|
||||
{ "LightRedTint", "RedTint" },
|
||||
{ "LightGreenTint", "GreenTint" },
|
||||
{ "LightBlueTint", "BlueTint" },
|
||||
};
|
||||
|
||||
var nodes = new List<MiniYamlNode>();
|
||||
foreach (var lamp in LampActors)
|
||||
{
|
||||
var lightingSection = file.GetSection(lamp, true);
|
||||
var lightingNodes = new List<MiniYamlNode>();
|
||||
|
||||
foreach (var kv in lightingSection)
|
||||
{
|
||||
if (kv.Key == "LightVisibility")
|
||||
{
|
||||
// Convert leptons to WDist
|
||||
var visibility = FieldLoader.GetValue<int>(kv.Key, kv.Value);
|
||||
lightingNodes.Add(new MiniYamlNode("Range", FieldSaver.FormatValue(new WDist(visibility * 4))));
|
||||
}
|
||||
else if (lightingTypes.TryGetValue(kv.Key, out var lightingType))
|
||||
{
|
||||
// Some maps use "," instead of "."!
|
||||
var value = FieldLoader.GetValue<float>(kv.Key, kv.Value.Replace(',', '.'));
|
||||
lightingNodes.Add(new MiniYamlNode(lightingType, FieldSaver.FormatValue(value)));
|
||||
}
|
||||
}
|
||||
|
||||
if (lightingNodes.Count > 0)
|
||||
{
|
||||
nodes.Add(new MiniYamlNode(lamp, new MiniYaml("",
|
||||
[
|
||||
new MiniYamlNode("TerrainLightSource", new MiniYaml("", lightingNodes))
|
||||
])));
|
||||
}
|
||||
}
|
||||
|
||||
map.RuleDefinitions = map.RuleDefinitions.WithNodesAppended(nodes);
|
||||
}
|
||||
|
||||
protected virtual void SetInteractableBounds(Map map, int[] iniBounds)
|
||||
{
|
||||
// Apply cordon cells + the ini-specified map margin.
|
||||
var left = Cordon.Left + iniBounds[0];
|
||||
var top = Cordon.Top + 2 * iniBounds[1];
|
||||
|
||||
var playableAreaWidth = iniBounds[2];
|
||||
var playableAreaHeight = 2 * iniBounds[3] - UninteractableMargin.Y;
|
||||
|
||||
// Black magic, don't ask. Non-flat maps seen to require additional height padding.
|
||||
var unknownHeightPadding = map.Height.Max() == 0 ? 0 : 8;
|
||||
|
||||
// Calculate Bounds edge tile coordinates.
|
||||
// Reduce bottom and right by 1 because map.SetBounds() increases them.
|
||||
var topLeft = new PPos(left, top);
|
||||
|
||||
if (map.Height.Max() != 0)
|
||||
{
|
||||
// Workaround: Some custom TS maps have invalid boundaries, with no padding area at the bottom
|
||||
// The boundary needs to be reduced until the projections are valid
|
||||
bool hasValidBoundaries;
|
||||
var paddingOffset = 1;
|
||||
do
|
||||
{
|
||||
var bottomRight = new PPos(playableAreaWidth + left - 1, playableAreaHeight + unknownHeightPadding + top - paddingOffset);
|
||||
map.SetBounds(topLeft, bottomRight);
|
||||
|
||||
var (topBound, bottomBound) = map.GetCellSpaceBounds();
|
||||
|
||||
hasValidBoundaries = topBound != int.MaxValue && bottomBound != int.MinValue;
|
||||
paddingOffset++;
|
||||
}
|
||||
while (!hasValidBoundaries);
|
||||
}
|
||||
else
|
||||
{
|
||||
var bottomRight = new PPos(playableAreaWidth + left - 1, playableAreaHeight + unknownHeightPadding + top - 1);
|
||||
map.SetBounds(topLeft, bottomRight);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual bool TryHandleOverlayToActorInner(
|
||||
CPos cell, byte[] overlayPack, CellLayer<int> overlayIndex, byte overlayType, out ActorReference actorReference)
|
||||
{
|
||||
actorReference = null;
|
||||
if (!OverlayToActor.TryGetValue(overlayType, out var actorType))
|
||||
return false;
|
||||
|
||||
// This could be just a dummy handler that we want to ignore.
|
||||
if (string.IsNullOrEmpty(actorType))
|
||||
return true;
|
||||
|
||||
if (OverlayShapes.TryGetValue(overlayType, out var shape))
|
||||
{
|
||||
// Only import the top-left cell of multi-celled overlays
|
||||
// Returning true here means this is a part of a bigger overlay that has already been handled.
|
||||
var aboveType = overlayPack[overlayIndex[cell - new CVec(1, 0)]];
|
||||
if (shape.Width > 1 && aboveType != 0xFF && OverlayToActor.TryGetValue(aboveType, out var a) && a == actorType)
|
||||
return true;
|
||||
|
||||
var leftType = overlayPack[overlayIndex[cell - new CVec(0, 1)]];
|
||||
if (shape.Height > 1 && leftType != 0xFF && OverlayToActor.TryGetValue(leftType, out var l) && l == actorType)
|
||||
return true;
|
||||
}
|
||||
|
||||
actorReference = new ActorReference(actorType)
|
||||
{
|
||||
new LocationInit(cell),
|
||||
new OwnerInit("Neutral")
|
||||
};
|
||||
|
||||
TryHandleOverlayToHealthInner(overlayType, actorReference);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual bool TryHandleOverlayToHealthInner(byte overlayType, ActorReference actorReference)
|
||||
{
|
||||
if (!OverlayToHealth.TryGetValue(overlayType, out var damageState))
|
||||
return false;
|
||||
|
||||
var health = 100;
|
||||
if (damageState == DamageState.Critical)
|
||||
health = 25;
|
||||
else if (damageState == DamageState.Heavy)
|
||||
health = 50;
|
||||
else if (damageState == DamageState.Medium)
|
||||
health = 75;
|
||||
|
||||
if (health != 100)
|
||||
actorReference.Add(new HealthInit(health));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual bool TryHandleResourceFromOverlayInner(byte overlayType, byte densityIndex, out ResourceTile resourceTile)
|
||||
{
|
||||
var resourceType = ResourceFromOverlay
|
||||
.Where(kv => kv.Value.Contains(overlayType))
|
||||
.Select(kv => kv.Key)
|
||||
.FirstOrDefault();
|
||||
|
||||
resourceTile = new ResourceTile(resourceType, densityIndex);
|
||||
return resourceType != 0;
|
||||
}
|
||||
|
||||
protected virtual bool TryHandleOtherOverlayInner(Map map, CPos cell, byte[] overlayDataPack, CellLayer<int> overlayIndex, byte overlayType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#region Helper methods
|
||||
|
||||
protected static void UnpackLZO(byte[] src, byte[] dest)
|
||||
{
|
||||
var srcOffset = 0U;
|
||||
var destOffset = 0U;
|
||||
|
||||
while (destOffset < dest.Length && srcOffset < src.Length)
|
||||
{
|
||||
var srcLength = BitConverter.ToUInt16(src, (int)srcOffset);
|
||||
var destLength = (uint)BitConverter.ToUInt16(src, (int)srcOffset + 2);
|
||||
srcOffset += 4;
|
||||
LZOCompression.DecodeInto(src, srcOffset, srcLength, dest, destOffset, ref destLength);
|
||||
srcOffset += srcLength;
|
||||
destOffset += destLength;
|
||||
}
|
||||
}
|
||||
|
||||
protected static void UnpackLCW(byte[] src, byte[] dest, byte[] temp)
|
||||
{
|
||||
var srcOffset = 0;
|
||||
var destOffset = 0;
|
||||
|
||||
while (destOffset < dest.Length)
|
||||
{
|
||||
var srcLength = BitConverter.ToUInt16(src, srcOffset);
|
||||
var destLength = BitConverter.ToUInt16(src, srcOffset + 2);
|
||||
srcOffset += 4;
|
||||
LCWCompression.DecodeInto(src, temp, srcOffset);
|
||||
Array.Copy(temp, 0, dest, destOffset, destLength);
|
||||
srcOffset += srcLength;
|
||||
destOffset += destLength;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert TS relative position to OpenRA MPos, accounting for map cordons.
|
||||
/// </summary>
|
||||
protected MPos ToMPos(int dx, int dy)
|
||||
{
|
||||
return new MPos(Cordon.Left + (dx + Cordon.Top % 2) / 2, Cordon.Top + dy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert TS relative position to OpenRA MPos, accounting for map cordons.
|
||||
/// </summary>
|
||||
protected MPos ToMPos(int rx, int ry, int mapWidthWithoutCordon)
|
||||
{
|
||||
var dx = rx - ry + mapWidthWithoutCordon - 1;
|
||||
var dy = rx + ry - mapWidthWithoutCordon - 1;
|
||||
return ToMPos(dx, dy);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user