This commit is contained in:
LadyAliceMargatroid 2024-04-28 14:41:33 -07:00
parent 7a77b79dc5
commit cdadd75ee9
104 changed files with 9416 additions and 0 deletions

View file

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using DunGen;
namespace ScarletMansion {
public class FloorCleanUpParent : MonoBehaviour, IDungeonCompleteReceiver {
public FloorCleanup[] children;
void Reset(){
children = GetComponentsInChildren<FloorCleanup>();
}
public void OnDungeonComplete(Dungeon dungeon) {
var anyChanges = true;
while(anyChanges) {
anyChanges = false;
foreach(var c in children) anyChanges = anyChanges | c.UpdateRender();
}
}
}
}

View file

@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace ScarletMansion {
public class FloorCleanup : MonoBehaviour {
[Header("References")]
public MeshRenderer targetRenderer;
public GameObject targerGameObject;
public int bitValueCheck;
[Header("Logic")]
public GameObject[] neighbors;
public int[] stateValues;
public Material[] stateMaterials;
public float[] stateRotations;
public State[] states;
[System.Serializable]
public class State {
public int value;
public Material material;
public float rotation;
}
void Reset(){
targetRenderer = GetComponent<MeshRenderer>();
targerGameObject = gameObject;
}
public bool UpdateRender(){
var value = 0;
var maxValue = (1 << neighbors.Length) - 1;
for(var i = 0; i < neighbors.Length; ++i){
var n = neighbors[i];
if (n != null && neighbors[i].activeSelf) value += 1 << i;
}
if (value == maxValue) return false;
for(var i = 0; i < stateValues.Length; ++i){
if (stateValues[i] == value) {
targetRenderer.material = stateMaterials[i];
targetRenderer.transform.localEulerAngles = new Vector3(0f, stateRotations[i], 0f);
return false;
}
}
var wasActive = targerGameObject.activeSelf;
targerGameObject.SetActive(false);
return wasActive;
}
void OnDrawGizmosSelected(){
Gizmos.color = Color.green;
for(var i = 0; i < neighbors.Length; ++i){
if ((bitValueCheck & (1 << i)) > 0) {
Gizmos.DrawCube(neighbors[i].transform.position, Vector3.one);
}
}
}
}
}

View file

@ -0,0 +1,50 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace ScarletMansion {
public class KnightSpawnPoint : MonoBehaviour {
public int index;
public void Start(){
if (GameNetworkManager.Instance.isHostingGame)
StartCoroutine(GetNearbyPlayers());
}
public const float minTime = 4f;
public const float maxTime = 8f;
public const float minSqrDistance = 6f * 6f;
public IEnumerator GetNearbyPlayers(){
while(true){
var randomWait = UnityEngine.Random.Range(minTime, maxTime);
yield return new WaitForSeconds(randomWait);
var sround = StartOfRound.Instance;
var players = sround.allPlayerScripts;
var playerCount = sround.connectedPlayersAmount + 1;
for(var i = 0; i < playerCount; ++i){
var player = players[i];
if (!player.isPlayerControlled && player.isPlayerDead) continue;
// could i do a raycast?
// i mean i could but most knights are in visable locations in every room
// plus its a big pain on GOD
var dist = Vector3.SqrMagnitude(transform.position - player.transform.position);
if (dist <= minSqrDistance) {
KnightSpawnManager.Instance.lastKnightSeenPlayer = index;
Plugin.logger.LogInfo($"Knight {index} has noticed player {player.playerUsername}");
}
}
}
}
}
}

View file

@ -0,0 +1,64 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using DunGen;
namespace ScarletMansion.Lights {
public class ScarletLight : MonoBehaviour, IComparable<ScarletLight>, IDungeonCompleteReceiver {
public Light light;
public int priority = 0;
private Coroutine anger;
private bool permenantAnger;
public int CompareTo(ScarletLight obj) {
return obj.priority.CompareTo(priority);
}
void Reset(){
light = GetComponent<Light>();
}
public void BeginAngry(float duration, bool forever){
if (permenantAnger) return;
if (anger != null) StopCoroutine(anger);
if (forever) permenantAnger = true;
anger = StartCoroutine(AngerCoroutine(duration));
}
private IEnumerator AngerCoroutine(float duration){
var t = 0f;
var c = light.color;
while(t < 0.375f) {
yield return null;
t += Time.deltaTime;
light.color = Color.Lerp(c, Color.red, t / 0.375f);
}
if (permenantAnger) yield break;
yield return new WaitForSeconds(duration + UnityEngine.Random.value);
c = light.color;
t = 0f;
while(t < 1.25f){
yield return null;
t += Time.deltaTime;
light.color = Color.Lerp(c, Color.white, t / 1.25f);
}
}
public void OnDungeonComplete(Dungeon dungeon) {
if (!gameObject.activeInHierarchy) return;
GamePatch.Managers.AngerManager.Instance.AddLight(this);
}
}
}

