public LevelData Bake(int?nonce, Random random)
        {
            var result = this.Static.MakeLevelData(new Vector2(this.Bounds.Left, this.Bounds.Top), nonce);

            bool ohgodwhat = random.Next(100) == 0; // :)

            string pickCrystalColor()
            {
                switch (random.Next(15))
                {
                case 0:
                case 1:
                case 2:
                    return("blue");

                case 3:
                case 4:
                case 5:
                    return("red");

                case 6:
                case 7:
                case 8:
                    return("purple");

                case 9:
                    return("rainbow");

                default:
                    return("dust");
                }
            }

            string crystalcolor = pickCrystalColor();

            string pickSpinnerColor()
            {
                return(random.Next(2) == 0 ? "dust" : "spike");
            }

            string spinnercolor = pickSpinnerColor();

            int maxID    = 0;
            var toRemove = new List <EntityData>();

            foreach (var e in result.Entities)
            {
                maxID = Math.Max(maxID, e.ID);

                switch (e.Name)
                {
                case "spinner":
                    if (ohgodwhat)
                    {
                        crystalcolor = pickCrystalColor();
                    }
                    if (e.Values == null)
                    {
                        e.Values = new Dictionary <string, object>();
                    }
                    if (crystalcolor == "dust")
                    {
                        e.Values["dust"] = "true";
                    }
                    else
                    {
                        e.Values["color"] = crystalcolor;
                    }
                    break;

                case "trackSpinner":
                case "rotateSpinner":
                    if (ohgodwhat)
                    {
                        spinnercolor = pickSpinnerColor();
                    }
                    if (e.Values == null)
                    {
                        e.Values = new Dictionary <string, object>();
                    }
                    if (spinnercolor == "dust")
                    {
                        e.Values["dust"] = "true";
                    }
                    break;

                case "lockBlock":
                    if (!this.UsedKeyholes.Contains(e.ID))
                    {
                        toRemove.Add(e);
                    }
                    break;
                }
            }
            foreach (var e in toRemove)
            {
                result.Entities.Remove(e);
            }

            void blockHole(Hole hole)
            {
                var topbottom = hole.Side == ScreenDirection.Up || hole.Side == ScreenDirection.Down;
                var farside   = hole.Side == ScreenDirection.Down || hole.Side == ScreenDirection.Right;

                Vector2 corner;

                switch (hole.Side)
                {
                case ScreenDirection.Up:
                    corner = new Vector2(0, -8);
                    break;

                case ScreenDirection.Left:
                    corner = new Vector2(-8, 0);
                    break;

                case ScreenDirection.Down:
                    corner = new Vector2(0, this.Bounds.Height);
                    break;

                case ScreenDirection.Right:
                default:
                    corner = new Vector2(this.Bounds.Width, 0);
                    break;
                }
                corner += hole.AlongDir.Unit() * hole.LowBound * 8;
                var e = new EntityData {
                    ID       = ++maxID,
                    Name     = "invisibleBarrier",
                    Width    = !topbottom ? 8 : hole.Size * 8,
                    Height   = topbottom ? 8 : hole.Size * 8,
                    Position = corner,
                    Level    = result,
                };

                result.Entities.Add(e);
            }

            void partiallyBlockHole(Hole hole, Hole hole2)
            {
                int diff         = hole.Compatible(hole2);
                var newHighBound = diff + hole2.LowBound - 1;
                var newHole      = new Hole(hole.Side, hole.LowBound, newHighBound, false);

                //Logger.Log("DEBUG", $"{result.Name}: special-blocking {newHole}");
                blockHole(newHole);
            }

            void beamHole(Hole hole)
            {
                Vector2 center;
                int     rotation;

                switch (hole.Side)
                {
                case ScreenDirection.Up:
                    center   = new Vector2(0, 0);
                    rotation = 0;
                    break;

                case ScreenDirection.Left:
                    center   = new Vector2(0, 0);
                    rotation = 270;
                    break;

                case ScreenDirection.Down:
                    center   = new Vector2(0, this.Bounds.Height);
                    rotation = 180;
                    break;

                case ScreenDirection.Right:
                default:
                    center   = new Vector2(this.Bounds.Width, 0);
                    rotation = 90;
                    break;
                }
                center += hole.AlongDir.Unit() * (hole.LowBound * 8 + hole.Size * 4);
                var e = new EntityData {
                    ID       = ++maxID,
                    Name     = "lightbeam",
                    Width    = hole.Size * 8,
                    Height   = 24,
                    Position = center,
                    Level    = result,
                    Values   = new Dictionary <string, object> {
                        { "rotation", (object)rotation },
                    }
                };

                result.Entities.Add(e);
            }

            void gateTopHole(Hole hole)
            {
                if (!hole.LowOpen)
                {
                    var coord = (hole.LowBound - 1) * 8;
                    var e     = new EntityData {
                        Name     = "invisibleBarrier",
                        Position = new Vector2(coord, -80),
                        Width    = 8,
                        Height   = 80,
                        Level    = result,
                        ID       = ++maxID,
                    };
                    result.Entities.Add(e);
                }
                if (!hole.HighOpen)
                {
                    var coord = (hole.HighBound + 1) * 8;
                    var e     = new EntityData {
                        Name     = "invisibleBarrier",
                        Position = new Vector2(coord, -80),
                        Width    = 8,
                        Height   = 80,
                        Level    = result,
                        ID       = ++maxID,
                    };
                    result.Entities.Add(e);
                }
            }

            bool disableDown           = true;
            bool disableUp             = true;
            var  unusedHorizontalHoles = new HashSet <Hole>();
            var  unusedTopHoles        = new HashSet <Hole>();

            foreach (var hole in this.Static.Holes)
            {
                if (hole.Side == ScreenDirection.Left || hole.Side == ScreenDirection.Right)
                {
                    unusedHorizontalHoles.Add(hole);
                }
                if (hole.Side == ScreenDirection.Up)
                {
                    unusedTopHoles.Add(hole);
                }
            }
            foreach (var node in this.Nodes.Values)
            {
                foreach (var edge in node.Edges)
                {
                    var hole  = edge.CorrespondingEdge(node).HoleTarget;
                    var hole2 = edge.OtherEdge(node).HoleTarget;
                    if (hole != null && hole.Side == ScreenDirection.Down)
                    {
                        disableDown = false;
                    }
                    if (hole != null && hole.Side == ScreenDirection.Up)
                    {
                        disableUp = false;
                    }
                    if (hole != null && (hole.Side == ScreenDirection.Left || hole.Side == ScreenDirection.Right))
                    {
                        unusedHorizontalHoles.Remove(hole);
                    }
                    if (hole != null && hole.Side == ScreenDirection.Up)
                    {
                        unusedTopHoles.Remove(hole);
                    }

                    // Block off holes connected to edges which should not be re-entered
                    if (hole != null && hole2 != null && hole2.Kind == HoleKind.Out)
                    {
                        blockHole(hole);
                    }
                    // Block off pieces of holes which are partially unused
                    if (hole != null && hole2 != null && hole.Size > hole2.Size)
                    {
                        partiallyBlockHole(hole, hole2);
                    }
                    // Add beams
                    var shine = RandoModule.Instance.Settings.Lights;
                    if ((shine == ShineLights.On || (shine == ShineLights.Hubs && this.Static.Hub)) &&
                        hole != null && hole2 != null &&
                        (hole.Kind == HoleKind.Out || hole.Kind == HoleKind.InOut) &&
                        (hole2.Kind == HoleKind.In || hole2.Kind == HoleKind.Unknown || hole2.Kind == HoleKind.InOut))
                    {
                        beamHole(hole);
                    }
                }

                foreach (var kv in node.Collectables)
                {
                    string name = null;
                    switch (kv.Value)
                    {
                    case LinkedNode.LinkedCollectable.Key:
                        name = "key";
                        break;

                    case LinkedNode.LinkedCollectable.Strawberry:
                    case LinkedNode.LinkedCollectable.WingedStrawberry:
                        name = "strawberry";
                        break;
                    }

                    var e = new EntityData {
                        ID       = ++maxID,
                        Name     = name,
                        Level    = result,
                        Position = kv.Key.Position,
                    };
                    if (kv.Value == LinkedNode.LinkedCollectable.WingedStrawberry)
                    {
                        e.Values["winged"] = "true";
                    }
                    result.Entities.Add(e);
                }
            }

            result.DisableDownTransition = disableDown;
            if (disableUp)
            {
                new DynData <LevelData>(result).Set("DisableUpTransition", true);
            }
            foreach (var hole in unusedHorizontalHoles)
            {
                blockHole(hole);
            }
            foreach (var hole in unusedTopHoles)
            {
                gateTopHole(hole);
            }
            return(result);
        }
