Skip to content

Commit

Permalink
added Map Randomiser
Browse files Browse the repository at this point in the history
  • Loading branch information
mathlover3 committed Apr 18, 2024
1 parent 2be858e commit 19a20b9
Showing 1 changed file with 365 additions and 0 deletions.
365 changes: 365 additions & 0 deletions MapRandomiser/plugin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,365 @@
using BepInEx;
using BepInEx.Configuration;
using BoplFixedMath;
using HarmonyLib;
using System.Reflection;
using UnityEngine;
using Steamworks;
using Steamworks.Data;
using UnityEngine.SceneManagement;
using System;
using System.Text.RegularExpressions;
using Random = UnityEngine.Random;

namespace MapRandomiser
{
[BepInPlugin("com.MLT.MapRandomiser", "MapRandomiser", "1.0.0")]
public class Plugin : BaseUnityPlugin
{
public static GameObject PlatformAbility;
public static Transform levelt;
public static StickyRoundedRectangle platformPrefab;
public static int CurrentMapId;
//jaged array of arrays with 2 arrays of 2 Fixes. the 2 arrays have 2 coradanents that make a rectatgle.
//no platforms are allowed to be inside this rectangle
public static Fix[][][] SpaceTakenUp;
//the minimum distance betreen platforms allowed in bopl units. Default: 2
public static ConfigEntry<double> min_dist;
//how many platforms it will attempt to place. if it fails there will be a chance it attempts to place agien. Default: 30
public static ConfigEntry<int> PlatPlaceAttempts;
//chance of attempting to place a platform agien if it fails. with a 1/x chance where x is this value. Default: 3
public static ConfigEntry<double> ChanceYouAttemptToPlaceAPlatformAgien;
//circle platform chance as 1/x where x is this value. Default: 5
public static ConfigEntry<double> CirclePlatformChance;
//min and max posisons platforms can be placed
//-97.27
public static Fix MinX = (Fix) (-97.27);
//-26
public static Fix MinY = (Fix) (-26);
//97.6
public static Fix MaxX = (Fix)97.6;
//40
public static Fix MaxY = (Fix)40;
//min and max sizes
//0.25
public static ConfigEntry<double> MinScaleX;
//5
public static ConfigEntry<double> MaxScaleX;
//0.25
public static ConfigEntry<double> MinScaleY;
//5
public static ConfigEntry<double> MaxScaleY;
//min and max Radius
//0.1
public static ConfigEntry<double> MinRadius;
//5
public static ConfigEntry<double> MaxRadius;
//rotatson?
public static ConfigEntry<bool> Rotate;
//random colors
public static ConfigEntry<bool> RandomColors;
//mass for 1x1 block.
public static ConfigEntry<double> OneByOneBlockMass;
public static ConfigFile config;

private void Awake()
{
Logger.LogInfo("MapRandomiser Has been loaded");
Harmony harmony = new Harmony("com.MLT.MapRandomiser");

Logger.LogInfo("Harmony harmony = new Harmony -- Melon, 2024");
harmony.PatchAll(); // Patch Harmony
Logger.LogInfo("MapRandomiser Patch Compleate!");

SceneManager.sceneLoaded += OnSceneLoaded;
config = Config;

min_dist = config.Bind("Settings", "Minimum distance", 2d, "the minimum distance betreen platforms allowed in bopl units. below 2 there is a chance you could be squished on spawn.");
PlatPlaceAttempts = config.Bind("Settings", "Platform Place Attempts", 30, "how many platforms it will attempt to place. if it fails there will be a chance it attempts to place agien.");
ChanceYouAttemptToPlaceAPlatformAgien = config.Bind("Chances", "Chance It Attempts To Place A Platform Agien", 2d, "chance of attempting to place a platform agien if it fails. with a 1/x chance where x is this value.");
CirclePlatformChance = config.Bind("Chances", "Circle Platform Chance", 5d, "chance a platform is a circle platform as 1/x where x is this value.");
MinScaleX = config.Bind("Width", "Minimum Width", 0.25d, "Minimum Platform Width in bopl units");
MaxScaleX = config.Bind("Width", "Maximum Width", 5d, "Maximum Platform Width in bopl units");
MinScaleY = config.Bind("Height", "Minimum Height", 0.25d, "Minimum Platform Height in bopl units");
MaxScaleY = config.Bind("Height", "Maximum Height", 5d, "Maximum Platform Height in bopl units");
MinRadius = config.Bind("Radius", "Minimum Radius", 0.25d, "Minimum Platform Radius in bopl units");
MaxRadius = config.Bind("Radius", "Maximum Radius", 5d, "Maximum Platform Radius in bopl units");
Rotate = config.Bind("Rotate", "Rotate", false, "can platforms spawn rotated. IN BETA. MAY SPAWN SQUISHED!");
RandomColors = config.Bind("Colors", "Random Colors", true, "Use Random Colors (note that this is the one value you dont need everyone to have the same value for.)");
OneByOneBlockMass = config.Bind("Mass", "1 by 1 block mass", 1d, "the mass of a platform that is 1 by 1 units");
}

private static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
Debug.Log("OnSceneLoaded: " + scene.name);
if (IsLevelName(scene.name))
{
CurrentMapId = GetMapIdFromSceneName(scene.name);
//get the platform prefab out of the Platform ability gameobject (david) DO NOT REMOVE!
//chatgpt code to get the Platform ability object
GameObject[] allObjects = Resources.FindObjectsOfTypeAll(typeof(GameObject)) as GameObject[];
Debug.Log("getting platform object");
foreach (GameObject obj in allObjects)
{
if (obj.name == "Platform")
{
// Found the object with the desired name and HideAndDontSave flag
// You can now store its reference or perform any other actions
PlatformAbility = obj;
Debug.Log("Found the object: " + obj.name);
break;
}
}
var platformTransform = PlatformAbility.GetComponent(typeof(PlatformTransform)) as PlatformTransform;
platformPrefab = platformTransform.platformPrefab;
//find the platforms and remove them (shadow + david)
levelt = GameObject.Find("Level").transform;
foreach (Transform tplatform in levelt)
{
Updater.DestroyFix(tplatform.gameObject);
}
//step 1: spawn platforms under the players and add a no-platform rectange to the list\
//step 1.1: reset the list of bad spawn spots
SpaceTakenUp = Array.Empty <Fix[][]>();
//step 1.5 get the player spawn points
var PlayerList = GameObject.Find("PlayerList");
//handle level with id 5 (scene name Level6) and posably outers
if (PlayerList == null)
{
PlayerList = GameObject.Find("PlayerList (1)");
}
var PlayerSpawns = PlayerList.GetComponent<GameSessionHandler>().teamSpawns;
foreach (Vec2 SpawnPoint in PlayerSpawns)
{
SpawnPlatform(SpawnPoint.x, SpawnPoint.y-(Fix)3, (Fix)2, (Fix)2, (Fix)1, (Fix)0);
}

//step 2: try to spawn PlatPlaceAttempts platforms. not spawning them if they overlap with a rectangle in the SpaceTakenUp array
for (int i = 0; i < PlatPlaceAttempts.Value; i++)
{
AttemptToSpawnRandomPlatform();
}
//testing
/*for (int x = -97; x < 97; x++)
{
for (int y = -26; y < 40; y++)
{
if (IsValidPlacement((Fix)x, (Fix)y, (Fix)3, (Fix)1, (Fix)5, (Fix)0))
{
SpawnPlatform((Fix)x, (Fix)y, (Fix)3, (Fix)1, (Fix)5, (Fix)0);
}
}
}
*/
//log SpaceTakenUp
/*Debug.Log("SpaceTakenUp:");
foreach (var layer in SpaceTakenUp)
{
foreach (var point in layer)
{
Debug.Log($"[{point[0]}, {point[1]}]");
}
}
*/
}
}

public static void SpawnPlatform(Fix X, Fix Y, Fix Width, Fix Height, Fix Radius, Fix rotatson)
{
// Spawn platform (david)
var StickyRect = FixTransform.InstantiateFixed<StickyRoundedRectangle>(platformPrefab, new Vec2(X, Y));
StickyRect.rr.Scale = Fix.One;
var platform = StickyRect.GetComponent<ResizablePlatform>();
platform.GetComponent<DPhysicsRoundedRect>().ManualInit();
ResizePlatform(platform, Width, Height, Radius);
//in radiens
StickyRect.GetGroundBody().up = new Vec2(rotatson);
//random color
if (RandomColors.Value)
{
StickyRect.GetComponent<SpriteRenderer>().color = new UnityEngine.Color(Random.Range((float)0, (float)1), Random.Range((float)0, (float)1), Random.Range((float)0, (float)1));
}
// set the mass
AccessTools.Field(typeof(BoplBody), "mass").SetValue(StickyRect.GetGroundBody(), CalculateMassOfPlatform(Width, Height, Radius));
Debug.Log("Set mass to " + (double)CalculateMassOfPlatform(Width, Height, Radius));
// log (melon)
Debug.Log("Spawned platform at position (" + X + ", " + Y + ") with dimensions (" + Width + ", " + Height + ") and radius " + Radius);
//calculate the true Width and Hight including the rotatson

Fix[] WidthAndHight = TrueWidthAndHight(Width, Height, Radius, rotatson);
var WidthWithRotatson = WidthAndHight[0];
var HeightWithRotatson = WidthAndHight[1];
// add to list (david)
//Debug.Log("WidthWithRotatson: " + WidthWithRotatson + " HightWithRotatson: " + HeightWithRotatson);
var x1 = X - (WidthWithRotatson / (Fix)2) - (Fix)min_dist.Value;
var y1 = Y - (HeightWithRotatson / (Fix)2) - (Fix)min_dist.Value;
var x2 = X + (WidthWithRotatson / (Fix)2) + (Fix)min_dist.Value;
var y2 = Y + (HeightWithRotatson / (Fix)2) + (Fix)min_dist.Value;

//append them
Fix[] point1 = { x1, y1 };
Fix[] point2 = { x2, y2 };
Fix[][] rect = { point1, point2 };
SpaceTakenUp = AppendRect(rect);
/*
Debug.Log("SpaceTakenUp:");
foreach (var point in rect)
{
Debug.Log($"[{point[0]}, {point[1]}]");
}
*/
}

public static void Update()
{
//ignore this
}

//this can be called anytime the object is active. this means you can have animated levels with shape changing platforms
public static void ResizePlatform(ResizablePlatform platform, Fix newWidth, Fix newHeight, Fix newRadius)
{
// ok so aparently the Height and Width need to be switched for some reson? idk. this took 3 hours to figger out...
platform.ResizePlatform(newWidth, newHeight, newRadius, true);
}
public static bool IsLevelName(String input)
{
Regex regex = new Regex("Level[0-9]+", RegexOptions.IgnoreCase);
return regex.IsMatch(input);
}
//https://stormconsultancy.co.uk/blog/storm-news/convert-an-angle-in-degrees-to-radians-in-c/
public static double ConvertToRadians(double angle)
{
return (Math.PI / 180) * angle;
}
//https://stackoverflow.com/questions/19167669/keep-only-numeric-value-from-a-string
// simply replace the offending substrings with an empty string
public static int GetMapIdFromSceneName(string s)
{
Regex rxNonDigits = new Regex(@"[^\d]+");
if (string.IsNullOrEmpty(s)) return 0;
string cleaned = rxNonDigits.Replace(s, "");
//subtract 1 as scene names start with 1 but ids start with 0
return int.Parse(cleaned)-1;
}
//chatgpt code
public static Fix[][][] AppendRect(Fix[][] rect)
{
// Resize array to accommodate the new element
Array.Resize(ref SpaceTakenUp, SpaceTakenUp.Length + 1);
SpaceTakenUp[SpaceTakenUp.Length - 1] = rect;
return SpaceTakenUp;
}
public static Fix[] TrueWidthAndHight(Fix Width, Fix Height, Fix Radius, Fix rotatson)
{
//convert to double so i can use Math operators instead of the Fix ones
var Width2 = (double)Width;
var Height2 = (double)Height;
var rotatson2 = (double)rotatson;
//calculate the Width And Height with the rotatson
Fix WidthWithRotatson = (Fix)Math.Max(Math.Sqrt(Math.Pow(Width2, 2) + Math.Pow(Height2, 2)) * Math.Cos(rotatson2 + Math.Atan2(Height2, Width2)), Math.Sqrt(Math.Pow(Width2, 2) + Math.Pow(Height2, 2)) * Math.Cos(rotatson2 - Math.Atan2(Height2, Width2)));
Fix HeightWithRotatson = (Fix)Math.Max(Math.Sqrt(Math.Pow(Width2, 2) + Math.Pow(Height2, 2)) * Math.Sin(rotatson2 + Math.Atan2(Height2, Width2)), Math.Sqrt(Math.Pow(Width2, 2) + Math.Pow(Height2, 2)) * Math.Sin(rotatson2 - Math.Atan2(Height2, Width2)));
//Fix WidthWithRotatson = (Fix)(Width2 * Math.Sin(rotatson2) + Height2 * Math.Cos(rotatson2));
//Fix HeightWithRotatson = (Fix)(Width2 * Math.Cos(rotatson2) + Height2 * Math.Sin(rotatson2));
//add the radius as its additive
WidthWithRotatson = WidthWithRotatson + Radius;
HeightWithRotatson = HeightWithRotatson + Radius;
//multiply by 2 as width and height is like radius and is distance from the center
Fix[] returnArray = {WidthWithRotatson * (Fix)2, HeightWithRotatson * (Fix)2};
return returnArray;
}
public static bool IsValidPlacement(Fix X, Fix Y, Fix Width, Fix Height, Fix Radius, Fix rotatson)
{
Fix[] WidthAndHight = TrueWidthAndHight(Width, Height, Radius, rotatson);
var WidthWithRotatson = WidthAndHight[0];
var HeightWithRotatson = WidthAndHight[1];
//get the bounding box
var BottomLeftX = X - (WidthWithRotatson / (Fix)2);
var BottomLeftY = Y - (HeightWithRotatson / (Fix)2);
var TopRightX = X + (WidthWithRotatson / (Fix)2);
var TopRightY = Y + (HeightWithRotatson / (Fix)2);
foreach (var rect in SpaceTakenUp)
{
//split the bounding box up so we can check it easyer
var BottomLeftX2 = rect[0][0];
var BottomLeftY2 = rect[0][1];
var TopRightX2 = rect[1][0];
var TopRightY2 = rect[1][1];
//https://www.geeksforgeeks.org/intersecting-rectangle-when-bottom-left-and-top-right-corners-of-two-rectangles-are-given/
// gives bottom-left point
// of intersection rectangle
Fix x5 = Fix.Max(BottomLeftX, BottomLeftX2);
Fix y5 = Fix.Max(BottomLeftY, BottomLeftY2);

// gives top-right point
// of intersection rectangle
Fix x6 = Fix.Min(TopRightX, TopRightX2);
Fix y6 = Fix.Min(TopRightY, TopRightY2);

// no intersection
if (!(x5 > x6 || y5 > y6))
{
//Debug.Log("failed to spawn platform due to box {{" + BottomLeftX + ", " + BottomLeftY + "}, {" + TopRightX + ", " + TopRightY + "}} and box {{" + BottomLeftX2 + ", " + BottomLeftY2 + "}, {" + TopRightX2 + ", " + TopRightY2 + "}} colliding");
return false;
}
}
return true;
}
public static void AttemptToSpawnRandomPlatform()
{
//generate the paramiters
var Xpos = Updater.RandomFix(MinX, MaxX);
var Ypos = Updater.RandomFix(MinY, MaxY);
Fix Width = Fix.Zero;
Fix Height = Fix.Zero;
//if we are not doing a circle platform
if (Updater.RandomFix(Fix.Zero, (Fix)CirclePlatformChance.Value) > 1)
{
Width = Updater.RandomFix((Fix)MinScaleX.Value, (Fix)MaxScaleX.Value);
Height = Updater.RandomFix((Fix)MinScaleY.Value, (Fix)MaxScaleY.Value);
}
//if its a circle platform
else
{
Width = (Fix)0.05;
Height = (Fix)0.05;
}
var Radius = Updater.RandomFix((Fix)MinRadius.Value, (Fix)MaxRadius.Value);
var Rot = Fix.Zero;
if (Rotate.Value)
{
//because rotatson is in radiens we make it random from 0 to Pi*2 also known as Tau
Rot = Updater.RandomFix(Fix.Zero, Fix.PiTimes2);
}
//check if its valid and if so spawn it
if (IsValidPlacement(Xpos, Ypos, Width, Height, Radius, Rot))
{
SpawnPlatform(Xpos, Ypos, Width, Height, Radius, Rot);
//Debug.Log("spawned platform");
}
else
{
if (Updater.RandomFix(Fix.Zero, (Fix)ChanceYouAttemptToPlaceAPlatformAgien.Value) < (Fix)1)
{
//if its lucky it gets a chance at a new life
AttemptToSpawnRandomPlatform();
}
}
}
public static Fix CalculateMassOfPlatform(Fix Width, Fix Height, Fix Radius)
{
//multiply by 2 because Width and Height are just distances from the center
var TrueWidth = Width * (Fix)2 + Radius;
var TrueHeight = Height * (Fix)2 + Radius;
var Area = TrueWidth * TrueHeight;
//if it is a circle
if (Width == (Fix)0.05 && Height == (Fix)0.05)
{
//A=Pi*R^2
//there is no exsponent for Fixes
Area = Fix.Pi * Radius * Radius;
}
return Area * (Fix)OneByOneBlockMass.Value;

}
}
}

0 comments on commit 19a20b9

Please sign in to comment.