View file

@ -0,0 +1,58 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using DunGen;
namespace ScarletMansion.Lights {
public class ScarletLightCleanup : MonoBehaviour, IDungeonCompleteReceiver {
public static float[] weights = new [] { 2f, 4f, 3f, 1f };
public int maxLights = 2;
public void OnDungeonComplete(Dungeon dungeon) {
var lights = GetComponentsInChildren<ScarletLight>();
if (lights.Length == 0) return;
// the smallest amount of optimization
var random = DunGenPatch.Patch.generatorInstance.RandomStream;
if (lights.Length > 1){
Utility.Shuffle(random, lights);
System.Array.Sort(lights);
}
//Plugin.logger.LogInfo(string.Join(", ", lights.Select(l => l.priority)));
var count = GetRandom(random, weights);
count = Mathf.Min(count, maxLights);
for(var i = count; i < lights.Length; ++i){
lights[i].gameObject.SetActive(false);
}
}
public static int GetRandom(RandomStream random, float[] weights) {
var total = 0f;
foreach(var w in weights){
total += w;
}
var value = (float)random.NextDouble() * total;
for(var i = 0; i < weights.Length; ++i){
var w = weights[i];
if (w == 0f) continue;
if (value < w) {
return i;
}
value -= w;
}
Plugin.logger.LogError("GetRandom for float[] failed. Defaulting to 0");
return 0;
}
}
}

View file

