Initial commit

This commit is contained in:
LadyAliceMargatroid 2024-07-25 21:46:18 -07:00
parent 1af2b57c1c
commit eebf00988e
30 changed files with 1883 additions and 21 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
Updates/
Pictures/
bin/
.vs/
obj/
/ScarletMansion/ScarletMansion/scarletmansion

25
DunGenPlus/DunGenPlus.sln Normal file
View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.10.35013.160
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DunGenPlus", "DunGenPlus\DunGenPlus.csproj", "{13CDE60E-1975-463B-9DA1-CCB3F3EBABD8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{13CDE60E-1975-463B-9DA1-CCB3F3EBABD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{13CDE60E-1975-463B-9DA1-CCB3F3EBABD8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{13CDE60E-1975-463B-9DA1-CCB3F3EBABD8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{13CDE60E-1975-463B-9DA1-CCB3F3EBABD8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F8E7870E-8528-46EE-84B6-8328BFCC093B}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DunGen;
using DunGen.Graph;
namespace DunGenPlus
{
public class API {
public static bool AddDunGenExtender(DungeonFlow dungeonFlow, DunGenExtender dunGenExtender) {
if (Plugin.DunGenExtenders.ContainsKey(dungeonFlow)) {
Plugin.logger.LogWarning($"Already contains DunGenExtender asset for {dungeonFlow.name}");
return false;
}
Plugin.DunGenExtenders.Add(dungeonFlow, dunGenExtender);
Plugin.logger.LogInfo($"Added DunGenExtender asset for {dungeonFlow.name}");
return true;
}
}
}

View File

@ -0,0 +1,36 @@
using DunGenPlus;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BepInEx;
using UnityEngine;
using LethalLevelLoader;
namespace DunGenPlus {
internal class Assets {
public static void LoadAssets(){
foreach (string text in Directory.GetFiles(Paths.PluginPath, "*.lethalbundle", SearchOption.AllDirectories)) {
FileInfo fileInfo = new FileInfo(text);
LethalLevelLoader.AssetBundleLoader.AddOnLethalBundleLoadedListener(AutoAddLethalBundle, fileInfo.Name);
}
}
static void AutoAddLethalBundle(AssetBundle assetBundle){
var extenders = assetBundle.LoadAllAssets<DunGenExtender>();
var content = assetBundle.LoadAllAssets<ExtendedContent>();
if (content.Length == 0) {
Plugin.logger.LogWarning($".lethalbundle does not contain any ExtendedContent. Unless you are manually creating and adding your ExtendedDungeonFlow with code, the DunGenExtender will probably not work.");
}
foreach(var e in extenders){
API.AddDunGenExtender(e.DungeonFlow, e);
}
}
}
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DunGenPlus.Collections {
[System.Serializable]
public class DunGenExtenderEvents {
public ExtenderEvent<DunGenExtenderProperties> OnModifyDunGenExtenderProperties = new ExtenderEvent<DunGenExtenderProperties>();
}
}

View File

@ -0,0 +1,131 @@
using DunGen.Graph;
using DunGen;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace DunGenPlus.Collections {
[System.Serializable]
public class DunGenExtenderProperties {
[Header("Main Path")]
[Tooltip("The number of main paths.\n\n1 means no additional main paths\n3 means two additional main paths\netc.")]
[Range(1, 9)]
public int MainPathCount = 1;
[Tooltip("The Tile Prefab where the additional main paths will start from. Cannot be null if MainPathCount is more than 1.\n\nHighly advice for this Tile Prefab to have multiple doorways.")]
public GameObject MainRoomTilePrefab;
[Header("Dungeon Bounds")]
[Tooltip("If enabled, restricts the dungeon's generation to the bounds described below.\n\nThis will help in condensing the dungeon, but it will increase the chance of dungeon generation failure (potentially guarantees failure if the bounds is too small).")]
public bool UseDungeonBounds = false;
[Tooltip("The base size of the bounds.")]
public Vector3 DungeonSizeBase = new Vector3(120f, 40f, 80f);
[Tooltip("The factor that's multiplied with the base size AND the dungeon's size. The resulting value is added to the base size of the bounds.\n\n0 means that the bound size is not influenced by the dungeon's size and is therefore a constant.")]
public Vector3 DungeonSizeFactor = new Vector3(1f, 0f, 1f);
[Tooltip("The base positional offset of the bounds.")]
public Vector3 DungeonPositionOffset;
[Tooltip("The pivot of the bounds.")]
public Vector3 DungeonPositionPivot = new Vector3(0.5f, 0f, 0.5f);
[Header("Archetypes on Normal Nodes")]
[Tooltip("If enabled, adds archetypes to the normal nodes in the DungeonFlow.\n\nBy default, nodes cannot have branching paths since they don't have archetype references. This allows nodes to have branching paths.")]
public bool AddArchetypesToNormalNodes = true;
public List<NodeArchetype> NormalNodeArchetypes;
internal Dictionary<string, NodeArchetype> _normalNodeArchetypesDictioanry;
internal NodeArchetype _defaultNodeArchetype;
[Header("Doorway Sisters")]
[Tooltip("If enabled, the DoorwaySisters component will become active.\n\nThe component prevents an intersecting doorway from generating if it's 'sister' doorway already generated and both doorways would lead to the same neighboring tile.\n\nThis is designed for the scenario where, two neighboring doorways would lead to the same tile, one doorway is a locked door and the other is an open doorway. This would defeat the purpose of the locked door, and such as, this feature exists if needed.\n\nThis feature slows down dungeon generation slightly when enabled.")]
public bool UseDoorwaySisters = false;
[Header("Line Randomizer")]
[Tooltip("If enabled, every archetype in LineRandomizerArchetypes will have the last LineRandomizerTakeCount tilesets replaced by a randomly selected set of tilesets from LineRandomizerTileSets. This applies for both archetype's TileSets and BranchCapTileSets.\n\nThis is designed for the scenario where dungeon generation takes a long time due to the combination of too many tiles and/or doorways in those tiles. This can reduce dungeon generation time while keeping some of the randomness of dungeon generation.\n\nAs stated previously, this WILL replace the last LineRandomizerTakeCount tilesets in the archetype's TileSets and BranchCapTileSets. As such you must guarantee that those elements can be replaced.")]
public bool UseLineRandomizer = false;
[Tooltip("The archetypes whose tilesets will be replaced.\n\nThese archetypes should ideally used in the Lines section of DungeonFlow, but it's a free country.")]
public List<DungeonArchetype> LineRandomizerArchetypes;
[Tooltip("The tilesets that will be used for replacement.")]
public List<TileSet> LineRandomizerTileSets;
[Tooltip("The amount of tilesets that will be replaced from the archetypes, starting from the last element to the first element.\n\nAs stated previously, this WILL replace the tilesets in the archetype's TileSets and BranchCapTileSets. As such you must guarantee that those elements can be replaced.")]
public int LineRandomizerTakeCount = 3;
[Header("Max Shadows Request")]
[Tooltip("If enabled, updates the MaxShadowsRequest to MaxShadowsRequestAmount when your dungeon loads.\n\nThis is designed for the scenario where your dungeon, for whatever reason, has too many lights nearby and causes the annoying 'Max shadow requests count reached' warning to spam the logs.")]
public bool UseMaxShadowsRequestUpdate = false;
[Tooltip("The amount of MaxShadowsRequest.\n\n4 is the game's default value. I find 8 to be more than acceptable.")]
public int MaxShadowsRequestAmount = 8;
internal void SetupProperties(DungeonGenerator generator){
_normalNodeArchetypesDictioanry = new Dictionary<string, NodeArchetype>();
_defaultNodeArchetype = null;
foreach(var n in NormalNodeArchetypes) {
if (_normalNodeArchetypesDictioanry.ContainsKey(n.label)) {
Plugin.logger.LogError($"Label {n.label} already exists. Ignoring latest entry.");
continue;
}
_normalNodeArchetypesDictioanry.Add(n.label, n);
if (string.IsNullOrWhiteSpace(n.label)) {
_defaultNodeArchetype = n;
}
}
}
internal DungeonArchetype GetRandomArchetype(string label, RandomStream randomStream) {
NodeArchetype node;
if (!_normalNodeArchetypesDictioanry.TryGetValue(label, out node)) {
node = _defaultNodeArchetype;
}
if (node != null) {
var archetypes = node.archetypes;
var count = archetypes.Count;
if (count == 0) return null;
var index = randomStream.Next(0, count);
return archetypes[index];
}
return null;
}
internal Bounds GetDungeonBounds(float dungeonScale) {
var size = DungeonSizeBase + Vector3.Scale(DungeonSizeBase * (dungeonScale - 1), DungeonSizeFactor);
var offset = DungeonPositionOffset + Vector3.Scale(size, DungeonPositionPivot);
return new Bounds(offset, size);
}
internal DunGenExtenderProperties Copy() {
var copy = new DunGenExtenderProperties();
copy.MainPathCount = MainPathCount;
copy.MainRoomTilePrefab = MainRoomTilePrefab;
copy.UseDungeonBounds = UseDungeonBounds;
copy.DungeonSizeBase = DungeonSizeBase;
copy.DungeonSizeFactor = DungeonSizeFactor;
copy.DungeonPositionOffset = DungeonPositionOffset;
copy.DungeonPositionPivot = DungeonPositionPivot;
copy.AddArchetypesToNormalNodes = AddArchetypesToNormalNodes;
copy.NormalNodeArchetypes = NormalNodeArchetypes;
copy.UseDoorwaySisters = UseDoorwaySisters;
copy.UseLineRandomizer = UseLineRandomizer;
copy.LineRandomizerTileSets = LineRandomizerTileSets;
copy.LineRandomizerArchetypes = LineRandomizerArchetypes;
copy.LineRandomizerTakeCount = LineRandomizerTakeCount;
copy.UseMaxShadowsRequestUpdate = UseMaxShadowsRequestUpdate;
copy.MaxShadowsRequestAmount = MaxShadowsRequestAmount;
return copy;
}
}
}

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DunGenPlus.Collections {
public class ExtenderEvent<T> {
internal event ParameterEvent onParameterEvent;
public void Invoke(T param) {
onParameterEvent?.Invoke(param);
}
public void AddListener(ParameterEvent listener) {
onParameterEvent += listener;
}
public void RemoveListener(ParameterEvent listener) {
onParameterEvent -= listener;
}
public delegate void ParameterEvent(T param);
}
}

View File

@ -0,0 +1,19 @@
using DunGen;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace DunGenPlus.Collections {
[System.Serializable]
public class NodeArchetype {
[Tooltip("The normal node with this label will gain a randomly chosen archetype.\n\nIf empty, this becomes the default choice for any normal node without a NodeArchetype specified in this list.")]
public string label;
[Tooltip("The list of archetypes. One will be randomly chosen.")]
public List<DungeonArchetype> archetypes;
}
}

View File

@ -0,0 +1,72 @@
using DunGen;
using DunGenPlus.Components.DoorwayCleanupScripting;
using DunGenPlus.Managers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace DunGenPlus.Components {
public class DoorwayCleanup : MonoBehaviour, IDungeonCompleteReceiver {
[Header("Doorway References")]
[Tooltip("The doorway reference.")]
public Doorway doorway;
[Tooltip("The connectors scene objects of the doorway.\n\nHighly advise to empty the corresponding list in the doorway.")]
public List<GameObject> connectors;
[Tooltip("The blockers scene objects of the doorway.\n\nHighly advise to empty the corresponding list in the doorway.")]
public List<GameObject> blockers;
[Tooltip("The doorway gameobject target for the DoorwayCleanupScripts. Can be null.")]
public GameObject doorwayGameObject;
[Header("Overrides")]
[Tooltip("Mainly for code purposes. Forces the connectors to be active.")]
public bool overrideConnector;
[Tooltip("Mainly for code purposes. Forces the blockers to be active.")]
public bool overrideBlocker;
[Tooltip("Mainly for code purposes. Forces the doorway gameobject to be disabled.")]
public bool overrideNoDoorway;
public void OnDungeonComplete(Dungeon dungeon) {
SetBlockers(true);
DoorwayManager.AddDoorwayCleanup(this);
}
public void Cleanup(){
// start up like in original
SwitchConnectorBlocker(doorway.ConnectedDoorway != null);
var cleanupList = GetComponentsInChildren<DoorwayCleanupScript>();
foreach(var c in cleanupList) c.Cleanup(this);
if (overrideNoDoorway) SwitchDoorwayGameObject(false);
// clean up like in original
foreach(var c in connectors){
if (!c.activeSelf) UnityEngine.Object.DestroyImmediate(c, false);
}
foreach(var b in blockers){
if (!b.activeSelf) UnityEngine.Object.DestroyImmediate(b, false);
}
}
public void SetBlockers(bool state){
foreach(var b in blockers) b.SetActive(state);
}
public void SwitchConnectorBlocker(bool isConnector){
if (overrideConnector) isConnector = true;
if (overrideBlocker) isConnector = false;
foreach(var c in connectors) c.SetActive(isConnector);
foreach(var b in blockers) b.SetActive(!isConnector);
}
public void SwitchDoorwayGameObject(bool isActive){
doorwayGameObject?.SetActive(isActive);
}
}
}

View File

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace DunGenPlus.Components.DoorwayCleanupScripting {
public class DCSConnectorBlockerSpawnedPrefab : DoorwayCleanupScript {
public enum Action { SwitchToConnector, SwitchToBlocker };
[Header("Calls switch action\nif Doorway instantiates a Connector/Blocker prefab with the target's name")]
[Header("Switch Action")]
public Action switchAction;
[Header("Target")]
public GameObject target;
public override void Cleanup(DoorwayCleanup parent) {
var result = false;
foreach(Transform t in parent.doorway.transform){
if (t.gameObject.activeSelf && t.name.Contains(target.name)) {
result = true;
break;
}
}
if (result) {
parent.SwitchConnectorBlocker(switchAction == Action.SwitchToConnector);
}
}
}
}

View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace DunGenPlus.Components.DoorwayCleanupScripting {
public class DCSRemoveDoorwayConnectedDoorway : DoorwayCleanupScriptDoorwayCompare {
[Header("Removes Doorway Gameobject\nif the neighboring doorway's priority matches the operation comparison")]
[Header("Operation Comparison")]
public int doorwayPriority;
public Operation operation = Operation.Equal;
public override void Cleanup(DoorwayCleanup parent) {
var doorway = parent.doorway;
if (doorway.connectedDoorway == null) return;
var result = GetOperation(operation).Invoke(doorway.connectedDoorway, doorwayPriority);
if (result) {
parent.SwitchDoorwayGameObject(false);
}
}
}
}

View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace DunGenPlus.Components.DoorwayCleanupScripting {
public class DCSRemoveDoorwaySpawnedPrefab : DoorwayCleanupScript {
[Header("Removes Doorway Gameobject\nif Doorway instantiates a Connector/Blocker prefab with the target's name")]
[Header("Target")]
public GameObject target;
public override void Cleanup(DoorwayCleanup parent) {
var result = false;
foreach(Transform t in parent.doorway.transform){
if (t.gameObject.activeSelf && t.name.Contains(target.name)) {
result = true;
break;
}
}
if (result) {
parent.SwitchDoorwayGameObject(false);
}
}
}
}

View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace DunGenPlus.Components.DoorwayCleanupScripting {
public class DCSRemoveGameObjectsConnectedDoorway : DoorwayCleanupScriptDoorwayCompare {
[Header("Removes target GameObjects\nif the neighboring doorway's priority matches the operation comparison")]
[Header("Operation Comparison")]
public int doorwayPriority;
public Operation operation = Operation.Equal;
[Header("Targets")]
public List<GameObject> targets;
public override void Cleanup(DoorwayCleanup parent) {
var doorway = parent.doorway;
if (doorway.connectedDoorway == null) return;
var result = GetOperation(operation).Invoke(doorway.connectedDoorway, doorwayPriority);
if (result) {
foreach(var t in targets) t.SetActive(false);
}
}
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace DunGenPlus.Components.DoorwayCleanupScripting {
public abstract class DoorwayCleanupScript : MonoBehaviour {
public abstract void Cleanup(DoorwayCleanup parent);
}
}

View File

@ -0,0 +1,44 @@
using DunGen;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DunGenPlus.Components.DoorwayCleanupScripting {
public abstract class DoorwayCleanupScriptDoorwayCompare : DoorwayCleanupScript {
public enum Operation { Equal, NotEqual, LessThan, GreaterThan }
public Func<Doorway, int, bool> GetOperation(Operation operation){
switch(operation){
case Operation.Equal:
return EqualOperation;
case Operation.NotEqual:
return NotEqualOperation;
case Operation.LessThan:
return LessThanOperation;
case Operation.GreaterThan:
return GreaterThanOperation;
}
return null;
}
public bool EqualOperation(Doorway other, int doorwayPriority){
return other.DoorPrefabPriority == doorwayPriority;
}
public bool NotEqualOperation(Doorway other, int doorwayPriority){
return other.DoorPrefabPriority != doorwayPriority;
}
public bool LessThanOperation(Doorway other, int doorwayPriority){
return other.DoorPrefabPriority < doorwayPriority;
}
public bool GreaterThanOperation(Doorway other, int doorwayPriority){
return other.DoorPrefabPriority > doorwayPriority;
}
}
}

View File

@ -0,0 +1,64 @@
using DunGen;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace DunGenPlus.Components {
public class DoorwaySisters : MonoBehaviour {
private Doorway _self;
public Doorway Self {
get {
if (_self == null) {
_self = GetComponent<Doorway>();
}
return _self;
}
}
[Tooltip("The list of 'sister' doorways.\n\nUseDoorwaySisters must be toggled in DunGenExtender for this component to be used.\n\nThis doorway will not generate if it's an intersecting doorway, any of it's 'sister' doorways are generated, and both this doorway and the 'sister' doorway lead to the same tile.")]
public List<Doorway> sisters;
void OnValidate(){
var sis = sisters.Select(s => s.GetComponent<DoorwaySisters>());
foreach(var s in sis) {
if (s == null) continue;
s.TryAddSisterDoorway(Self);
}
}
public void TryAddSisterDoorway(Doorway doorway){
if (sisters.Contains(doorway)) return;
sisters.Add(doorway);
}
public void OnDrawGizmosSelected(){
var center = transform.position + Vector3.up;
if (sisters == null) return;
foreach(var sis in sisters){
var target = sis.transform.position + Vector3.up;
var comp = sis.GetComponent<DoorwaySisters>();
var self = Self;
if (self == null) {
Gizmos.color = Color.magenta;
} else if (comp == null || comp.sisters == null){
Gizmos.color = Color.yellow;
} else if (!comp.sisters.Contains(self)) {
Gizmos.color = Color.red;
} else {
Gizmos.color = Color.green;
}
Gizmos.DrawLine(center, target);
Gizmos.DrawSphere((center + target) * 0.5f, 0.25f);
}
}
}
}

View File

@ -0,0 +1,36 @@
using DunGen;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace DunGenPlus.Components {
public class MainRoomDoorwayGroups : MonoBehaviour {
[System.Serializable]
public class DoorwayList {
[Tooltip("For organizing purposes. Has no effect.")]
public string name;
[Tooltip("The group of doorways.")]
public List<Doorway> doorways;
public bool Contains(Doorway target) {
return doorways.Contains(target);
}
}
[Tooltip("When an additional main path is being generated, it will get the doorway used for the previous main path, find it's corresponding group below, and prevents the dungeon generation from using that group's doorways until the main paths are all generated.\n\nIf you want this feature, this must be attached to the tile that will act as the MainRoomTilePrefab.\n\nThis is designed for the scenario where you would like the main paths to be generated more evenly throughout the MainRoomTilePrefab.")]
public List<DoorwayList> doorwayLists;
public List<Doorway> doorwayListFirst => doorwayLists.Count > 0 ? doorwayLists[0].doorways : null;
public List<Doorway> GrabDoorwayGroup(Doorway target){
foreach(var a in doorwayLists){
if (a.Contains(target)) return a.doorways;
}
return null;
}
}
}

View File

@ -0,0 +1,50 @@
using DunGen;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace DunGenPlus.Components.Props
{
public class SpawnSyncedObjectCycle : MonoBehaviour, IDungeonCompleteReceiver {
public static int cycle;
public static Dictionary<int, int> cycleDictionary;
[Tooltip("The SpawnSyncedObject reference.\n\nWhen the dungeon generation finishes, the spawnPrefab of the referenced SpawnSyncedObject will change to one of the Props based on a cycle. The starting value is random.\n\nThis is designed for the scenario where you have multiple very similar networked gameobjects that serve the same purpose, and you just want them all to spawn equally for diversity sake.")]
public SpawnSyncedObject Spawn;
[Tooltip("The unique id for this script's cycle.\n\nWhen the dungeon generation finishes, a random cycle value is calculated for each Id. Each script will reference their Id's corresponding cycle value to determine their Prop, and advance the cycle value by 1.")]
public int Id;
[Tooltip("The list of props that would selected based on a cycle.")]
public List<GameObject> Props = new List<GameObject>();
void Reset(){
Spawn = GetComponent<SpawnSyncedObject>();
}
public static void UpdateCycle(int value){
Plugin.logger.LogInfo($"Updating SpawnSyncedObject start cycle to {value}");
cycle = value;
cycleDictionary = new Dictionary<int, int>();
}
public int GetCycle(int id){
if (!cycleDictionary.TryGetValue(id, out var value)){
value = cycle;
cycleDictionary.Add(id, value);
}
cycleDictionary[id] = value + 1;
Plugin.logger.LogInfo($"Cycle{id}: {value}");
return value;
}
public void OnDungeonComplete(Dungeon dungeon) {
var index = GetCycle(Id) % Props.Count;
var prefab = Props[index];
Spawn.spawnPrefab = prefab;
}
}
}

View File

@ -0,0 +1,22 @@
using DunGen;
using DunGen.Graph;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using DunGenPlus.Collections;
namespace DunGenPlus {
[CreateAssetMenu(fileName = "DunGenExtender", menuName = "DunGenExtender", order = 1)]
public class DunGenExtender : ScriptableObject {
[Tooltip("DunGenExtender will only influence this DungeonFlow")]
public DungeonFlow DungeonFlow;
public DunGenExtenderProperties Properties;
public DunGenExtenderEvents Events;
}
}

View File

@ -0,0 +1,119 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{13CDE60E-1975-463B-9DA1-CCB3F3EBABD8}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>DunGenPlus</RootNamespace>
<AssemblyName>DunGenPlus</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>embedded</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>..\..\..\Libraries\0Harmony.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\..\..\Libraries\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp-publicized">
<HintPath>..\..\..\Libraries\Assembly-CSharp-publicized.dll</HintPath>
</Reference>
<Reference Include="BepInEx">
<HintPath>..\..\..\Libraries\BepInEx.dll</HintPath>
</Reference>
<Reference Include="BepInEx.Harmony">
<HintPath>..\..\..\Libraries\BepInEx.Harmony.dll</HintPath>
</Reference>
<Reference Include="LethalLevelLoader">
<HintPath>..\..\..\Libraries\LethalLevelLoader.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="Unity.Collections">
<HintPath>..\..\..\Libraries\Unity.Collections.dll</HintPath>
</Reference>
<Reference Include="Unity.RenderPipelines.Core.Runtime, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\Libraries\Unity.RenderPipelines.Core.Runtime.dll</HintPath>
</Reference>
<Reference Include="Unity.RenderPipelines.HighDefinition.Config.Runtime, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\Libraries\Unity.RenderPipelines.HighDefinition.Config.Runtime.dll</HintPath>
</Reference>
<Reference Include="Unity.RenderPipelines.HighDefinition.Runtime, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\Libraries\Unity.RenderPipelines.HighDefinition.Runtime.dll</HintPath>
</Reference>
<Reference Include="UnityEngine">
<HintPath>..\..\..\Libraries\UnityEngine.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.AssetBundleModule, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\Libraries\UnityEngine.AssetBundleModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\..\..\Libraries\UnityEngine.CoreModule.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="API.cs" />
<Compile Include="Assets.cs" />
<Compile Include="Collections\DunGenExtenderEvents.cs" />
<Compile Include="Collections\ExtenderEvent.cs" />
<Compile Include="Collections\NodeArchetype.cs" />
<Compile Include="Components\DoorwayCleanup.cs" />
<Compile Include="Components\DoorwayCleanupScripting\DCSRemoveDoorwayConnectedDoorway.cs" />
<Compile Include="Components\DoorwayCleanupScripting\DCSRemoveDoorwaySpawnedPrefab.cs" />
<Compile Include="Components\DoorwayCleanupScripting\DCSRemoveGameObjectsConnectedDoorway.cs" />
<Compile Include="Components\DoorwayCleanupScripting\DoorwayCleanupScript.cs" />
<Compile Include="Components\DoorwayCleanupScripting\DCSConnectorBlockerSpawnedPrefab.cs" />
<Compile Include="Components\DoorwayCleanupScripting\DoorwayCleanupScriptDoorwayCompare.cs" />
<Compile Include="Components\DoorwaySisters.cs" />
<Compile Include="Components\MainRoomDoorwayGroups.cs" />
<Compile Include="Components\Props\SpawnSyncedObjectCycle.cs" />
<Compile Include="DunGenExtender.cs" />
<Compile Include="Collections\DunGenExtenderProperties.cs" />
<Compile Include="Generation\DunGenPlusGenerator.cs" />
<Compile Include="Managers\DoorwayManager.cs" />
<Compile Include="Patches\DoorwayConnectionPatch.cs" />
<Compile Include="Generation\DoorwaySistersRule.cs" />
<Compile Include="Patches\DungeonGeneratorPatch.cs" />
<Compile Include="Plugin.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Utils\TranspilerUtilities.cs" />
<Compile Include="Utils\Utility.cs" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>copy "$(TargetPath)" "C:\Users\Jose Garcia\AppData\Roaming\r2modmanPlus-local\LethalCompany\profiles\SDM Debug\BepInEx\plugins\Unknown-DunGenPlus\$(TargetName).dll"</PostBuildEvent>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,90 @@
using DunGen;
using DunGenPlus.Components;
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DunGenPlus.Generation {
internal static class DoorwaySistersRule {
public class Data {
public DoorwaySisters info;
public List<DoorwayProxy> proxies;
}
public static Dictionary<Doorway, Data> doorwayDictionary;
public static Dictionary<DoorwayProxy, Data> doorwayProxyDictionary;
public static void UpdateCache(IEnumerable<DoorwayProxy> list){
if (!DunGenPlusGenerator.Active || !DunGenPlusGenerator.Properties.UseDoorwaySisters) return;
Plugin.logger.LogInfo("Updating DoorwayProxy cache for DoorwaySistersRule");
doorwayDictionary = new Dictionary<Doorway, Data>();
doorwayProxyDictionary = new Dictionary<DoorwayProxy, Data>();
foreach(var a in list){
var doorway = a.DoorwayComponent;
if (doorwayDictionary.TryGetValue(doorway, out var data)){
data.proxies.Add(a);
doorwayProxyDictionary.Add(a, data);
} else {
var proxies = new List<DoorwayProxy>();
proxies.Add(a);
var item = new Data {
info = doorway.GetComponent<DoorwaySisters>(),
proxies = proxies
};
doorwayProxyDictionary.Add(a, item);
doorwayDictionary.Add(a.DoorwayComponent, item);
}
}
}
public static bool CanDoorwaysConnect(bool result, TileProxy tileA, TileProxy tileB, DoorwayProxy doorwayA, DoorwayProxy doorwayB){
if (!result) return false;
if (!DunGenPlusGenerator.Active || !DunGenPlusGenerator.Properties.UseDoorwaySisters) return true;
var infoA = doorwayProxyDictionary[doorwayA].info;
var infoB = doorwayProxyDictionary[doorwayB].info;
// deny if any sister doorway is already in use
// cause it feels like dumb otherwise
if (CheckIfSisterActive(infoA, tileB)){
return false;
}
if (CheckIfSisterActive(infoB, tileA)){
return false;
}
// allow like normal
return true;
}
public static bool CheckIfSisterActive(DoorwaySisters info, TileProxy targetTile){
if (info == null || info.sisters == null) return false;
foreach(var sis in info.sisters){
var proxies = doorwayDictionary[sis].proxies;
foreach(var proxy in proxies){
var result = proxy.ConnectedDoorway != null && proxy.ConnectedDoorway.TileProxy == targetTile;
if (result) return true;
}
}
return false;
}
}
}

View File

@ -0,0 +1,346 @@
using DunGen.Graph;
using DunGen;
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections;
using UnityEngine;
using DunGenPlus.Collections;
using DunGenPlus.Components;
using System.Security.Permissions;
using DunGenPlus.Managers;
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;
using BepInEx.Logging;
using static UnityEngine.Rendering.HighDefinition.ScalableSettingLevelParameter;
[assembly: SecurityPermission( SecurityAction.RequestMinimum, SkipVerification = true )]
namespace DunGenPlus.Generation {
internal class DunGenPlusGenerator {
public static DunGenExtender Instance { get; internal set; }
public static DunGenExtenderProperties Properties { get; internal set; }
public static bool Active { get; internal set; }
public static bool ActiveAlternative { get; internal set; }
internal static HDRenderPipelineAsset previousHDRPAsset;
internal static HDRenderPipelineAsset newHDRPAsset;
public static void Activate(DungeonGenerator generator, DunGenExtender extender){
Instance = extender;
Active = true;
ActiveAlternative = true;
var props = extender.Properties.Copy();
Instance.Events.OnModifyDunGenExtenderProperties.Invoke(props);
props.SetupProperties(generator);
Properties = props;
if (Properties.UseDungeonBounds) {
generator.RestrictDungeonToBounds = Properties.UseDungeonBounds;
var bounds = Properties.GetDungeonBounds(generator.LengthMultiplier);
generator.TilePlacementBounds = bounds;
Plugin.logger.LogInfo($"Dungeon Bounds: {bounds}");
}
if (Properties.UseMaxShadowsRequestUpdate) {
Plugin.logger.LogInfo($"Updating HDRP asset: setting max shadows request to {Properties.MaxShadowsRequestAmount}");
try {
previousHDRPAsset = QualitySettings.renderPipeline as HDRenderPipelineAsset;
newHDRPAsset = ScriptableObject.Instantiate(previousHDRPAsset);
var settings = newHDRPAsset.currentPlatformRenderPipelineSettings;
settings.hdShadowInitParams.maxScreenSpaceShadowSlots = Properties.MaxShadowsRequestAmount;
newHDRPAsset.currentPlatformRenderPipelineSettings = settings;
QualitySettings.renderPipeline = newHDRPAsset;
} catch (Exception e) {
Plugin.logger.LogError("Failed to update HDRP asset");
Plugin.logger.LogError(e.ToString());
}
}
DoorwayManager.ResetList();
}
public static void Deactivate(){
Instance = null;
Properties = null;
Active = false;
ActiveAlternative = false;
if (previousHDRPAsset && QualitySettings.renderPipeline == newHDRPAsset) {
Plugin.logger.LogInfo("Restoring original HDRP asset");
QualitySettings.renderPipeline = previousHDRPAsset;
previousHDRPAsset = null;
newHDRPAsset = null;
}
}
public static IEnumerator GenerateAlternativeMainPaths(DungeonGenerator gen) {
var altCount = Properties.MainPathCount - 1;
// default behaviour in case the multiple main paths are not considered
if (!Active) {
ActiveAlternative = false;
yield return gen.Wait(gen.GenerateBranchPaths());
ActiveAlternative = true;
yield break;
}
if (altCount <= 0) {
Plugin.logger.LogInfo($"Switching to default dungeon branch generation due to MainPathCount being {altCount + 1}");
ActiveAlternative = false;
yield return gen.Wait(gen.GenerateBranchPaths());
ActiveAlternative = true;
yield break;
}
if (Properties.MainRoomTilePrefab == null) {
Plugin.logger.LogWarning($"Switching to default dungeon branch generation due to MainRoomTilePrefab being null");
ActiveAlternative = false;
yield return gen.Wait(gen.GenerateBranchPaths());
ActiveAlternative = true;
yield break;
}
var allMainPathTiles = new List<List<TileProxy>>();
allMainPathTiles.Add(gen.proxyDungeon.MainPathTiles.ToList());
// main room is the true main room and not the fake room
// this MUST have multiple doorways as you can imagine
var mainRoom = gen.proxyDungeon.MainPathTiles.FirstOrDefault(t => t.Prefab == Properties.MainRoomTilePrefab);
if (mainRoom == null) {
Plugin.logger.LogWarning($"Switching to default dungeon branch generation due to MainRoomTilePrefab not spawning on the main path");
ActiveAlternative = false;
yield return gen.Wait(gen.GenerateBranchPaths());
ActiveAlternative = true;
yield break;
}
var doorwayGroups = mainRoom.Prefab.GetComponentInChildren<MainRoomDoorwayGroups>();
// index of MaxValue is how we tell which doorway proxy is fake
var fakeDoorwayProxy = new DoorwayProxy(mainRoom, int.MaxValue, mainRoom.doorways[0].DoorwayComponent, Vector3.zero, Quaternion.identity);
// nodes
var nodesSorted = gen.DungeonFlow.Nodes.OrderBy(n => n.Position).ToList();
var startingNodeIndex = nodesSorted.FindIndex(n => n.TileSets.SelectMany(t => t.TileWeights.Weights).Any(t => t.Value == Properties.MainRoomTilePrefab));
if (startingNodeIndex == -1) {
Plugin.logger.LogWarning($"Switching to default dungeon branch generation due to MainRoomTilePrefab not existing in the Nodes' tilesets");
ActiveAlternative = false;
yield return gen.Wait(gen.GenerateBranchPaths());
ActiveAlternative = true;
yield break;
}
//FixDoorwaysToAllFloors(mainRoom, doorwayGroups);
gen.ChangeStatus(GenerationStatus.MainPath);
for (var b = 0; b < altCount; ++b) {
RandomizeLineArchetypes(gen, true);
var previousTile = mainRoom;
var targetLength = Mathf.RoundToInt(gen.DungeonFlow.Length.GetRandom(gen.RandomStream) * gen.LengthMultiplier);
var archetypes = new List<DungeonArchetype>(targetLength);
var newMainPathTiles = new List<TileProxy>();
newMainPathTiles.Add(mainRoom);
var nodes = nodesSorted.Skip(startingNodeIndex + 1);
var nodesVisited = new List<GraphNode>(nodes.Count());
// most of this code is a mix of the GenerateMainPath()
// and GenerateBranch() code
for(var t = 1; t < targetLength; ++t){
var lineDepthRatio = Mathf.Clamp01((float)t / (targetLength - 1));
var lineAtDepth = gen.DungeonFlow.GetLineAtDepth(lineDepthRatio);
if (lineAtDepth == null){
yield return gen.Wait(gen.InnerGenerate(true));
yield break;
}
if (lineAtDepth != gen.previousLineSegment){
gen.currentArchetype = lineAtDepth.GetRandomArchetype(gen.RandomStream, archetypes);
gen.previousLineSegment = lineAtDepth;
}
// terrible solution but FUCK it
// and yet it worked
// this is how my last node cannot be a target of pruning
GraphNode graphNode = null;
DungeonArchetype archetype = null;
foreach(var g in nodes) {
if (lineDepthRatio >= g.Position && !nodesVisited.Contains(g)) {
graphNode = g;
nodesVisited.Add(g);
break;
}
}
List<TileSet> useableTileSets;
if (graphNode != null) {
archetype = ModifyMainBranchNodeArchetype(null, graphNode, gen.RandomStream);
useableTileSets = graphNode.TileSets;
} else {
archetype = gen.currentArchetype;
useableTileSets = archetype.TileSets;
}
// places fake doorways at the first node
if (doorwayGroups && t == 1){
foreach(var d in mainRoom.UsedDoorways) {
if (d.ConnectedDoorway.Index != int.MaxValue) {
var groups = doorwayGroups.GrabDoorwayGroup(d.DoorwayComponent);
if (groups == null) continue;
foreach(var doorway in mainRoom.UnusedDoorways){
if (groups.Contains(doorway.DoorwayComponent)){
doorway.ConnectedDoorway = fakeDoorwayProxy;
}
}
}
}
}
var tileProxy = gen.AddTile(previousTile, useableTileSets, lineDepthRatio, archetype, TilePlacementResult.None);
if (tileProxy == null) {
Plugin.logger.LogInfo($"Alt. main branch gen failed at {b}:{lineDepthRatio}");
yield return gen.Wait(gen.InnerGenerate(true));
yield break;
}
if (lineDepthRatio >= 1f){
Plugin.logger.LogInfo($"Alt. main branch at {b} ended with {tileProxy.PrefabTile.name}");
}
tileProxy.Placement.BranchDepth = t;
tileProxy.Placement.NormalizedBranchDepth = lineDepthRatio;
if (graphNode != null) {
tileProxy.Placement.GraphNode = graphNode;
tileProxy.Placement.GraphLine = null;
} else {
tileProxy.Placement.GraphNode = null;
tileProxy.Placement.GraphLine = lineAtDepth;
}
previousTile = tileProxy;
newMainPathTiles.Add(tileProxy);
if (gen.ShouldSkipFrame(true)) yield return gen.GetRoomPause();
}
allMainPathTiles.Add(newMainPathTiles);
}
// okay lets fix the fakes
foreach(var doorway in mainRoom.UsedDoorways){
if (doorway.ConnectedDoorway.Index == int.MaxValue) {
doorway.ConnectedDoorway = null;
}
}
ActiveAlternative = false;
Plugin.logger.LogInfo($"Created {altCount} alt. paths, creating branches now");
gen.ChangeStatus(GenerationStatus.Branching);
// this is major trickery and it works still
for(var b = 0; b < altCount + 1; ++b){
Plugin.logger.LogInfo($"Branch {b}");
RandomizeLineArchetypes(gen, false);
gen.proxyDungeon.MainPathTiles = allMainPathTiles[b];
yield return gen.Wait(gen.GenerateBranchPaths());
}
ActiveAlternative = true;
gen.proxyDungeon.MainPathTiles = allMainPathTiles[0];
}
public static void RandomizeLineArchetypes(DungeonGenerator gen, bool randomizeMainPath){
if (!Properties.UseLineRandomizer) return;
var flow = Instance.DungeonFlow;
var lines = flow.Lines;
var tilesetsUsed = new Dictionary<TileSet, int>();
foreach(var t in Properties.LineRandomizerTileSets){
tilesetsUsed.Add(t, 0);
}
foreach(var a in Properties.LineRandomizerArchetypes) {
var tiles = randomizeMainPath ? a.TileSets : a.BranchCapTileSets;
RandomizeArchetype(gen, tiles, tilesetsUsed);
}
}
public static void RandomizeArchetype(DungeonGenerator gen, List<TileSet> targetTileSet, Dictionary<TileSet, int> tilesetsUsed){
// get 3 random
var newTiles = Properties.LineRandomizerTileSets
.OrderBy(t => tilesetsUsed[t] + gen.RandomStream.NextDouble())
.Take(Properties.LineRandomizerTakeCount);
var i = targetTileSet.Count - 1;
foreach(var n in newTiles){
targetTileSet[i] = n;
--i;
tilesetsUsed[n] += 1;
}
}
public static DungeonArchetype ModifyMainBranchNodeArchetype(DungeonArchetype archetype, GraphNode node, RandomStream randomStream){
if (!DunGenPlusGenerator.Active) return archetype;
if (Properties.AddArchetypesToNormalNodes && node.NodeType == NodeType.Normal) {
return Properties.GetRandomArchetype(node.Label, randomStream);;
}
return archetype;
}
public static void FixDoorwaysToAllFloors(TileProxy mainRoom, MainRoomDoorwayGroups doorwayGroups) {
var first = doorwayGroups.doorwayListFirst;
if (first == null) return;
foreach(var target in mainRoom.UsedDoorways){
if (target.ConnectedDoorway.Index == int.MaxValue && !first.Contains(target.DoorwayComponent)) {
target.ConnectedDoorway = null;
}
}
}
/*
public static GraphNode ModifyGraphNode(GraphNode node) {
if (!Patch.active) return node;
if (node.Label == "Hallway Entrance 1") {
return Assets.networkObjectList.gardenEntranceGraphNode;
}
return node;
}
public static TileProxy FixTilesToAllFloors(TileProxy mainTile) {
if (!Patch.active) return mainTile;
var groups = mainTile.Prefab.GetComponentInChildren<MainRoomDoorwayGroups>();
var first = groups.groupFirst;
foreach(var target in mainTile.doorways){
if (target.ConnectedDoorway != null && target.ConnectedDoorway.Index == int.MaxValue && !first.Contains(target.DoorwayComponent)) {
target.ConnectedDoorway = null;
}
}
return mainTile;
}
*/
}
}

View File

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DunGen.Adapters;
using DunGenPlus.Components;
using DunGenPlus.Generation;
using DunGenPlus.Utils;
namespace DunGenPlus.Managers {
public static class DoorwayManager {
public static ActionList onMainEntranceTeleportSpawnedEvent = new ActionList("onMainEntranceTeleportSpawned");
public static List<DoorwayCleanup> doorwayCleanupList;
public static void ResetList(){
doorwayCleanupList = new List<DoorwayCleanup>();
}
public static void AddDoorwayCleanup(DoorwayCleanup cleanup){
doorwayCleanupList.Add(cleanup);
}
public static void onMainEntranceTeleportSpawnedFunction(){
if (DunGenPlusGenerator.Active) {
foreach(var d in doorwayCleanupList){
d.SetBlockers(false);
d.Cleanup();
}
try{
var dungeonGen = RoundManager.Instance.dungeonGenerator;
var navmesh = dungeonGen.transform.parent.GetComponentInChildren<UnityNavMeshAdapter>();
navmesh.Run(dungeonGen.Generator);
Plugin.logger.LogInfo("Rebuild nav mesh");
} catch (Exception e){
Plugin.logger.LogError("Failed to rebuild nav mesh");
Plugin.logger.LogError(e.ToString());
}
}
}
}
}

View File

@ -0,0 +1,63 @@
using DunGen;
using DunGenPlus.Utils;
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using DunGenPlus.Generation;
namespace DunGenPlus.Patches {
internal class DoorwayConnectionPatch {
[HarmonyPatch(typeof(DungeonProxy), "ConnectOverlappingDoorways")]
[HarmonyPrefix]
public static void ConnectOverlappingDoorwaysPrePatch(ref DungeonProxy __instance){
var enumerable = __instance.AllTiles.SelectMany(t => t.Doorways);
DoorwaySistersRule.UpdateCache(enumerable);
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(DungeonProxy), "ConnectOverlappingDoorways")]
public static IEnumerable<CodeInstruction> ConnectOverlappingDoorwaysPatch(IEnumerable<CodeInstruction> instructions){
var callFunction = typeof(DunGen.Graph.DungeonFlow).GetMethod("CanDoorwaysConnect", BindingFlags.Instance | BindingFlags.Public);
var sequence = new InstructionSequence("doorway connect", false);
sequence.AddBasic(OpCodes.Callvirt, callFunction);
sequence.AddBasic(OpCodes.Brfalse);
foreach(var instruction in instructions){
if (sequence.VerifyStage(instruction)){
var method = typeof(DoorwaySistersRule).GetMethod("CanDoorwaysConnect", BindingFlags.Static | BindingFlags.Public);
var getTileProxy = typeof(DoorwayProxy).GetMethod("get_TileProxy", BindingFlags.Instance | BindingFlags.Public);
yield return new CodeInstruction(OpCodes.Ldloc_2);
yield return new CodeInstruction(OpCodes.Callvirt, getTileProxy);
yield return new CodeInstruction(OpCodes.Ldloc_S, 4);
yield return new CodeInstruction(OpCodes.Callvirt, getTileProxy);
yield return new CodeInstruction(OpCodes.Ldloc_2);
yield return new CodeInstruction(OpCodes.Ldloc_S, 4);
yield return new CodeInstruction(OpCodes.Call, method);
yield return instruction;
continue;
}
yield return instruction;
}
sequence.ReportComplete();
}
}
}

View File

@ -0,0 +1,162 @@
using DunGen;
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Collections;
using DunGenPlus.Utils;
using DunGenPlus.Generation;
using DunGenPlus.Managers;
namespace DunGenPlus.Patches {
internal class DungeonGeneratorPatch {
[HarmonyPostfix]
[HarmonyPatch(typeof(DungeonGenerator), "GenerateMainPath")]
public static void GenerateMainPathPatch(ref DungeonGenerator __instance, ref IEnumerator __result){
if (DunGenPlusGenerator.Active && DunGenPlusGenerator.ActiveAlternative) {
DunGenPlusGenerator.RandomizeLineArchetypes(__instance, true);
}
}
[HarmonyPostfix]
[HarmonyPatch(typeof(DungeonGenerator), "GenerateBranchPaths")]
public static void GenerateBranchPathsPatch(ref DungeonGenerator __instance, ref IEnumerator __result){
if (DunGenPlusGenerator.Active && DunGenPlusGenerator.ActiveAlternative) {
__result = DunGenPlusGenerator.GenerateAlternativeMainPaths(__instance);
}
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(DungeonGenerator), "GenerateMainPath", MethodType.Enumerator)]
public static IEnumerable<CodeInstruction> GenerateMainPathPatch(IEnumerable<CodeInstruction> instructions){
var addArchFunction = typeof(List<DungeonArchetype>).GetMethod("Add", BindingFlags.Instance | BindingFlags.Public);
var archSequence = new InstructionSequence("archetype node");
archSequence.AddOperandTypeCheck(OpCodes.Ldfld, typeof(List<DungeonArchetype>));
archSequence.AddBasic(OpCodes.Ldnull);
archSequence.AddBasic(OpCodes.Callvirt, addArchFunction);
foreach(var instruction in instructions){
if (archSequence.VerifyStage(instruction)){
var randomStreamMethod = typeof(DungeonGenerator).GetMethod("get_RandomStream", BindingFlags.Public | BindingFlags.Instance);
var modifyMethod = typeof(DunGenPlusGenerator).GetMethod("ModifyMainBranchNodeArchetype", BindingFlags.Public | BindingFlags.Static);
yield return new CodeInstruction(OpCodes.Ldloc_S, 8);
yield return new CodeInstruction(OpCodes.Ldloc_1);
yield return new CodeInstruction(OpCodes.Call, randomStreamMethod);
yield return new CodeInstruction(OpCodes.Call, modifyMethod);
yield return instruction;
continue;
}
yield return instruction;
}
archSequence.ReportComplete();
}
[HarmonyPostfix]
[HarmonyPatch(typeof(RoundManager), "FinishGeneratingLevel")]
public static void GenerateBranchPathsPatch(){
if (DunGenPlusGenerator.Active) {
Plugin.logger.LogInfo("Alt. InnerGenerate() function complete");
}
}
[HarmonyPostfix]
[HarmonyPatch(typeof(RoundManager), "SetPowerOffAtStart")]
public static void SetPowerOffAtStartPatch(){
DoorwayManager.onMainEntranceTeleportSpawnedEvent.Call();
}
[HarmonyPostfix]
[HarmonyPatch(typeof(DungeonGenerator), "PostProcess")]
public static void GenerateBranchPathsPatch(ref DungeonGenerator __instance){
if (DunGenPlusGenerator.Active) {
var value = __instance.RandomStream.Next(999);
Components.Props.SpawnSyncedObjectCycle.UpdateCycle(value);
}
}
/*
[HarmonyTranspiler]
[HarmonyPatch(typeof(DungeonGenerator), "GenerateMainPath", MethodType.Enumerator)]
public static IEnumerable<CodeInstruction> GenerateMainPathPatch(IEnumerable<CodeInstruction> instructions){
var addArchFunction = typeof(List<DungeonArchetype>).GetMethod("Add", BindingFlags.Instance | BindingFlags.Public);
//var addNodeFunction = typeof(List<GraphNode>).GetMethod("Add", BindingFlags.Instance | BindingFlags.Public);
var archSequence = new InstructionSequence("archetype node");
archSequence.AddOperandTypeCheck(OpCodes.Ldfld, typeof(List<DungeonArchetype>));
archSequence.AddBasic(OpCodes.Ldnull);
archSequence.AddBasic(OpCodes.Callvirt, addArchFunction);
var nodeSequence = new InstructionSequence("graph node");
nodeSequence.AddBasicLocal(OpCodes.Ldloc_S, 12);
nodeSequence.AddBasicLocal(OpCodes.Stloc_S, 8);
var limitSequence = new InstructionSequence("limit nodes");
limitSequence.AddBasic(OpCodes.Ldnull);
limitSequence.AddBasicLocal(OpCodes.Stloc_S, 13);
limitSequence.AddBasic(OpCodes.Ldloc_1);
limitSequence.AddBasicLocal(OpCodes.Ldloc_S, 13);
foreach(var instruction in instructions){
if (archSequence.VerifyStage(instruction)){
var method = typeof(GeneratePath).GetMethod("ModifyMainBranchNodeArchetype", BindingFlags.Public | BindingFlags.Static);
yield return new CodeInstruction(OpCodes.Ldloc_S, 8);
yield return new CodeInstruction(OpCodes.Call, method);
yield return instruction;
continue;
}
if (nodeSequence.VerifyStage(instruction)){
var method = typeof(GeneratePath).GetMethod("ModifyGraphNode", BindingFlags.Public | BindingFlags.Static);
yield return new CodeInstruction(OpCodes.Call, method);
yield return instruction;
continue;
}
if (limitSequence.VerifyStage(instruction)){
var method = typeof(GeneratePath).GetMethod("LimitTilesToFirstFloor", BindingFlags.Public | BindingFlags.Static);
var field = typeof(DungeonGenerator).Assembly.GetType("DunGen.DungeonGenerator+<GenerateMainPath>d__100").GetField("<j>5__8", BindingFlags.NonPublic | BindingFlags.Instance);
yield return instruction;
yield return new CodeInstruction(OpCodes.Ldarg_0);
yield return new CodeInstruction(OpCodes.Ldfld, field);
yield return new CodeInstruction(OpCodes.Call, method);
continue;
}
yield return instruction;
}
archSequence.ReportComplete();
nodeSequence.ReportComplete();
limitSequence.ReportComplete();
}
*/
}
}

View File

@ -0,0 +1,67 @@
using BepInEx;
using BepInEx.Logging;
using DunGen;
using DunGen.Graph;
using DunGenPlus.Generation;
using DunGenPlus.Managers;
using DunGenPlus.Patches;
using HarmonyLib;
using LethalLevelLoader;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Assertions;
namespace DunGenPlus {
[BepInPlugin(modGUID, modName, modVersion)]
[BepInProcess("Lethal Company.exe")]
public class Plugin : BaseUnityPlugin {
internal const string modGUID = "ImoutoSama.DungeonGenerationPlus";
private const string modName = "Dungeon Generation Plus";
private const string modVersion = "1.0.0";
internal readonly Harmony Harmony = new Harmony(modGUID);
internal static Plugin Instance {get; private set;}
internal static ManualLogSource logger { get; private set; }
internal static Dictionary<DungeonFlow, DunGenExtender> DunGenExtenders = new Dictionary<DungeonFlow, DunGenExtender>();
void Awake() {
if (Instance == null) Instance = this;
logger = BepInEx.Logging.Logger.CreateLogSource(modGUID);
logger.LogInfo($"Plugin {modName} has been added!");
Harmony.PatchAll(typeof(DungeonGeneratorPatch));
Harmony.PatchAll(typeof(DoorwayConnectionPatch));
Assets.LoadAssets();
DungeonManager.GlobalDungeonEvents.onBeforeDungeonGenerate.AddListener(OnDunGenExtenderLoad);
DoorwayManager.onMainEntranceTeleportSpawnedEvent.AddEvent("DoorwayCleanup", DoorwayManager.onMainEntranceTeleportSpawnedFunction);
}
void OnDunGenExtenderLoad(RoundManager roundManager) {
DunGenPlusGenerator.Deactivate();
var generator = roundManager.dungeonGenerator.Generator;
var flow = generator.DungeonFlow;
if (DunGenExtenders.TryGetValue(flow, out var value)) {
Plugin.logger.LogInfo($"Loading DunGenExtender for {flow.name}");
DunGenPlusGenerator.Activate(generator, value);
return;
}
Plugin.logger.LogInfo($"Did not load a DunGenExtenderer");
DunGenPlusGenerator.Deactivate();
}
}
}

View File

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// アセンブリに関する一般情報は以下を通して制御されます
// 制御されます。アセンブリに関連付けられている情報を変更するには、
// これらの属性値を変更してください。
[assembly: AssemblyTitle("DunGenPlus")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("DunGenPlus")]
[assembly: AssemblyCopyright("Copyright © 2024")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// ComVisible を false に設定すると、このアセンブリ内の型は COM コンポーネントから
// 参照できなくなります。COM からこのアセンブリ内の型にアクセスする必要がある場合は、
// その型の ComVisible 属性を true に設定してください。
[assembly: ComVisible(false)]
// このプロジェクトが COM に公開される場合、次の GUID が typelib の ID になります
[assembly: Guid("13cde60e-1975-463b-9da1-ccb3f3ebabd8")]
// アセンブリのバージョン情報は、以下の 4 つの値で構成されています:
//
// メジャー バージョン
// マイナー バージョン
// ビルド番号
// リビジョン
//
// すべての値を指定するか、次を使用してビルド番号とリビジョン番号を既定に設定できます
// 既定値にすることができます:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,191 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HarmonyLib;
using GameNetcodeStuff;
using System.Reflection;
using System.Reflection.Emit;
using BepInEx.Logging;
using UnityEngine;
using DunGenPlus;
namespace DunGenPlus.Utils {
internal class InjectionDictionary {
public string name;
public List<CodeInstruction> instructions;
public CodeInstruction[] injections;
int counter;
public InjectionDictionary(string name, MethodInfo methodInjection, params CodeInstruction[] instructions) {
this.name = name;
this.injections = new CodeInstruction[] { new CodeInstruction(OpCodes.Call, methodInjection) } ;
this.instructions = instructions.ToList();
}
public InjectionDictionary(string name, CodeInstruction[] codeInjections, params CodeInstruction[] instructions) {
this.name = name;
this.injections = codeInjections;
this.instructions = instructions.ToList();
}
public void ResetCounter(){
counter = 0;
}
public void AddCounter() {
counter++;
}
public void Report(string debugFunction, int? expectedCounter){
if (counter == 0) {
Plugin.logger.LogError($"{debugFunction} could not inject {name}. Probably scary");
} else if (!expectedCounter.HasValue) {
Plugin.logger.LogInfo($"{debugFunction} inject {name} {counter} time(s)");
} else if (expectedCounter.Value != counter){
Plugin.logger.LogWarning($"{debugFunction} inject {name} {counter} time(s) (Expected {expectedCounter.Value}). Probably not an error but be warned");
}
}
}
internal class InstructionSequence {
public static ManualLogSource logger => Plugin.logger;
List<Func<CodeInstruction, bool>> seq;
string name;
string extraErrorMessage;
int stage = 0;
bool completed = false;
bool single;
public InstructionSequence(string name, bool single = true, string extraErrorMessage = default(string)){
this.name = name;
this.single = single;
this.extraErrorMessage = extraErrorMessage;
seq = new List<Func<CodeInstruction, bool>>();
}
public void Add(Func<CodeInstruction, bool> next){
seq.Add(next);
}
public void AddBasic(OpCode opcode){
seq.Add((i) => i.opcode == opcode);
}
public void AddBasic(OpCode opcode, object operand){
seq.Add((i) => i.opcode == opcode && i.operand == operand);
}
public void AddBasicLocal(OpCode opcode, int operand){
seq.Add((i) => i.opcode == opcode && (i.operand as LocalBuilder).LocalIndex == operand);
}
public void AddOperandTypeCheck(OpCode opcode, Type operandType){
seq.Add((i) => {
var fieldInfo = i.operand as FieldInfo;
if (i.opcode == opcode && fieldInfo != null) {
return fieldInfo.FieldType == operandType;
}
return false;
});
}
public void AddBasicWithAlternateMethodName(OpCode opcode, object operand, string methodName){
seq.Add((i) => {
if (i.opcode == opcode && i.operand == operand) return true;
var mth = i.operand as MethodInfo;
if (mth != null && mth.Name == methodName) return true;
return false;
});
}
public void AddSpecial(OpCode opcode, Func<CodeInstruction, bool> extra){
seq.Add((i) => i.opcode == opcode && extra.Invoke(i));
}
public void AddQuickInjection(MethodInfo methodInfo){
}
public bool VerifyStage(CodeInstruction current){
var s = seq[stage];
if (s.Invoke(current)) {
//Plugin.logger.LogInfo($"{name}({stage}): current.ToString()");
stage++;
} else {
stage = 0;
}
if (stage >= seq.Count){
if (completed && single){
throw new Exception($"Found multiple valid {name} instructions");
}
stage = 0;
completed = true;
return true;
}
return false;
}
public void ReportComplete(){
if (completed == false){
var errorM = string.IsNullOrWhiteSpace(extraErrorMessage) ? "BIG PROBLEM!" : extraErrorMessage;
logger.LogError($"HarmonyTranspiler for {name} has failed. {errorM}");
}
}
}
internal class TranspilerUtilities {
public static IEnumerable<CodeInstruction> InjectMethod(IEnumerable<CodeInstruction> instructions, InjectionDictionary injection, string debugFunction, int? expectedCounter = default){
var targets = injection.instructions;
var codeInjections = injection.injections;
injection.ResetCounter();
foreach(var i in instructions){
foreach(var t in targets){
if (i.opcode == t.opcode && i.operand == t.operand){
yield return i;
foreach(var c in codeInjections) yield return c;
injection.AddCounter();
goto GoNext;
}
}
yield return i;
GoNext:;
}
injection.Report(debugFunction, expectedCounter);
}
public static bool IsInstructionNearFloatValue(CodeInstruction instruction, float value){
return Mathf.Abs((float)instruction.operand - value) < 0.1f;
}
public static void PrintInstructions(IEnumerable<CodeInstruction> instructions) {
foreach(var i in instructions){
var opString = i.opcode.ToString();
var objString = i.operand != null ? i.operand.ToString() : "NULL";
Plugin.logger.LogInfo($"{opString}: {objString}");
}
}
}
}

View File

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DunGenPlus.Utils {
public class ActionList {
public string name;
public List<(string name, Action action)> actionList;
public ActionList(string name){
this.name = name;
actionList = new List<(string, Action)>();
}
public void AddEvent(string name, Action act){
actionList.Add((name, act));
}
public void Call(){
foreach(var pair in actionList){
try {
pair.action.Invoke();
} catch (Exception e) {
Plugin.logger.LogError($"Error with event {name}/{pair.name}");
Plugin.logger.LogError(e.ToString());
}
}
}
}
}

42
LICENSE
View File

@ -1,4 +1,4 @@
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
Creative Commons Attribution-ShareAlike 4.0 International
Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible.
@ -8,11 +8,13 @@ Creative Commons public licenses provide a standard set of terms and conditions
Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. More considerations for licensors.
Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensors permission is not necessary for any reasonfor example, because of any applicable exception or limitation to copyrightthen that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public.
Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensors permission is not necessary for any reasonfor example, because of any applicable exception or limitation to copyrightthen that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described.
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License
Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public.
By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions.
Creative Commons Attribution-ShareAlike 4.0 International Public License
By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-ShareAlike 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions.
Section 1 Definitions.
@ -20,7 +22,7 @@ Section 1 Definitions.
b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License.
c. BY-NC-SA Compatible License means a license listed at creativecommons.org/compatiblelicenses, approved by Creative Commons as essentially the equivalent of this Public License.
c. BY-SA Compatible License means a license listed at creativecommons.org/compatiblelicenses, approved by Creative Commons as essentially the equivalent of this Public License.
d. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.
@ -28,7 +30,7 @@ Section 1 Definitions.
f. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material.
g. License Elements means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution, NonCommercial, and ShareAlike.
g. License Elements means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution and ShareAlike.
h. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License.
@ -36,13 +38,11 @@ Section 1 Definitions.
j. Licensor means the individual(s) or entity(ies) granting rights under this Public License.
k. NonCommercial means not primarily intended for or directed towards commercial advantage or monetary compensation. For purposes of this Public License, the exchange of the Licensed Material for other material subject to Copyright and Similar Rights by digital file-sharing or similar means is NonCommercial provided there is no payment of monetary compensation in connection with the exchange.
k. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them.
l. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them.
l. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world.
m. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world.
n. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning.
m. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning.
Section 2 Scope.
@ -50,9 +50,9 @@ Section 2 Scope.
1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to:
A. reproduce and Share the Licensed Material, in whole or in part, for NonCommercial purposes only; and
A. reproduce and Share the Licensed Material, in whole or in part; and
B. produce, reproduce, and Share Adapted Material for NonCommercial purposes only.
B. produce, reproduce, and Share Adapted Material.
2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions.
@ -76,7 +76,7 @@ Section 2 Scope.
2. Patent and trademark rights are not licensed under this Public License.
3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties, including when the Licensed Material is used other than for NonCommercial purposes.
3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties.
Section 3 License Conditions.
@ -108,7 +108,7 @@ Your exercise of the Licensed Rights is expressly made subject to the following
b. ShareAlike.In addition to the conditions in Section 3(a), if You Share Adapted Material You produce, the following conditions also apply.
1. The Adapters License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-NC-SA Compatible License.
1. The Adapters License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-SA Compatible License.
2. You must include the text of, or the URI or hyperlink to, the Adapter's License You apply. You may satisfy this condition in any reasonable manner based on the medium, means, and context in which You Share Adapted Material.
@ -118,7 +118,7 @@ Section 4 Sui Generis Database Rights.
Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material:
a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database for NonCommercial purposes only;
a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database;
b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material, including for purposes of Section 3(b); and
@ -139,15 +139,15 @@ Section 6 Term and Termination.
b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates:
1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or
1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or
2. upon express reinstatement by the Licensor.
2. upon express reinstatement by the Licensor.
For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License.
c. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License.
c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License.
d. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License.
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License.
e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License.
Section 7 Other Terms and Conditions.