Example #2
0
        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");
                }
            }
        }
Example #3
0
        private Requirement ProcessReqs(RandoConfigReq config, Hole matchHole = null, bool isOut = false)
        {
            if (matchHole != null)
            {
                if (matchHole.Kind == HoleKind.None)
                {
                    return(new Impossible());
                }
                if (isOut && (matchHole.Kind == HoleKind.In || matchHole.Kind == HoleKind.Unknown))
                {
                    return(new Impossible());
                }
                if (!isOut && matchHole.Kind == HoleKind.Out)
                {
                    return(new Impossible());
                }
                if (config == null)
                {
                    return(new Possible());
                }
            }
            if (config == null)
            {
                return(new Impossible());
            }

            var conjunction = new List <Requirement>();

            if (config.And != null)
            {
                var children = new List <Requirement>();
                foreach (var childconfig in config.And)
                {
                    children.Add(this.ProcessReqs(childconfig));
                }
                conjunction.Add(new Conjunction(children));
            }

            if (config.Or != null)
            {
                var children = new List <Requirement>();
                foreach (var childconfig in config.Or)
                {
                    children.Add(this.ProcessReqs(childconfig));
                }
                conjunction.Add(new Disjunction(children));
            }

            if (config.Dashes != null)
            {
                conjunction.Add(new DashRequirement(config.Dashes.Value));
            }

            // not nullable
            conjunction.Add(new SkillRequirement(config.Difficulty));

            if (config.Key)
            {
                if (config.KeyholeID == null)
                {
                    throw new Exception("Config error: Key: true without KeyholeID");
                }
                conjunction.Add(new KeyRequirement(config.KeyholeID.Value));
            }

            if (conjunction.Count == 0)
            {
                throw new Exception("this should be unreachable");
            }
            else if (conjunction.Count == 1)
            {
                return(conjunction[0]);
            }
            return(new Conjunction(conjunction));
        }