@ -0,0 +1,247 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Unity.Netcode;
using DunGen;
using ScarletMansion.Lights;
using ScarletMansion.GamePatch.Managers;
using GameNetcodeStuff;
namespace ScarletMansion.GamePatch.Components {
public class ScarletBedroom : MonoBehaviour, IDungeonCompleteReceiver {
public static ActionList onBedroomEndEvent = new ActionList("onBedroomEnd");
[Header("References")]
public ScarletVent vent;
public Transform paintingSpawnTransform;
public AudioSource screamAudioSource;
public Transform[] itemSpawns;
private Light[] lights;
private Doorway[] doorways;
private ItemReference[] bonusItems;
private EnemyReferenceSpawnLogic bonusEnemy;
const float spawnTime = 10f;
const float cloudTime = 4f;
public void Anger(Transform target){
AngerManager.Instance.Anger();
StartCoroutine(PlayAudio());
StartCoroutine(IncreaseVentAudio());
StartCoroutine(LockDoorAndSpawnEnemy(target));
foreach(var l in lights){
StartCoroutine(FlickerLight(l));
}
}
public IEnumerator PlayAudio(){
yield return new WaitForSeconds(UnityEngine.Random.Range(0.75f, 1f));
screamAudioSource.Play();
AngerManager.Instance.TriggerAngerLightBrief(6f);
var local = StartOfRound.Instance.localPlayerController;
var dist = Vector3.SqrMagnitude(local.transform.position - transform.position);
if (dist < 12f * 12f)
local.JumpToFearLevel(0.75f);
else if (dist < 24f * 24f)
local.JumpToFearLevel(0.5f);
else if (dist < 32f * 32f)
local.JumpToFearLevel(0.25f);
}
public IEnumerator LockDoorAndSpawnEnemy(Transform target){
var roundmanager = RoundManager.Instance;
var doors = new List<ScarletDoor>();
// lock doors
if (roundmanager.IsServer){
foreach(var d in doorways){
var result = AngerManager.Instance.GetScarletDoor(d.transform.position);
if (result) doors.Add(result);
}
foreach(var d in doors){
d.LockDoorClientRpc();
}
}
yield return new WaitForSeconds(spawnTime);
SpawnRandomEnemy(target);
SpawnLoot();
onBedroomEndEvent.Call();
// stop volume stuff
vent.OpenVentClientRpc();
vent.shakeAudioSource.Stop();
vent.shakeAudioSource.volume = 0f;
// unlock doors
if (roundmanager.IsServer){
foreach(var d in doors){
d.UnlockDoorClientRpc();
}
}
}
public IEnumerator IncreaseVentAudio(){
yield return new WaitForSeconds(1f);
vent.shakeAudioSource.Play();
var t = 0f;
while (t < cloudTime){
vent.shakeAudioSource.volume = t / cloudTime;
t += Time.deltaTime;
yield return null;
}
}
public IEnumerator FlickerLight(Light light){
// gives time for player to react to their environment
yield return new WaitForSeconds(UnityEngine.Random.Range(1f, 1.5f));
// scarlet lights flicker
var isScarletLight = light.GetComponent<ScarletLight>() != null;
if (isScarletLight){
var count = UnityEngine.Random.Range(6, 8);
for (var i = 0; i < count; ++i) {
light.enabled = false;
yield return new WaitForSeconds(UnityEngine.Random.Range(0.15f, 0.25f) * (1f - count * 0.05f));
light.enabled = true;
yield return new WaitForSeconds(UnityEngine.Random.Range(0.5f, 0.6f) * (1f - count * 0.05f));
}
light.enabled = false;
}
// normal lights are candles, so just turn them off
else {
yield return new WaitForSeconds(UnityEngine.Random.Range(3f, 5f));
light.enabled = false;
}
}
private static List<EnemyReferenceSpawnLogic> spawnableEnemiesTrueList;
public static void CreateRandomEnemyList(List<SpawnableEnemyWithRarity> enemies){
var lower = PluginConfig.Instance.paintingEnemyListValue.ToLowerInvariant();
if (lower == "default"){
lower = "knight@s1,crawler,nutcracker,springman,maskedplayerenemy,jester@s1";
}
spawnableEnemiesTrueList = new List<EnemyReferenceSpawnLogic>();
var entries = Utility.ParseString(lower, new string[] {","}, new string[] {"@"} );
for(var i = 0; i < enemies.Count; ++i){
var enemyType = enemies[i].enemyType;
var enemyName = enemyType.name.ToLowerInvariant();
var enemyDisplayName = enemyType.enemyName.ToLowerInvariant();
var link = entries.FirstOrDefault(e => e.main == enemyName || e.main == enemyDisplayName);
if (link != null){
EnemyReferenceSpawnLogic.SpawnLogic spawnValue;
var spawnTag = link.GetParameter("s");
if (spawnTag != null) spawnValue = (EnemyReferenceSpawnLogic.SpawnLogic)Utility.TryParseInt(spawnTag.Substring(1), 0);
else spawnValue = 0;
var reference = new EnemyReferenceSpawnLogic(enemyType, i, spawnValue);
spawnableEnemiesTrueList.Add(reference);
Plugin.logger.LogInfo($"Added {reference.ToString()} to bedroom event");
}
}
}
public EnemyReferenceSpawnLogic GetRandomEnemy(System.Random sysRandom){
var roundManager = RoundManager.Instance;
if (!roundManager.IsServer) return null;
if (!PluginConfig.Instance.paintingSpawnEnemyValue) return null;
if (spawnableEnemiesTrueList == null || spawnableEnemiesTrueList.Count == 0){
Plugin.logger.LogError($"Could not select enemy to spawn in bedroom. Empty enemy list?");
return null;
}
var index = sysRandom.Next(spawnableEnemiesTrueList.Count);
var enemy = spawnableEnemiesTrueList[index];
Plugin.logger.LogInfo($"Selected enemy to spawn {enemy.ToString()}");
return enemy;
}
public void SpawnRandomEnemy(Transform target, int overrideIndex = -1){
var roundmanager = RoundManager.Instance;
if (!roundmanager.IsHost) return;
if (bonusEnemy == null) return;
try {
var enemy = bonusEnemy.enemy;
var enemyIndex = bonusEnemy.index;
if (enemyIndex > -1){
var pos = vent.transform.position;
var dir = target.transform.position - pos;
dir.y = 0f;
var rot = Quaternion.LookRotation(dir);
var y = rot.eulerAngles.y;
bonusEnemy.ApplySpawnLogic();
roundmanager.currentEnemyPower += enemy.PowerLevel;
roundmanager.SpawnEnemyServerRpc(vent.transform.position, y, enemyIndex);
}
} catch (Exception e) {
var enemyString = bonusEnemy != null ? bonusEnemy.ToString() : "NULL";
Plugin.logger.LogError($"Failed to spawn enemy for bedroom event, the smelly culprit is {enemyString}");
Plugin.logger.LogError(e.ToString());
}
}
public void SpawnLoot(){
var roundmanager = RoundManager.Instance;
if (!roundmanager.IsServer) return;
try {
AngerManager.Instance.SpawnAngerLoot(bonusItems, itemSpawns);
} catch (Exception e) {
var itemStrings = string.Join("\n", bonusItems.Select(i => i != null ? i.ToString() : "NULL"));
Plugin.logger.LogError($"Failed to spawn items for bedroom event, the smelly culprits are {itemStrings}");
Plugin.logger.LogError(e.ToString());
}
}
public void OnDungeonComplete(Dungeon dungeon) {
AngerManager.Instance.AddBedroom(this);
var parent = GetComponentInParent<Tile>();
lights = parent.GetComponentsInChildren<Light>();
doorways = parent.UsedDoorways.ToArray();
var dunRandom = DunGenPatch.Patch.generatorInstance.RandomStream;
var randomValue = (int)(dunRandom.NextDouble() * int.MaxValue);
var sysRandom = new System.Random(randomValue);
Utility.Shuffle(sysRandom, itemSpawns);
var count = sysRandom.Next(PluginConfig.Instance.paintingExtraLootValue.min, PluginConfig.Instance.paintingExtraLootValue.max + 1);
bonusItems = AngerManager.Instance.CreateAngerLoot(count, sysRandom);
bonusEnemy = GetRandomEnemy(sysRandom);
}
public void OnTriggerEnter(Collider other){
Plugin.logger.LogInfo(other.gameObject.tag);
if (other.gameObject.tag == "Player"){
var player = other.gameObject.GetComponent<PlayerControllerB>();
if (player && player.IsLocalPlayer){
HUDManager.Instance.DisplayTip("SDM Dungeon Events", "Paintings fetch a good price, if you are daring...", false, true, "SDM_Bedroom");
}
}
}
}
}

