public StaticEdgeReversed(StaticEdge Child, StaticNode target) { this.Child = Child; this.NodeTarget = target; this.FromNode = Child.FromNode; }
private void ProcessSubroom(StaticNode node, RandoConfigRoom config) { if (node.Name != "main" && config.Tweaks != null) { throw new Exception("Config error: you have a subroom with tweaks in it"); } foreach (RandoConfigHole holeConfig in config.Holes ?? new List <RandoConfigHole>()) { Hole matchedHole = null; int remainingMatches = holeConfig.Idx; foreach (Hole hole in this.Holes) { if (hole.Side == holeConfig.Side) { if (remainingMatches == 0) { matchedHole = hole; break; } else { remainingMatches--; } } } if (matchedHole == null) { throw new Exception($"Could not find the hole identified by area:{this.Area} room:{config.Room} side:{holeConfig.Side} idx:{holeConfig.Idx}"); } //Logger.Log("randomizer", $"Matching {roomConfig.Room} {holeConfig.Side} {holeConfig.Idx} to {matchedHole}"); matchedHole.Kind = holeConfig.Kind; matchedHole.Launch = holeConfig.Launch; if (holeConfig.LowBound != null) { matchedHole.LowBound = (int)holeConfig.LowBound; } if (holeConfig.HighBound != null) { matchedHole.HighBound = (int)holeConfig.HighBound; } if (holeConfig.HighOpen != null) { matchedHole.HighOpen = (bool)holeConfig.HighOpen; } if (holeConfig.Kind != HoleKind.None) { node.Edges.Add(new StaticEdge() { FromNode = node, HoleTarget = matchedHole, ReqIn = this.ProcessReqs(holeConfig.ReqIn, matchedHole, false), ReqOut = this.ProcessReqs(holeConfig.ReqOut, matchedHole, true) }); } } foreach (var edge in config.InternalEdges ?? new List <RandoConfigInternalEdge>()) { StaticNode toNode; if (edge.To != null) { toNode = this.Nodes[edge.To]; } else if (edge.Split != null) { if (node.Edges.Count != 2) { throw new Exception($"[{this.Name}.{node.Name}] Cannot split: must have exactly two edges"); } toNode = new StaticNode() { Name = node.Name + "_autosplit", ParentRoom = node.ParentRoom }; if (node.ParentRoom.Nodes.ContainsKey(toNode.Name)) { throw new Exception($"[{this.Name}.{node.Name}] You may only autosplit a room once"); } node.ParentRoom.Nodes[toNode.Name] = toNode; bool firstMain; var first = node.Edges[0].HoleTarget; var second = node.Edges[1].HoleTarget; switch (edge.Split) { case RandoConfigInternalEdge.SplitKind.BottomToTop: firstMain = first.Side == ScreenDirection.Down || second.Side == ScreenDirection.Up || (first.Side != ScreenDirection.Up && second.Side != ScreenDirection.Down && first.HighBound > second.HighBound); break; case RandoConfigInternalEdge.SplitKind.TopToBottom: firstMain = first.Side == ScreenDirection.Up || second.Side == ScreenDirection.Down || (first.Side != ScreenDirection.Down && second.Side != ScreenDirection.Up && first.LowBound < second.LowBound); break; case RandoConfigInternalEdge.SplitKind.RightToLeft: firstMain = first.Side == ScreenDirection.Right || second.Side == ScreenDirection.Left || (first.Side != ScreenDirection.Left && second.Side != ScreenDirection.Right && first.HighBound > second.HighBound); break; case RandoConfigInternalEdge.SplitKind.LeftToRight: default: firstMain = first.Side == ScreenDirection.Left || second.Side == ScreenDirection.Right || (first.Side != ScreenDirection.Right && second.Side != ScreenDirection.Left && first.LowBound < second.LowBound); break; } var secondary = firstMain ? node.Edges[1] : node.Edges[0]; node.Edges.Remove(secondary); toNode.Edges.Add(secondary); secondary.FromNode = toNode; } else if (edge.Collectable != null) { toNode = new StaticNode() { Name = node.Name + "_coll" + edge.Collectable.ToString(), ParentRoom = node.ParentRoom }; if (node.ParentRoom.Nodes.ContainsKey(toNode.Name)) { throw new Exception($"[{this.Name}.{node.Name}] You may only autosplit a room once"); } node.ParentRoom.Nodes[toNode.Name] = toNode; var thing = this.Collectables[edge.Collectable.Value]; if (thing.ParentNode != null) { throw new Exception($"[{this.Name}.{node.Name}] Can only assign a collectable to one owner"); } thing.ParentNode = toNode; toNode.Collectables.Add(thing); } else { throw new Exception($"[{this.Name}.{node.Name}] Internal edge must have either To or Split or Collectable"); } var reqIn = this.ProcessReqs(edge.ReqIn, null, false); var reqOut = this.ProcessReqs(edge.ReqOut, null, true); var forward = new StaticEdge() { FromNode = node, NodeTarget = toNode, ReqIn = reqIn, ReqOut = reqOut }; var reverse = new StaticEdgeReversed(forward, node); node.Edges.Add(forward); toNode.Edges.Add(reverse); } foreach (var col in config.Collectables) { if (col.Idx != null) { var thing = this.Collectables[col.Idx.Value]; if (thing.ParentNode != null) { throw new Exception($"[{this.Name}.{node.Name}] Can only assign a collectable to one owner"); } thing.ParentNode = node; thing.MustFly = col.MustFly; node.Collectables.Add(thing); } else if (col.X != null && col.Y != null) { node.Collectables.Add(new StaticCollectable { ParentNode = node, Position = new Vector2((float)col.X.Value, (float)col.Y.Value), MustFly = col.MustFly }); } else { throw new Exception($"[{this.Name}.{node.Name}] Collectable must specify Idx or X/Y"); } } }
public StaticRoom(AreaKey Area, RandoConfigRoom config, LevelData Level, List <Hole> Holes) { // hack: force credits screens into the epilogue roomset if (Area.ID == 7 && Level.Name.StartsWith("credits-")) { Area = new AreaKey(8); } this.Area = Area; this.Level = Level; this.Holes = Holes; this.Name = AreaData.Get(Area).GetSID() + "/" + (Area.Mode == AreaMode.Normal ? "A" : Area.Mode == AreaMode.BSide ? "B" : "C") + "/" + Level.Name; this.ReqEnd = this.ProcessReqs(config.ReqEnd); this.Hub = config.Hub; this.Tweaks = config.Tweaks ?? new List <RandoConfigEdit>(); this.CoreModes = config.Core; this.ExtraSpace = config.ExtraSpace ?? new List <RandoConfigRectangle>(); this.Worth = config.Worth ?? (float)Math.Sqrt(Level.Bounds.Width * Level.Bounds.Width + Level.Bounds.Height * Level.Bounds.Height) / 369.12870384189847f + 1; this.SpinnersShatter = config.SpinnersShatter; this.Collectables = new List <StaticCollectable>(); foreach (var entity in Level.Entities) { if (RandoModule.Instance.MetaConfig.CollectableNames.Contains(entity.Name)) { this.Collectables.Add(new StaticCollectable { Position = entity.Position, MustFly = false, }); } } this.Collectables.Sort((a, b) => { if (a.Position.Y > b.Position.Y) { return(1); } else if (a.Position.Y < b.Position.Y) { return(-1); } else if (a.Position.X > b.Position.X) { return(1); } else if (a.Position.X < b.Position.X) { return(-1); } else { return(0); } }); this.Nodes = new Dictionary <string, StaticNode>() { { "main", new StaticNode() { Name = "main", ParentRoom = this } } }; foreach (var subroom in config.Subrooms ?? new List <RandoConfigRoom>()) { if (subroom.Room == null || this.Nodes.ContainsKey(subroom.Room)) { throw new Exception($"Invalid subroom name in {this.Area} {this.Name}"); } this.Nodes.Add(subroom.Room, new StaticNode() { Name = subroom.Room, ParentRoom = this }); } this.ProcessSubroom(this.Nodes["main"], config); foreach (var subroom in config.Subrooms ?? new List <RandoConfigRoom>()) { this.ProcessSubroom(this.Nodes[subroom.Room], subroom); } // assign unmarked holes foreach (var uhole in this.Holes) { if (uhole.Kind != HoleKind.Unknown) { continue; } var bestDist = 10000f; StaticNode bestNode = null; var lowPos = uhole.LowCoord(this.Level.Bounds); var highPos = uhole.HighCoord(this.Level.Bounds); foreach (var node in this.Nodes.Values) { foreach (var edge in node.Edges.Where(edge => edge.HoleTarget != null)) { if (edge.HoleTarget == uhole) { bestNode = null; goto doublebreak; } var pos = edge.HoleTarget.LowCoord(this.Level.Bounds); var dist = (pos - lowPos).Length(); if (!uhole.LowOpen && dist < bestDist) { bestDist = dist; bestNode = node; } dist = (pos - highPos).Length(); if (!uhole.HighOpen && dist < bestDist) { bestDist = dist; bestNode = node; } pos = edge.HoleTarget.HighCoord(this.Level.Bounds); dist = (pos - lowPos).Length(); if (!uhole.LowOpen && dist < bestDist) { bestDist = dist; bestNode = node; } dist = (pos - highPos).Length(); if (!uhole.HighOpen && dist < bestDist) { bestDist = dist; bestNode = node; } } } doublebreak: bestNode?.Edges?.Add(new StaticEdge { FromNode = bestNode, HoleTarget = uhole, ReqIn = this.ProcessReqs(null, uhole, false), ReqOut = this.ProcessReqs(null, uhole, true), }); } // assign unmarked collectables foreach (var c in this.Collectables) { if (c.ParentNode != null) { continue; } var bestDist = 1000f; StaticNode bestNode = null; foreach (var node in this.Nodes.Values) { foreach (var edge in node.Edges) { if (edge.HoleTarget == null) { continue; } var pos = edge.HoleTarget.LowCoord(new Rectangle(0, 0, this.Level.Bounds.Width, this.Level.Bounds.Height)); var dist = (pos - c.Position).Length(); if (dist < bestDist) { bestDist = dist; bestNode = node; } pos = edge.HoleTarget.HighCoord(new Rectangle(0, 0, this.Level.Bounds.Width, this.Level.Bounds.Height)); dist = (pos - c.Position).Length(); if (dist < bestDist) { bestDist = dist; bestNode = node; } } } if (bestNode != null) { c.ParentNode = bestNode; bestNode.Collectables.Add(c); } } // perform fg tweaks var regex = new Regex("\\r\\n|\\n\\r|\\n|\\r"); var tweakable = new List <List <char> >(); foreach (var line in regex.Split(Level.Solids)) { var lst = new List <char>(); tweakable.Add(lst); foreach (var ch in line) { lst.Add(ch); } } void setTile(int x, int y, char tile) { while (y >= tweakable.Count) { tweakable.Add(new List <char>()); } while (x >= tweakable[y].Count) { tweakable[y].Add('0'); } tweakable[y][x] = tile; } foreach (var tweak in config.Tweaks ?? new List <RandoConfigEdit>()) { if (tweak.Name == "fgTiles") { setTile((int)tweak.X, (int)tweak.Y, tweak.Update.Tile); } } Level.Solids = string.Join("\n", tweakable.Select(line => string.Join("", line))); // peform decal tweaks foreach (var decalList in new[] { Level.FgDecals, Level.BgDecals }) { var removals = new List <DecalData>(); foreach (var decal in decalList) { var fg = object.ReferenceEquals(decalList, Level.FgDecals); foreach (var tweak in config.Tweaks ?? new List <RandoConfigEdit>()) { if (tweak.Decal == (fg ? RandoConfigDecalType.FG : RandoConfigDecalType.BG) && (tweak.Name == null || tweak.Name == decal.Texture) && (tweak.X == null || (int)tweak.X.Value == (int)decal.Position.X) && (tweak.Y == null || (int)tweak.Y.Value == (int)decal.Position.Y)) { if (tweak.Update?.Remove ?? false) { removals.Add(decal); } else { if (tweak.Update?.X != null) { decal.Position.X = tweak.Update.X.Value; } if (tweak.Update?.Y != null) { decal.Position.Y = tweak.Update.Y.Value; } if (tweak.Update?.ScaleX != null) { decal.Position.X = tweak.Update.ScaleX.Value; } if (tweak.Update?.ScaleY != null) { decal.Position.Y = tweak.Update.ScaleY.Value; } } break; } } } foreach (var decal in removals) { decalList.Remove(decal); } } foreach (var tweak in config.Tweaks ?? new List <RandoConfigEdit>()) { if ((tweak.Update?.Add ?? false) && tweak.Decal != RandoConfigDecalType.None) { var newDecal = new DecalData { Texture = tweak.Name, Position = new Vector2(tweak.Update.X.Value, tweak.Update.Y.Value), Scale = new Vector2(tweak.Update.ScaleX.Value, tweak.Update.ScaleY.Value), }; (tweak.Decal == RandoConfigDecalType.BG ? Level.BgDecals : Level.FgDecals).Add(newDecal); } } }
public StaticRoom(AreaKey Area, RandoConfigRoom config, LevelData Level, List <Hole> Holes) { this.Area = Area; this.Level = Level; this.Holes = Holes; this.Name = AreaData.Get(Area).GetSID() + "/" + (Area.Mode == AreaMode.Normal ? "A" : Area.Mode == AreaMode.BSide ? "B" : "C") + "/" + Level.Name; this.End = config.End; this.Hub = config.Hub; this.Tweaks = config.Tweaks ?? new List <RandoConfigEdit>(); this.CoreModes = config.Core; this.ExtraSpace = config.ExtraSpace ?? new List <RandoConfigRectangle>(); this.Collectables = new List <StaticCollectable>(); foreach (var entity in Level.Entities) { switch (entity.Name.ToLower()) { case "strawberry": case "key": this.Collectables.Add(new StaticCollectable { Position = entity.Position, MustFly = false, }); break; } } this.Collectables.Sort((StaticCollectable a, StaticCollectable b) => { if (a.Position.Y > b.Position.Y) { return(1); } else if (a.Position.Y < b.Position.Y) { return(-1); } else if (a.Position.X > b.Position.X) { return(1); } else if (a.Position.X < b.Position.X) { return(-1); } else { return(0); } }); this.Nodes = new Dictionary <string, StaticNode>() { { "main", new StaticNode() { Name = "main", ParentRoom = this } } }; foreach (var subroom in config.Subrooms ?? new List <RandoConfigRoom>()) { if (subroom.Room == null || this.Nodes.ContainsKey(subroom.Room)) { throw new Exception($"Invalid subroom name in {this.Area} {this.Name}"); } this.Nodes.Add(subroom.Room, new StaticNode() { Name = subroom.Room, ParentRoom = this }); } this.ProcessSubroom(this.Nodes["main"], config); foreach (var subroom in config.Subrooms ?? new List <RandoConfigRoom>()) { this.ProcessSubroom(this.Nodes[subroom.Room], subroom); } // assign unmarked holes foreach (var uhole in this.Holes) { if (uhole.Kind != HoleKind.Unknown) { continue; } var bestDist = 10000f; StaticNode bestNode = null; var lowPos = uhole.LowCoord(this.Level.Bounds); var highPos = uhole.HighCoord(this.Level.Bounds); foreach (var node in this.Nodes.Values) { foreach (var edge in node.Edges) { if (edge.HoleTarget == null) { continue; } if (edge.HoleTarget == uhole) { bestNode = null; goto doublebreak; } var pos = edge.HoleTarget.LowCoord(this.Level.Bounds); var dist = (pos - lowPos).Length(); if (!uhole.LowOpen && dist < bestDist) { bestDist = dist; bestNode = node; } dist = (pos - highPos).Length(); if (!uhole.HighOpen && dist < bestDist) { bestDist = dist; bestNode = node; } pos = edge.HoleTarget.HighCoord(this.Level.Bounds); dist = (pos - lowPos).Length(); if (!uhole.LowOpen && dist < bestDist) { bestDist = dist; bestNode = node; } dist = (pos - highPos).Length(); if (!uhole.HighOpen && dist < bestDist) { bestDist = dist; bestNode = node; } } } doublebreak: if (bestNode != null) { bestNode.Edges.Add(new StaticEdge { FromNode = bestNode, HoleTarget = uhole, ReqIn = this.ProcessReqs(null, uhole, false), ReqOut = this.ProcessReqs(null, uhole, true), }); } } // assign unmarked collectables foreach (var c in this.Collectables) { if (c.ParentNode != null) { continue; } var bestDist = 1000f; StaticNode bestNode = null; foreach (var node in this.Nodes.Values) { foreach (var edge in node.Edges) { if (edge.HoleTarget == null) { continue; } var pos = edge.HoleTarget.LowCoord(new Rectangle(0, 0, this.Level.Bounds.Width, this.Level.Bounds.Height)); var dist = (pos - c.Position).Length(); if (dist < bestDist) { bestDist = dist; bestNode = node; } pos = edge.HoleTarget.HighCoord(new Rectangle(0, 0, this.Level.Bounds.Width, this.Level.Bounds.Height)); dist = (pos - c.Position).Length(); if (dist < bestDist) { bestDist = dist; bestNode = node; } } } if (bestNode != null) { c.ParentNode = bestNode; bestNode.Collectables.Add(c); } } }
private void ProcessSubroom(StaticNode node, RandoConfigRoom config) { if (node.Name != "main" && config.Tweaks != null) { throw new Exception("Config error: you have a subroom with tweaks in it"); } foreach (RandoConfigHole holeConfig in config.Holes ?? new List <RandoConfigHole>()) { if (holeConfig.New) { if (holeConfig.LowBound == null || holeConfig.HighBound == null) { throw new Exception("Config error: new hole missing LowBound/HighBound"); } if (holeConfig.Kind == HoleKind.None) { throw new Exception("You probably didn't mean to add a new hole with kind None"); } var hole = new Hole(holeConfig.Side, holeConfig.LowBound.Value, holeConfig.HighBound.Value, holeConfig.HighOpen ?? false) { Launch = holeConfig.Launch, Kind = holeConfig.Kind, }; this.Holes.Add(hole); node.Edges.Add(new StaticEdge { FromNode = node, HoleTarget = hole, ReqIn = this.ProcessReqs(holeConfig.ReqIn, hole, false), ReqOut = this.ProcessReqs(holeConfig.ReqOut, hole, true), }); continue; } Hole matchedHole = null; var remainingMatches = holeConfig.Idx; foreach (var hole in this.Holes.Where(hole => hole.Side == holeConfig.Side)) { if (remainingMatches == 0) { matchedHole = hole; break; } remainingMatches--; } if (matchedHole == null) { throw new Exception($"Could not find the hole identified by area:{this.Area} room:{config.Room} side:{holeConfig.Side} idx:{holeConfig.Idx}"); } //Logger.Log("randomizer", $"Matching {roomConfig.Room} {holeConfig.Side} {holeConfig.Idx} to {matchedHole}"); matchedHole.Kind = holeConfig.Kind; matchedHole.Launch = holeConfig.Launch; if (holeConfig.LowBound != null) { matchedHole.LowBound = holeConfig.LowBound.Value; } if (holeConfig.HighBound != null) { matchedHole.HighBound = holeConfig.HighBound.Value; } if (holeConfig.HighOpen != null) { matchedHole.HighOpen = holeConfig.HighOpen.Value; } if (holeConfig.Kind != HoleKind.None) { node.Edges.Add(new StaticEdge { FromNode = node, HoleTarget = matchedHole, ReqIn = this.ProcessReqs(holeConfig.ReqIn, matchedHole, false), ReqOut = this.ProcessReqs(holeConfig.ReqOut, matchedHole, true), }); } if (holeConfig.Split != null) { matchedHole.HighOpen = true; var hole = new Hole(matchedHole.Side, 0, matchedHole.HighBound, false) { Launch = holeConfig.Split.Launch, Kind = holeConfig.Split.Kind, }; this.Holes.Add(hole); node.Edges.Add(new StaticEdge { FromNode = node, HoleTarget = hole, ReqIn = this.ProcessReqs(holeConfig.Split.ReqIn, hole, false), ReqOut = this.ProcessReqs(holeConfig.Split.ReqOut, hole, true), }); } } foreach (var edge in config.InternalEdges ?? new List <RandoConfigInternalEdge>()) { StaticNode toNode; if (edge.Warp != null) { // deal with this later node.WarpConfig.Add(edge); continue; } else if (edge.To != null) { if (!this.Nodes.ContainsKey(edge.To)) { throw new Exception($"[{this.Name}.{node.Name}] \"To\" edge says \"{edge.To}\" but no such subroom exists"); } toNode = this.Nodes[edge.To]; } else if (edge.CustomWarp) { toNode = null; } else if (edge.Split != null) { if (node.Edges.Count != 2) { throw new Exception($"[{this.Name}.{node.Name}] Cannot split: must have exactly two edges"); } toNode = new StaticNode() { Name = node.Name + "_autosplit", ParentRoom = node.ParentRoom }; if (node.ParentRoom.Nodes.ContainsKey(toNode.Name)) { throw new Exception($"[{this.Name}.{node.Name}] You may only autosplit a room once"); } node.ParentRoom.Nodes[toNode.Name] = toNode; bool firstMain; var first = node.Edges[0].HoleTarget; var second = node.Edges[1].HoleTarget; switch (edge.Split) { case RandoConfigInternalEdge.SplitKind.BottomToTop: firstMain = first.Side == ScreenDirection.Down || second.Side == ScreenDirection.Up || (first.Side != ScreenDirection.Up && second.Side != ScreenDirection.Down && first.HighBound > second.HighBound); break; case RandoConfigInternalEdge.SplitKind.TopToBottom: firstMain = first.Side == ScreenDirection.Up || second.Side == ScreenDirection.Down || (first.Side != ScreenDirection.Down && second.Side != ScreenDirection.Up && first.LowBound < second.LowBound); break; case RandoConfigInternalEdge.SplitKind.RightToLeft: firstMain = first.Side == ScreenDirection.Right || second.Side == ScreenDirection.Left || (first.Side != ScreenDirection.Left && second.Side != ScreenDirection.Right && first.HighBound > second.HighBound); break; case RandoConfigInternalEdge.SplitKind.LeftToRight: default: firstMain = first.Side == ScreenDirection.Left || second.Side == ScreenDirection.Right || (first.Side != ScreenDirection.Right && second.Side != ScreenDirection.Left && first.LowBound < second.LowBound); break; } var secondary = firstMain ? node.Edges[1] : node.Edges[0]; node.Edges.Remove(secondary); toNode.Edges.Add(secondary); secondary.FromNode = toNode; } else if (edge.Collectable != null) { toNode = new StaticNode() { Name = node.Name + "_coll" + edge.Collectable.ToString(), ParentRoom = node.ParentRoom }; if (node.ParentRoom.Nodes.ContainsKey(toNode.Name)) { throw new Exception($"[{this.Name}.{node.Name}] You may only autosplit a room once"); } node.ParentRoom.Nodes[toNode.Name] = toNode; var thing = this.Collectables[edge.Collectable.Value]; if (thing.ParentNode != null) { throw new Exception($"[{this.Name}.{node.Name}] Can only assign a collectable to one owner"); } thing.ParentNode = toNode; toNode.Collectables.Add(thing); } else { throw new Exception($"[{this.Name}.{node.Name}] Internal edge must have either To or Split or Collectable or CustomWarp"); } var reqIn = this.ProcessReqs(edge.ReqIn, null, false); var reqOut = this.ProcessReqs(edge.ReqOut, null, true); var forward = new StaticEdge() { FromNode = node, NodeTarget = toNode, ReqIn = reqIn, ReqOut = reqOut, CustomWarp = edge.CustomWarp, }; node.Edges.Add(forward); if (toNode != null) { var reverse = new StaticEdgeReversed(forward, node); toNode.Edges.Add(reverse); } } foreach (var col in config.Collectables) { if (col.Idx != null) { var thing = this.Collectables[col.Idx.Value]; if (thing.ParentNode != null) { throw new Exception($"[{this.Name}.{node.Name}] Can only assign a collectable to one owner"); } thing.ParentNode = node; thing.MustFly = col.MustFly; node.Collectables.Add(thing); } else if (col.X != null && col.Y != null) { node.Collectables.Add(new StaticCollectable { ParentNode = node, Position = new Vector2(col.X.Value, col.Y.Value), MustFly = col.MustFly }); } else { throw new Exception($"[{this.Name}.{node.Name}] Collectable must specify Idx or X/Y"); } } foreach (var flagStr in config.Flags ?? new List <string>()) { var name = flagStr; var val = true; if (name.Contains(":")) { var split = flagStr.Split(':'); name = split[0]; var v = split[1].ToLower(); val = v == "on" || v == "set" || v == "true" || v == "yes"; } node.FlagSetters.Add(Tuple.Create(name, val)); } }