Example #4
0
        public int Compatible(Hole other)
        {
            int alignLow()
            {
                return(this.LowBound - other.LowBound);
            }

            int alignHigh()
            {
                return(this.HighBound - other.HighBound);
            }

            if (other.Side == ScreenDirection.Up && this.Side != ScreenDirection.Up && this.Side != ScreenDirection.Left)
            {
                return(-other.Compatible(this));
            }
            if (other.Side == ScreenDirection.Left && this.Side != ScreenDirection.Left && this.Side != ScreenDirection.Up)
            {
                return(-other.Compatible(this));
            }

            if (this.Side == ScreenDirection.Up && other.Side == ScreenDirection.Down)
            {
                // Vertical transitions
                if (this.Launch != null)
                {
                    if (other.Launch != null)
                    {
                        if (other.Launch == -1)
                        {
                            return(INCOMPATIBLE);
                        }
                        return(this.Launch.Value - other.Launch.Value);
                    }
                    else if (other.Closed)
                    {
                        return(this.Launch.Value - (other.LowBound + other.HighBound) / 2);
                    }
                    else if (other.HighOpen)
                    {
                        return(this.Launch.Value - (other.LowBound + 2));
                    }
                    else if (other.LowOpen)
                    {
                        return(this.Launch.Value - (other.HighBound - 2));
                    }
                }
                else if (this.BothOpen || other.BothOpen)
                {
                    // if either is open on both ends, they must line up perfectly
                    if (this.BothOpen && other.BothOpen && this.Size == other.Size)
                    {
                        return(0);
                    }
                }
                else if (this.HalfOpen || other.HalfOpen)
                {
                    // if either is half-open, they must be the same half open
                    if (this.LowOpen == other.LowOpen)
                    {
                        return(this.LowOpen ? alignHigh() : alignLow());
                    }
                }
                else
                {
                    // Only remaining option is both closed. they must be the same size
                    if (this.Size == other.Size)
                    {
                        return(alignLow());
                    }
                }
            }
            if (this.Side == ScreenDirection.Left && other.Side == ScreenDirection.Right)
            {
                // Horizontal transitions
                if (this.HighOpen || other.HighOpen)
                {
                    // if either is open on the bottom, they both must be open on the bottom and we align the death planes
                    // this is kind of a questionable choice all around tbh
                    // maybe additionally restrict that sizes must be the same?
                    if (this.HighOpen && other.HighOpen)
                    {
                        return(alignHigh());
                    }
                }
                else if (this.LowOpen || other.LowOpen)
                {
                    // if either is open on the top, the other must also be open on the top OR it must be sufficiently tall
                    if (this.LowOpen && other.LowOpen)
                    {
                        return(alignHigh());
                    }
                    else if (this.Closed && this.Size > 3)
                    {
                        return(alignHigh());
                    }
                    else if (other.Closed && other.Size > 3)
                    {
                        return(alignHigh());
                    }
                }
                else
                {
                    // only remaining option is both closed. they must be the same size OR sufficiently tall
                    if (this.Size == other.Size)
                    {
                        return(alignHigh());
                    }
                    else if (this.Size > 3 && other.Size > 3)
                    {
                        return(alignHigh());
                    }
                }
            }
            return(INCOMPATIBLE);
        }