View file

@ -0,0 +1,122 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Unity.Netcode;
namespace ScarletMansion.GamePatch.Components {
public class ScarletClock : NetworkBehaviour {
[Header("Animations")]
public Vector3 eulerAngleOffset = new Vector3(-90f, -90f, -90f);
public Transform hourHand;
public Transform minuteHand;
private Coroutine animationCoroutine;
private int lastHour = -1;
private int lastTotalMinutes = -1;
[Header("Sounds")]
public AudioSource audioSource;
public AudioClip[] audioClips;
[Header("Destruction Values")]
public bool stop;
public float chanceEveryHourToDestroy = 1f / 52f;
private float timerTillDestroyed = -1f;
void Update(){
if (stop) return;
if (IsServer && timerTillDestroyed > 0f){
timerTillDestroyed -= Time.deltaTime;
if (timerTillDestroyed <= 0f) {
StopClockClientRpc();
return;
}
}
var timeOfDay = TimeOfDay.Instance;
var totalMinutes = (int)(timeOfDay.normalizedTimeOfDay * (60f * timeOfDay.numberOfHours)) + 360;
var hours = Mathf.FloorToInt(totalMinutes / 60f);
var minutes = totalMinutes % 60;
var timeChanged = lastTotalMinutes != totalMinutes;
var hourChanged = lastHour != hours;
if (timeChanged) {
if (animationCoroutine != null) StopCoroutine(animationCoroutine);
animationCoroutine = StartCoroutine(MoveHands(hours, minutes));
}
if (IsServer && hourChanged){
HourlySelfDestroyCheck();
}
lastHour = hours;
lastTotalMinutes = totalMinutes;
}
// 60 = -90
// 07 = -45
// 15 = 0
// 22 = 45
// 12 = -90
// 01 = -45
// 03 = 0
// 04 = 45
public IEnumerator MoveHands(int hour, int minutes){
var currentHourHandRotation = hourHand.localRotation;
var currentMinuteHandRotation = minuteHand.localRotation;
var nextHourHandAngles = eulerAngleOffset + new Vector3(hour / 12f * 360f, 0f, 0f);
var nextHourHandRotation = Quaternion.Euler(nextHourHandAngles);
var nextMinuteHandAngles = eulerAngleOffset + new Vector3(minutes / 60f * 360f, 0f, 0f);
var nextMinuteHandRotation = Quaternion.Euler(nextMinuteHandAngles);
//Plugin.logger.LogInfo($"({hour}: {minutes}): ({nextHourHandAngles.x}, {nextMinuteHandRotation.x})");
PlayAudio();
var t = 0f;
var atimer = 0.3f;
while(t < atimer) {
yield return null;
t += Time.deltaTime;
hourHand.localRotation = Quaternion.Slerp(currentHourHandRotation, nextHourHandRotation, t / atimer);
minuteHand.localRotation = Quaternion.Slerp(currentMinuteHandRotation, nextMinuteHandRotation, t / atimer);
}
}
public void PlayAudio(){
var clip = audioClips[UnityEngine.Random.Range(0, audioClips.Length)];
audioSource.pitch = UnityEngine.Random.value * 0.3f + 0.85f;
audioSource.PlayOneShot(clip);
WalkieTalkie.TransmitOneShotAudio(audioSource, clip);
}
public void HourlySelfDestroyCheck(){
if (stop || timerTillDestroyed > 0) return;
Plugin.logger.LogInfo("Hourly clock self-destroy check");
if (UnityEngine.Random.value < chanceEveryHourToDestroy) {
timerTillDestroyed = UnityEngine.Random.value * 60f + 15f;
Plugin.logger.LogInfo($"Clock offing itself in {timerTillDestroyed} sec");
}
}
[ClientRpc]
public void StopClockClientRpc(){
stop = true;
}
}
}

