using Networking.Server;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
[CreateAssetMenu(menuName = "Major Project/Map Generation/Map Manager")]
|
|
public class MapManager : ScriptableObject
|
|
{
|
|
public ClientList clients;
|
|
public GameObject spawn;
|
|
public GameObject spawn4; //The section to use as a spawn-point for games with 2-5 players
|
|
public GameObject spawn8; //The section to use a spawn-point for games with 5-8 players
|
|
//In 5-player games, we choose between them at random
|
|
public List<MapSection> sections; //The list of sections to choose from after starting
|
|
//Split up the inspector lists to make them easier to manage. They'll be combined on initialisation
|
|
public List<SectionList> sectionLists;
|
|
|
|
public int minConns = 2; //The minimum number of valid connections between two map sections for them to be allowed to link up
|
|
|
|
public List<MapSection> activeSections; //The list of sections that have been placed on the map (and not removed)
|
|
MapSection lastSection; //Which map-section was most recently added?
|
|
public float startX; //The x-position of the current start of the track
|
|
float startXinit = -16.0f;
|
|
float endX; //The x-position of the current end of the track
|
|
int totalSections; //How many sections have been added? Including ones that have been deleted
|
|
|
|
int initialPlayerCount;
|
|
int diffStart = 0; //Initial difficulty rating
|
|
int diffCap = 5; //The highest the difficulty rating can go
|
|
int difficulty; //Current difficulty rating
|
|
|
|
int widthMin; //The minimum widthIn that we want for a new map section
|
|
int widthMax; //The maximum widthIn that we want for a new map section
|
|
int widthMinMin = 3; //The minimum to which widthMin can be reduced
|
|
int widthMaxMin = 5; //The minimum to which widthMax can be reduced
|
|
|
|
public void init()
|
|
{
|
|
sections = new List<MapSection>();
|
|
startX = startXinit;
|
|
endX = startX;
|
|
totalSections = 0;
|
|
|
|
foreach (SectionList sectionList in sectionLists)
|
|
{
|
|
foreach (MapSection section in sectionList.sectionList)
|
|
{
|
|
sections.Add(section);
|
|
}
|
|
}
|
|
|
|
initialPlayerCount = clients.ConnectedClients.Count;
|
|
activeSections = new List<MapSection>();
|
|
|
|
if (initialPlayerCount < 5)
|
|
{
|
|
addSection(spawn4.GetComponent<MapSection>());
|
|
}
|
|
else if (initialPlayerCount > 5)
|
|
{
|
|
addSection(spawn8.GetComponent<MapSection>());
|
|
}
|
|
else
|
|
{
|
|
if (Random.Range(0.0f, 1.0f) < 0.5f)
|
|
{
|
|
addSection(spawn4.GetComponent<MapSection>());
|
|
}
|
|
else
|
|
{
|
|
addSection(spawn8.GetComponent<MapSection>());
|
|
}
|
|
}
|
|
|
|
widthMin = activeSections[0].widthOut - 2;
|
|
widthMax = activeSections[0].widthOut + 2;
|
|
difficulty = diffStart;
|
|
|
|
switch (initialPlayerCount)
|
|
{
|
|
case 2:
|
|
foreach (GameObject spawnBlock in lastSection.spawns2)
|
|
{
|
|
spawnBlock.GetComponent<Block>().isSpawnable = true;
|
|
}
|
|
break;
|
|
case 3:
|
|
foreach (GameObject spawnBlock in lastSection.spawns3)
|
|
{
|
|
spawnBlock.GetComponent<Block>().isSpawnable = true;
|
|
}
|
|
break;
|
|
case 4:
|
|
foreach (GameObject spawnBlock in lastSection.spawns4)
|
|
{
|
|
spawnBlock.GetComponent<Block>().isSpawnable = true;
|
|
}
|
|
break;
|
|
case 5:
|
|
foreach (GameObject spawnBlock in lastSection.spawns5)
|
|
{
|
|
spawnBlock.GetComponent<Block>().isSpawnable = true;
|
|
}
|
|
break;
|
|
case 6:
|
|
foreach (GameObject spawnBlock in lastSection.spawns6)
|
|
{
|
|
spawnBlock.GetComponent<Block>().isSpawnable = true;
|
|
}
|
|
break;
|
|
case 7:
|
|
foreach (GameObject spawnBlock in lastSection.spawns7)
|
|
{
|
|
spawnBlock.GetComponent<Block>().isSpawnable = true;
|
|
}
|
|
break;
|
|
case 8:
|
|
foreach (GameObject spawnBlock in lastSection.spawns8)
|
|
{
|
|
spawnBlock.GetComponent<Block>().isSpawnable = true;
|
|
}
|
|
break;
|
|
default:
|
|
foreach (GameObject spawnBlock in lastSection.spawns1)
|
|
{
|
|
spawnBlock.GetComponent<Block>().isSpawnable = true;
|
|
}
|
|
break;
|
|
}
|
|
checkForward();
|
|
}
|
|
|
|
void chooseNextSection()
|
|
{
|
|
Debug.Log("<b>Choosing next section </b>");
|
|
//First, we determine which sections are valid
|
|
List<MapSection> validSections = new List<MapSection>();
|
|
|
|
updateCriteria(); //We update the section selection criteria for the current gamestate
|
|
|
|
int count = -1;
|
|
|
|
foreach (MapSection section in sections)
|
|
{
|
|
count++;
|
|
if (section == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Debug.Log("Checking section " + section.name + ", weight = " + section.weight);
|
|
|
|
if (section.weight > 0 && checkSegments(section))
|
|
{
|
|
//If a segment is a valid continuation of the current most-recent segment, add it to the list
|
|
//Sections with higher weights get more entries => higher chance of being picked
|
|
for (int i = 0; i < section.weight; i++)
|
|
{
|
|
validSections.Add(section);
|
|
}
|
|
}
|
|
}
|
|
//Having generated our list, we choose a random segment from it
|
|
int selectedIndex = Random.Range(0, validSections.Count);
|
|
Debug.Log("<b>Validmap sections: </b>" + validSections.Count);
|
|
Debug.Log("<b>Selected section: </b>" + selectedIndex);
|
|
|
|
MapSection nextSection = validSections[selectedIndex];
|
|
addSection(nextSection);
|
|
}
|
|
|
|
void addSection(MapSection section)
|
|
{
|
|
//Instantiate new section at x = endX
|
|
Vector3 pos = new Vector3(endX, 0.0f, 0.0f);
|
|
GameObject newSection = (GameObject)Instantiate(section.gameObject, pos, Quaternion.identity);
|
|
|
|
MapSection newSectionScript = newSection.GetComponent<MapSection>();
|
|
|
|
newSectionScript.InitSection(activeSections.Count);
|
|
newSection.name = newSectionScript.name;
|
|
activeSections.Add(newSectionScript);
|
|
lastSection = newSectionScript;
|
|
|
|
endX += newSectionScript.length;
|
|
totalSections++;
|
|
}
|
|
|
|
bool checkSegments(MapSection second)
|
|
{
|
|
return checkSegments(this.lastSection, second);
|
|
}
|
|
|
|
bool checkSegments(MapSection first, MapSection second)
|
|
{
|
|
Debug.Log("Checking " + first.name + " & " + second.name + ". DiffMin = " + second.difficultyMin + ", DiffMax = " + second.difficultyMax);
|
|
int connections = 0;
|
|
//No more than one link section in a row
|
|
if (first.length == 1 && second.length == 1)
|
|
{
|
|
Debug.Log("Disqualified: repeated link sections");
|
|
return false;
|
|
}
|
|
|
|
if (second.difficultyMax < difficulty || second.difficultyMin > difficulty) //Check that we're in the right difficulty range for this section
|
|
{
|
|
Debug.Log("Disqualified: wrong difficulty");
|
|
return false;
|
|
}
|
|
|
|
if (second.widthIn < widthMin || second.widthIn > widthMax) //And that it's in the right width range
|
|
{
|
|
Debug.Log("Disqualified: wrong width");
|
|
return false;
|
|
}
|
|
|
|
foreach (GameObject exit in first.exits)
|
|
{
|
|
foreach (GameObject entry in second.entrances)
|
|
{
|
|
if (checkConnection(exit, entry))
|
|
{
|
|
connections++;
|
|
}
|
|
}
|
|
}
|
|
|
|
Debug.Log("Check result: " + (connections >= minConns));
|
|
|
|
return (connections >= minConns);
|
|
}
|
|
|
|
bool checkConnection(GameObject exit, GameObject entry)
|
|
{
|
|
//If the squares being checked don't line up, the connection is invalid
|
|
if (exit.transform.localPosition.z != entry.transform.localPosition.z)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//If both components require jumping (pits or water), the connection is invalid
|
|
//It's technically possible to cross two water blocks, but we don't count that
|
|
if (requiresJump(exit) && requiresJump(entry))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Since we currently don't let people jump over walls, if either block is a wall, the connection is invalid
|
|
if (isWall(exit) || isWall(entry))
|
|
{
|
|
return false;
|
|
}
|
|
//If we've passed all these tests, the connection is valid!
|
|
return true;
|
|
}
|
|
|
|
bool requiresJump(GameObject block)
|
|
{
|
|
if (block.GetComponent<Block>() == null) //The object must be a pit trap
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return block.GetComponent<Block>().isWater; //If it's not a pit, then whether it requires jumping depends on whether it's water or not
|
|
}
|
|
|
|
bool isWall(GameObject block)
|
|
{
|
|
if (block.GetComponent<Block>() == null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return !(block.GetComponent<Block>().is_Walkable);
|
|
}
|
|
|
|
//Check whether it's time to extend the track forward
|
|
void checkForward()
|
|
{
|
|
//We check if the end of the last section of track is in sight
|
|
Vector3 trackEnd = new Vector3(endX, 0.0f); //Get the middle of the end of the last track section
|
|
|
|
//If it is, then we add a new section
|
|
if (checkView(trackEnd))
|
|
{
|
|
chooseNextSection();
|
|
checkForward();
|
|
}
|
|
}
|
|
|
|
//Check whether it's time to delete the oldest section of active track
|
|
void checkBack()
|
|
{
|
|
//We check if the end of the first section of track is still in sight
|
|
Vector3 firstSectionEnd = new Vector3(startX + activeSections[0].length, 0.0f); //Get the middle of the end of the first track section
|
|
|
|
//If it's not, then we remove it
|
|
if (!(checkView(firstSectionEnd)))
|
|
{
|
|
spawn.GetComponent<blockSpawn>().updatePositions((int)startX + activeSections[0].length);
|
|
startX += activeSections[0].length;
|
|
spawn.GetComponent<blockSpawn>().updatePositions((int)startX);
|
|
activeSections[0].destroySection();
|
|
activeSections.RemoveAt(0);
|
|
}
|
|
}
|
|
|
|
//Check whether a point is in sight or not
|
|
bool checkView(Vector3 point)
|
|
{
|
|
Vector3 screenPoint = Camera.main.WorldToViewportPoint(point); //Map it into viewport space
|
|
|
|
//The camera's field of view is represented by 0 > (x, y) < 1, with z being the distance from the camera
|
|
return (screenPoint.z > 0 && screenPoint.x > 0 && screenPoint.x < 1 && screenPoint.y > 0 && screenPoint.y < 1);
|
|
}
|
|
|
|
//Checks in both directions for sections needing to be added or removed
|
|
public void checkTrack()
|
|
{
|
|
checkForward();
|
|
checkBack();
|
|
}
|
|
|
|
//Updates minimum and maximum difficulty, width, etc, based on current gamestate
|
|
public void updateCriteria()
|
|
{
|
|
//Start with base values
|
|
difficulty = diffStart;
|
|
//By default, we can add a section 1 tile wider or narrower on either side than the last section
|
|
widthMin = lastSection.widthOut - 2;
|
|
widthMax = lastSection.widthOut + 2;
|
|
|
|
/* Calculate min & max difficulties & width modifications
|
|
* We recalculate from scratch each time (that is,
|
|
* each time a section is added) so as to avoid having
|
|
* to track which one-off increase has been applied
|
|
* and which hasn't
|
|
*/
|
|
|
|
//As the number of players shrinks, we ramp up the difficulty and contract the track
|
|
if (initialPlayerCount > 0)
|
|
{
|
|
if (clients.ConnectedClients.Count <= (float)(0.5f * initialPlayerCount))
|
|
{
|
|
difficulty++;
|
|
widthMin -= 2;
|
|
widthMax -= 2;
|
|
}
|
|
|
|
if (clients.ConnectedClients.Count <= (float)(0.33f * initialPlayerCount))
|
|
{
|
|
difficulty += 2;
|
|
widthMin -= 2;
|
|
widthMax -= 2;
|
|
}
|
|
}
|
|
//Ramp up the difficulty as the track extends
|
|
difficulty += ((int)endX - (int)startX) / 7;
|
|
|
|
//Apply caps
|
|
if (difficulty > diffCap)
|
|
{
|
|
difficulty = diffCap;
|
|
}
|
|
|
|
if (widthMin < widthMinMin)
|
|
{
|
|
widthMin = widthMinMin;
|
|
}
|
|
|
|
if (widthMax < widthMaxMin)
|
|
{
|
|
widthMax = widthMaxMin;
|
|
}
|
|
|
|
Debug.Log("Difficulty = " + difficulty + ", widthMin = " + widthMin + ", widthMax = " + widthMax);
|
|
}
|
|
|
|
// Update is called once per frame
|
|
void Update()
|
|
{
|
|
checkTrack();
|
|
}
|
|
}
|