View file

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Unity.Netcode;
using UnityEngine.AI;
using ScarletMansion.GamePatch.Managers;
namespace ScarletMansion.GamePatch.Components {
public class ScarletDoor : NetworkBehaviour {
[Header("Door Reference")]
public DoorLock door;
[Header("Personal Refs")]
public NavMeshObstacle obstacle;
public BoxCollider boxCollider;
public ParticleSystem ps;
private bool previousDoorLockValue;
void Awake(){
AngerManager.Instance.AddDoor(this);
}
[ServerRpc]
public void LockDoorServerRpc(){
LockDoorClientRpc();
}
[ServerRpc]
public void UnlockDoorServerRpc(){
UnlockDoorClientRpc();
}
[ClientRpc]
public void LockDoorClientRpc(){
if (door){
door.isDoorOpened = false;
door.navMeshObstacle.enabled = true;
if (RoundManager.Instance.IsServer)
door.GetComponent<AnimatedObjectTrigger>().TriggerAnimationNonPlayer(false, true, true);
door.doorTrigger.interactable = false;
previousDoorLockValue = door.isLocked;
door.isLocked = true;
}
obstacle.enabled = true;
boxCollider.enabled = true;
ps.Play();
}
[ClientRpc]
public void UnlockDoorClientRpc(){
if (door){
door.doorTrigger.interactable = true;
door.isLocked = previousDoorLockValue;
}
obstacle.enabled = false;
boxCollider.enabled = false;
ps.Stop();
}
}
}

View file

@ -0,0 +1,207 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Unity.Netcode;
namespace ScarletMansion.GamePatch.Components {
public class ScarletDoorLock : DoorLock {
public static float internalTimerCooldown = 1f;
[Header("Scarlet")]
public List<GameObject> healthTilePieces;
public List<GameObject> frameTilePieces;
private int healthTilePiecesCount;
public GameObject propPrefab;
[Header("Audio")]
public AudioSource audioSource;
public AudioClip[] destroyAudioClips;
public AudioClip fullyDestroyedAudioClip;
[Header("Proper Disable Logic")]
public Collider[] disableColliders;
public Animator disableAnimator;
public bool callNormalScript;
private float nextInternalTimer = 0f;
private float destroyMeter = 0f;
private int currentDamage = 0;
public void AwakeScarlet(){
healthTilePiecesCount = healthTilePieces.Count + 1;
}
void Start(){
if (IsOwner){
var randomCheck = UnityEngine.Random.value < 0.1f;
if (randomCheck){
ApplyDamageServerRpc(Vector3.forward, 50f);
}
}
}
public void OnTriggerStayScarlet(Collider other){
if (NetworkManager.Singleton == null || !base.IsServer) return;
if (isDoorOpened || !doorTrigger.interactable) return;
if (other.tag == "Enemy"){
var comp = other.GetComponent<EnemyAICollisionDetect>();
if (comp == null) return;
// do normal door opening script
var enemyScript = comp.mainScript;
if (IsInOpenDoorNormallyState(enemyScript)) {
callNormalScript = true;
OnTriggerStay(other);
return;
}
var direction = transform.position - comp.transform.position;
direction.y = 0f;
direction = direction.normalized;
var timeDelta = Time.deltaTime * GetDoorDamagePerSecond(enemyScript);
if (isLocked) timeDelta *= PluginConfig.Instance.lockedDoorEnemyDamageMultiplierValue;
destroyMeter += timeDelta;
ApplyDoorPieceDamage(direction);
}
}
static readonly Dictionary<Type, Func<EnemyAI, bool>> EnemyToDoorOpen = new Dictionary<Type, Func<EnemyAI, bool>>(){
{ typeof(CentipedeAI), (e) => e.currentBehaviourStateIndex <= 1 },
{ typeof(CrawlerAI), (e) => e.currentBehaviourStateIndex == 0 },
{ typeof(FlowermanAI), (e) => e.currentBehaviourStateIndex <= 1 },
{ typeof(HoarderBugAI), (e) => e.currentBehaviourStateIndex <= 1 },
{ typeof(JesterAI), (e) => e.currentBehaviourStateIndex <= 1 },
{ typeof(MaskedPlayerEnemy), (e) => e.currentBehaviourStateIndex == 0 },
{ typeof(NutcrackerEnemyAI), (e) => e.currentBehaviourStateIndex <= 1 },
{ typeof(PufferAI), (e) => e.currentBehaviourStateIndex == 0 },
{ typeof(SandSpiderAI), (e) => e.currentBehaviourStateIndex <= 1 },
{ typeof(SpringManAI), (e) => e.currentBehaviourStateIndex == 0 },
{ typeof(KnightVariant), (e) => e.currentBehaviourStateIndex == 0 },
};
static readonly Dictionary<Type, float> EnemyDoorDamagePerSecond = new Dictionary<Type, float>(){
{ typeof(BlobAI), 6.25f },
{ typeof(CentipedeAI), 25f },
{ typeof(CrawlerAI), 50f },
{ typeof(DressGirlAI), 50f },
{ typeof(FlowermanAI), 50f },
{ typeof(HoarderBugAI), 50f },
{ typeof(JesterAI), 50f },
{ typeof(MaskedPlayerEnemy), 50f },
{ typeof(NutcrackerEnemyAI), 50f },
{ typeof(PufferAI), 25f },
{ typeof(SandSpiderAI), 25f },
{ typeof(SpringManAI), 12.5f },
{ typeof(KnightVariant), 12.5f },
};
public bool IsInOpenDoorNormallyState(EnemyAI enemy){
var type = enemy.GetType();
if (EnemyToDoorOpen.ContainsKey(type)) return EnemyToDoorOpen[type](enemy);
return false;
}
public float GetDoorDamagePerSecond(EnemyAI enemy){
var type = enemy.GetType();
if (EnemyDoorDamagePerSecond.ContainsKey(type)) return EnemyDoorDamagePerSecond[type];
return enemy.openDoorSpeedMultiplier * 100f;
}
public void ApplyDoorPieceDamage(Vector3 direction, bool ignoreInternalTimer = false){
// full destruction
if (destroyMeter >= 100f) DestroyAndOpenDoorServerRpc(direction);
// health destruction
var passesTimeVibeCheck = ignoreInternalTimer || Time.time >= nextInternalTimer;
if (passesTimeVibeCheck && healthTilePieces.Count > 0) {
var newDamage = Mathf.FloorToInt(destroyMeter * healthTilePiecesCount) / 100;
var damageDiff = newDamage - currentDamage;
if (damageDiff > 0) {
DestroyDoorHealthPiecesClientRpc(newDamage - currentDamage, direction);
nextInternalTimer = Time.time + internalTimerCooldown;
currentDamage = newDamage;
}
}
}
[ServerRpc(RequireOwnership = false)]
public void ApplyDamageServerRpc(Vector3 direction, float damage){
destroyMeter += damage;
ApplyDoorPieceDamage(direction, true);
}
[ServerRpc(RequireOwnership = false)]
public void DestroyAndOpenDoorServerRpc(Vector3 direction){
Plugin.logger.LogInfo("Destro time");
OpenDoorAsEnemyClientRpc();
DestroyDoorClientRpc(direction);
}
[ClientRpc]
public void DestroyDoorClientRpc(Vector3 direction){
DestroyDoorPieces(healthTilePieces, healthTilePieces.Count, direction);
DestroyDoorPieces(frameTilePieces, frameTilePieces.Count, direction);
PlayAudio(fullyDestroyedAudioClip);
disableAnimator.enabled = false;
foreach(var c in disableColliders){
c.enabled = false;
}
//doorTrigger.interactable = false;
//doorTrigger.disabledHoverTip = string.Empty;
isLocked = false;
isDoorOpened = true;
navMeshObstacle.enabled = false;
}
[ClientRpc]
public void DestroyDoorHealthPiecesClientRpc(int count, Vector3 direction){
DestroyDoorPieces(healthTilePieces, count, direction);
PlayAudio(destroyAudioClips[UnityEngine.Random.Range(0, destroyAudioClips.Length)]);
}
public void DestroyDoorPieces(List<GameObject> tilePieces, int count, Vector3 direction){
while(count > 0 && tilePieces.Count > 0){
var lastIndex = tilePieces.Count - 1;
var target = tilePieces[lastIndex];
var prop = Instantiate(propPrefab);
prop.GetComponent<ScarletProp>().CreateProp(target, direction);
target.SetActive(false);
tilePieces.RemoveAt(lastIndex);
count--;
}
}
public void PlayAudio(AudioClip clip){
audioSource.PlayOneShot(clip);
WalkieTalkie.TransmitOneShotAudio(audioSource, clip);
}
public static bool ScarletDoorRaycast(GrabbableObject __instance, Vector3 position, Vector3 forward, float distance, out ScarletDoorLock door){
door = null;
var isPlayerHolding = __instance.isHeld && __instance.playerHeldBy == GameNetworkManager.Instance.localPlayerController;
var isServerCheck = __instance.IsServer && !isPlayerHolding;
if (!isPlayerHolding && !isServerCheck) return false;
var ray = new Ray(position, forward);
var layer = 1 << LayerMask.NameToLayer("InteractableObject");
if (Physics.Raycast(ray, out var hit, distance, layer, QueryTriggerInteraction.Ignore)) {
door = hit.transform.GetComponent<ScarletDoorLock>();
return door != null;
}
return false;
}
}
}

View file

@ -0,0 +1,71 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using GameNetcodeStuff;
namespace ScarletMansion.GamePatch.Components {
public class ScarletFireExit : MonoBehaviour {
public bool enablePortalAnimation;
[Header("Animations")]
public MeshRenderer portalRenderer;
public MeshRenderer blackRenderer;
private Material portalMaterial;
private Material blackMaterial;
private Coroutine timerCoroutine;
private Coroutine currentCoroutine;
public const float animationTime = 0.4f;
void Start(){
portalMaterial = portalRenderer.material;
blackMaterial = blackRenderer.material;
}
public void DisableEnablePortal(){
if (!enablePortalAnimation) return;
if (timerCoroutine != null) StopCoroutine(timerCoroutine);
timerCoroutine = StartCoroutine(DisableEnablePortalCoroutine());
}
private IEnumerator DisableEnablePortalCoroutine(){
DisablePortal();
yield return new WaitForSeconds(3f);
EnablePortal();
}
public void DisablePortal(){
if (currentCoroutine != null) StopCoroutine(currentCoroutine);
currentCoroutine = StartCoroutine(SetPortalCoroutine(-0.5f, 0f));
}
public void EnablePortal(){
if (currentCoroutine != null) StopCoroutine(currentCoroutine);
currentCoroutine = StartCoroutine(SetPortalCoroutine(1.5f, 1f));
}
private IEnumerator SetPortalCoroutine(float dissolveTarget, float blackTarget){
float dv;
Color bv;
do {
dv = portalMaterial.GetFloat("_DissolveValue");
dv = Mathf.MoveTowards(dv, dissolveTarget, 2f / animationTime * Time.deltaTime);
portalMaterial.SetFloat("_DissolveValue", dv);
bv = blackMaterial.color;
bv.a = Mathf.MoveTowards(bv.a, blackTarget, 1f / animationTime * Time.deltaTime);
blackMaterial.color = bv;
yield return null;
} while (dv != dissolveTarget && bv.a != blackTarget);
}
}
}

View file

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Unity.Netcode;
using GameNetcodeStuff;
namespace ScarletMansion.GamePatch.Components {
public class ScarletFrame : NetworkBehaviour {
//public MeshRenderer renderer;
//public Material[] materials;
public void OnInteract(PlayerControllerB player){
var direction = player.transform.position - transform.position;
direction.y = 0f;
ChangeDirection(direction);
ChangeDirectionServerRpc(direction);
}
/*
[ClientRpc]
public void UpdateMaterialClientRpc(int value){
var mats = new List<Material>(2);
renderer.GetMaterials(mats);
mats[1] = materials[value % materials.Length];
renderer.SetMaterials(mats);
}
*/
[ServerRpc(RequireOwnership = false)]
public void ChangeDirectionServerRpc(Vector3 direction){
ChangeDirectionClientRpc(direction);
}
[ClientRpc]
public void ChangeDirectionClientRpc(Vector3 direction){
ChangeDirection(direction);
}
public void ChangeDirection(Vector3 direction){
transform.rotation = Quaternion.LookRotation(direction);
}
}
}

View file

@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
/*
namespace ScarletMansion {
public class ScarletHDRISky : ScarletLighting {
public Volume volume;
public HDRISky GetSky(){
volume.sharedProfile.TryGet<HDRISky>(out var sky);
return sky;
}
public VisualEnvironment GetEnv(){
volume.sharedProfile.TryGet<VisualEnvironment>(out var env);
return env;
}
public override void DisableLights() {
volume.enabled = false;
}
public override void UpdateLight(float ratio) {
GetSky().multiplier.value = valueRatio;
GetEnv().skyAmbientMode.value = skyMode;
volume.enabled = trueEnabled;
}
public int value = 100;
public float valueRatio = 1f;
public bool trueEnabled = true;
public SkyAmbientMode skyMode = SkyAmbientMode.Dynamic;
void Update(){
int direction = 0;
if (IfKeyPress(Keyboard.current.minusKey, Keyboard.current.numpadMinusKey)){
direction = -1;
} else if (IfKeyPress(Keyboard.current.equalsKey, Keyboard.current.numpadPlusKey)){
direction = 1;
}
if (IfKeyPress(Keyboard.current.backspaceKey)){
trueEnabled = !trueEnabled;
Plugin.logger.LogInfo(trueEnabled);
}
if (IfKeyPress(Keyboard.current.digit0Key, Keyboard.current.numpad0Key)){
if (skyMode == SkyAmbientMode.Dynamic) skyMode = SkyAmbientMode.Static;
else skyMode = SkyAmbientMode.Dynamic;
Plugin.logger.LogInfo(skyMode);
}
if (direction != 0){
value = Mathf.Clamp(value + direction * 5, -100, 200);
valueRatio = value * 0.01f;
Plugin.logger.LogInfo(value);
}
}
bool IfKeyPress(params KeyControl[] keys){
foreach(var k in keys){
if (k.wasPressedThisFrame) return true;
}
return false;
}
}
}
*/

View file

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
/*
namespace ScarletMansion {
public abstract class ScarletLighting : MonoBehaviour {
public abstract void UpdateLight(float ratio);
public abstract void DisableLights() ;
}
}
*/

View file

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using GameNetcodeStuff;
namespace ScarletMansion.GamePatch.Components {
public class ScarletPlayerControllerB : MonoBehaviour {
public PlayerControllerB player;
public void Initialize(PlayerControllerB player){
this.player = player;
CreateHelmetForFlashlight(player, Assets.flashlight, 0);
CreateHelmetForFlashlight(player, Assets.flashlightBB, 1);
}
public static void CreateHelmetForFlashlight(PlayerControllerB player, Assets.Flashlight flashlight, int index){
try {
var helmetLights = player.allHelmetLights.ToList();
var light = helmetLights[index];
var parent = light.transform.parent;
var gameObj = GameObject.Instantiate(light.gameObject, parent);
gameObj.name = $"scarletlight{index}";
var newFlashlightHelmetIndex = helmetLights.Count;
if (flashlight.scarletHelmetIndex != newFlashlightHelmetIndex)
Plugin.logger.LogInfo($"Created helmet light for scarlet flashlight {index}. Updated index from {flashlight.scarletHelmetIndex} to {newFlashlightHelmetIndex}");
flashlight.scarletHelmetIndex = newFlashlightHelmetIndex;
helmetLights.Add(gameObj.GetComponent<Light>());
player.allHelmetLights = helmetLights.ToArray();
} catch (Exception e) {
Plugin.logger.LogError("Failed to create helmet light for scarlet flashlight");
Plugin.logger.LogError(e.ToString());
flashlight.scarletHelmetIndex = index;
}
}
}
}

View file

@ -0,0 +1,52 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Random = UnityEngine.Random;
namespace ScarletMansion.GamePatch.Components {
public class ScarletProp : MonoBehaviour {
[Header("References")]
public MeshFilter meshFilter;
public MeshRenderer meshRenderer;
public MeshCollider meshCollider;
public Rigidbody rigidbody;
public static float minForce = 4f;
public static float maxForce = 6f;
public static float minAngle = -15f;
public static float maxAngle = 15f;
public static float despawnTime = 10f;
public void CreateProp(GameObject target, Vector3 direction){
gameObject.layer = 6;
transform.position = target.transform.position;
transform.rotation = target.transform.rotation;
transform.localScale = target.transform.lossyScale * 0.9f; // so they don't get stuck in any frame
var targetMesh = target.GetComponent<MeshFilter>().sharedMesh;
meshFilter.sharedMesh = targetMesh;
meshCollider.sharedMesh = targetMesh;
meshRenderer.materials = target.GetComponent<MeshRenderer>().materials;
var randomDirection = Quaternion.Euler(Random.Range(-minAngle, maxAngle), Random.Range(-minAngle, maxAngle), Random.Range(-minAngle, maxAngle)) * direction;
var force = randomDirection * Random.Range(minForce, maxForce);
rigidbody.AddForce(force, ForceMode.Impulse);
StartCoroutine(DespawnTimer());
}
IEnumerator DespawnTimer(){
yield return new WaitForSeconds(despawnTime);
gameObject.SetActive(false);
}
}
}

View file

@ -0,0 +1,67 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace ScarletMansion {
public class ScarletVent : MonoBehaviour {
public AudioSource shakeAudioSource;
public AudioSource summonAudioSource;
public PlayAudioAnimationEvent audioEvent;
[Header("Shake")]
public ParticleSystem spawningPartciles;
public ParticleSystem lightingParticles;
public ParticleSystem emitParticles;
public float volumeToRate = 1f;
public static bool prewarm;
void Start(){
if (!prewarm){
OpenVentClientRpc();
prewarm = true;
Plugin.logger.LogInfo("Prewarming particles by forcing it emit now lmao");
}
}
private void PlayParticleSystem(ParticleSystem ps){
if (ps.isPlaying) return;
ps.Play();
}
private void StopParticleSystem(ParticleSystem ps){
if (!ps.isPlaying) return;
ps.Stop();
}
public void OpenVentClientRpc(){
emitParticles.Play();
audioEvent.PlayAudio1Oneshot();
}
void Update(){
var isPlaying = shakeAudioSource.isPlaying;
if (isPlaying){
PlayParticleSystem(spawningPartciles);
SetEmissionRate(spawningPartciles, shakeAudioSource.volume * volumeToRate);
if (shakeAudioSource.volume >= 0.8f) PlayParticleSystem(lightingParticles);
else StopParticleSystem(lightingParticles);
} else {
StopParticleSystem(spawningPartciles);
StopParticleSystem(lightingParticles);
}
}
private void SetEmissionRate(ParticleSystem ps, float mult){
var emis = ps.emission;
emis.rateOverTimeMultiplier = mult;
}
}
}