Added more radio music, based on dungeon variant.

Redid vent code to work better and like work in general.
Knight animation now pauses for a second.
Knights no longer trigger the default scarlet vent animation.
Revenants now spawn at a vent if no knights exist.
This commit is contained in:
LadyAliceMargatroid 2025-01-15 20:41:48 -08:00
parent e28f3ca2db
commit f37cded831
14 changed files with 285 additions and 114 deletions

View File

@ -104,12 +104,12 @@ namespace ScarletMansion.DunGenPatch {
DungeonFlow.GlobalPropSettings GetGlobalPropSetting(int id) {
foreach(var p in dungeonConfig.dungeon.GlobalProps){
if (p.ID == id ) return p;
if (p.ID == id) return p;
}
return null;
}
GetGlobalPropSetting(254).Count = new DunGen.IntRange(
GetGlobalPropSetting(254).Count = new IntRange(
dungeonConfig.paintingCountValue,
dungeonConfig.paintingCountValue
);

View File

@ -5,12 +5,23 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Events;
namespace ScarletMansion {
public class KnightSpawnPoint : MonoBehaviour {
//[System.Serializable]
//public class OnRemoveUnityEvent {
// public UnityEvent onSelected;
// public UnityEvent onStop;
//}
//public OnRemoveUnityEvent onRemoveNormalUnityEvent;
//public OnRemoveUnityEvent onRemoveGhostUnityEvent;
public int index;
public GameObject renderGameObject;
public void Start(){
if (GameNetworkManager.Instance.isHostingGame)
@ -45,6 +56,13 @@ namespace ScarletMansion {
}
}
public void OnStop(){
renderGameObject.SetActive(false);
StopAllCoroutines();
//GetUnityEvent(isNormal).onStop.Invoke();
}
}
}

View File

@ -21,7 +21,7 @@ namespace ScarletMansion.GamePatch.Components {
public static ActionList onBedroomEndEvent = new ActionList("onBedroomEnd");
[Header("References")]
public ScarletVent vent;
public ScarletVentVisuals vent;
public Transform paintingSpawnTransform;
public AudioSource screamAudioSource;
public Transform[] itemSpawns;
@ -32,7 +32,7 @@ namespace ScarletMansion.GamePatch.Components {
private EnemyReferenceSpawnLogic bonusEnemy;
const float spawnTime = 10f;
const float cloudTime = 4f;
const float cloudTime = 7f;
public void Anger(Transform target){
AngerManager.Instance.Anger();
@ -84,9 +84,9 @@ namespace ScarletMansion.GamePatch.Components {
onBedroomEndEvent.Call();
// stop volume stuff
vent.OpenVentClientRpc();
vent.shakeAudioSource.Stop();
vent.shakeAudioSource.volume = 0f;
vent.StopSpawnParticles();
vent.StopLightningParticles();
ScarletNetworkManager.Instance.CreateSpawnAudioPrefabLocal(target.transform.position);
// unlock doors
if (roundmanager.IsServer){
@ -97,13 +97,13 @@ namespace ScarletMansion.GamePatch.Components {
}
public IEnumerator IncreaseVentAudio(){
yield return new WaitForSeconds(1f);
yield return new WaitForSeconds(3f);
vent.shakeAudioSource.Play();
vent.StartSpawnParticles();
var t = 0f;
while (t < cloudTime){
vent.shakeAudioSource.volume = t / cloudTime;
t += Time.deltaTime;
vent.SetVolumeLazy(t / cloudTime);
yield return null;
}
}

View File

@ -41,12 +41,19 @@ namespace ScarletMansion.GamePatch.Components {
void Start() {
audioSource.volume = maxVolume * (volume * 0.1f);
var inst = ScarletGenericManager.Instance;
if (inst) {
audioClips = inst.radioAudioClips;
}
}
public override void OnNetworkSpawn() {
if (IsOwner) {
ToggleOnOffSwitchClientRpc(UnityEngine.Random.value > 0.9f);
ToggleVolumeSwitchClientRpc(UnityEngine.Random.Range(3, 7));
if (audioClips.Length == 0) return;
ToggleSongSwitchClientRpc(UnityEngine.Random.Range(0, audioClips.Length));
}
}
@ -112,6 +119,8 @@ namespace ScarletMansion.GamePatch.Components {
[ServerRpc(RequireOwnership = false)]
public void ToggleSongSwitchServerRpc(){
if (audioClips.Length == 0) return;
ToggleSongSwitchClientRpc((audioIndex + 1) % audioClips.Length);
treasureRoomEvent?.UpdateTreasureDoorStatus();
}

View File

@ -1,4 +1,5 @@
using System;
using ScarletMansion.GamePatch.Components;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
@ -7,61 +8,43 @@ using System.Threading.Tasks;
using UnityEngine;
namespace ScarletMansion {
public class ScarletVent : MonoBehaviour {
public class ScarletVent : EnemyVent {
public AudioSource shakeAudioSource;
public AudioSource summonAudioSource;
public PlayAudioAnimationEvent audioEvent;
[Header("References")]
public ScarletVentVisuals visuals;
public EnemyType ignoreEnemyType;
[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.LogDebug("Prewarming particles by forcing it emit now lmao");
}
void Awake(){
ignoreEnemyType = Assets.knight.enemyType;
}
private void PlayParticleSystem(ParticleSystem ps){
if (ps.isPlaying) return;
ps.Play();
public void ScarletUpdate(){
if (occupied && enemyType != ignoreEnemyType){
var startTime = spawnTime - roundManager.timeScript.currentDayTime;
if (isPlayingAudio) {
var lerp = Mathf.Abs(startTime / enemyType.timeToPlayAudio - 1f);
visuals.SetVolumeLazy(lerp);
lowPassFilter.lowpassResonanceQ = Math.Abs(lerp * 2f - 2f);
return;
}
if (startTime < enemyType.timeToPlayAudio) {
isPlayingAudio = true;
visuals.StartSpawnParticles();
return;
}
} else if (isPlayingAudio) {
isPlayingAudio = false;
visuals.StopSpawnParticles();
visuals.StopLightningParticles();
}
private void StopParticleSystem(ParticleSystem ps){
if (!ps.isPlaying) return;
ps.Stop();
}
public void OpenVentClientRpc(){
emitParticles.Play();
audioEvent.PlayAudio1Oneshot();
ScarletNetworkManager.Instance.CreateSpawnAudioPrefabLocal(transform.position);
}
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;
}
}
}

View File

@ -0,0 +1,109 @@
using OdinSerializer;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting.Metadata.W3cXsd2001;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace ScarletMansion.GamePatch.Components
{
public class ScarletVentVisuals : MonoBehaviour {
[Header("Shake")]
public AudioSource shakeAudioSource;
public ParticleSystem spawningPartciles;
[Header("Shake")]
public AudioSource lightingAudioSource;
public ParticleSystem lightingParticles;
public static bool prewarm;
void Start(){
if (!prewarm){
prewarm = true;
Plugin.logger.LogDebug("Prewarming particles by forcing it emit now lmao");
}
}
private IEnumerator StopAudioSourceLerp(AudioSource audioSource, float volume){
while(audioSource.volume != volume){
audioSource.volume = Mathf.MoveTowards(audioSource.volume, volume, Time.deltaTime * 2f);
yield return null;
}
if (volume == 0f) audioSource.Stop();
}
public void SetVolumeLazy(float volume){
SetSpawnParticlesVolume(volume);
var altVolume = Mathf.InverseLerp(0.7f, 1f, volume);
if (altVolume > 0f){
StartLightningParticles();
SetLightningVolume(altVolume);
}
}
private Coroutine previousSpawnParticlesCoroutine;
public void StartSpawnParticles(){
if (!spawningPartciles.isPlaying) {
spawningPartciles.Play();
shakeAudioSource.Play();
shakeAudioSource.volume = 0f;
}
}
public void StopSpawnParticles(){
if (spawningPartciles.isPlaying) {
spawningPartciles.Stop();
spawningPartciles.Clear();
if (previousSpawnParticlesCoroutine != null) StopCoroutine(previousSpawnParticlesCoroutine);
previousSpawnParticlesCoroutine = StartCoroutine(StopAudioSourceLerp(shakeAudioSource, 0f));
}
}
public void SetSpawnParticlesVolume(float volume){
shakeAudioSource.volume = volume * 0.6f;
SetEmissionRate(spawningPartciles, volume * 5f);
}
private Coroutine previousLightningParticlesCoroutine;
public void StartLightningParticles(){
if (!lightingParticles.isPlaying) {
lightingParticles.Play();
lightingAudioSource.Play();
lightingAudioSource.volume = 0f;
}
}
public void StopLightningParticles(){
if (lightingParticles.isPlaying) {
lightingParticles.Stop();
if (previousLightningParticlesCoroutine != null) StopCoroutine(previousLightningParticlesCoroutine);
previousLightningParticlesCoroutine = StartCoroutine(StopAudioSourceLerp(lightingAudioSource, 0f));
}
}
public void SetLightningVolume(float volume){
lightingAudioSource.volume = volume * 0.6f;
}
private void SetEmissionRate(ParticleSystem ps, float mult){
var emis = ps.emission;
emis.rateOverTimeMultiplier = mult;
}
}
}

View File

@ -24,6 +24,9 @@ namespace ScarletMansion {
public AudioClip[] ramAudioClips;
public KnightSpawnPoint spawnPoint;
public bool activated;
public override void Start(){
base.Start();
@ -31,10 +34,18 @@ namespace ScarletMansion {
if (IsOwner && KnightSpawnManager.Instance) {
var index = KnightSpawnManager.Instance.GetSpawnPointIndex();
if (index == -1) return;
if (index == -1) {
// grab position instead
var allVents = RoundManager.Instance.allEnemyVents;
var anyEnemyVent = allVents[UnityEngine.Random.Range(0, allVents.Length)];
SyncKnightAtPositionClientRpc(anyEnemyVent.floorNode.position, Quaternion.Euler(0f, anyEnemyVent.floorNode.eulerAngles.y, 0f));
return;
}
SyncKnightReplacementClientRpc(index);
}
}
public override void DoAIInterval() {
@ -44,6 +55,7 @@ namespace ScarletMansion {
if (targetPlayer == null || !targetPlayer.IsOwner) return;
if (isEnemyDead) return;
if (timeSinceSpawn < 1f) return;
var targetOutOfMap = !PlayerIsTargetable(targetPlayer);
if (targetOutOfMap){
@ -67,6 +79,12 @@ namespace ScarletMansion {
public override void Update() {
base.Update();
if (isEnemyDead) return;
if (timeSinceSpawn < 1f) return;
if (!activated) {
activated = true;
creatureAnimator.SetBool("Activate", true);
}
var trueSpeed = currentBehaviourStateIndex == 0 ? maxChaseSpeed : slowChaseSpeed;
if (IsOwner) {
@ -80,6 +98,8 @@ namespace ScarletMansion {
public override void OnCollideWithPlayer(Collider other) {
base.OnCollideWithPlayer(other);
if (timeSinceSpawn < 1f) return;
var playerControllerB = MeetsStandardPlayerCollisionConditions(other, false, false);
if (playerControllerB != null && playerControllerB.IsOwner && playerControllerB == targetPlayer) {
var damage = ScarletNetworkManagerUtility.GetMarkedDamageToPlayer(playerControllerB);
@ -92,7 +112,7 @@ namespace ScarletMansion {
Assets.onPlayerDeath.Call(new ModPatch.CoronerParameters(playerControllerB, ModPatch.CoronerDeathEnum.GhostKnight));
}
RoundManager.PlayRandomClip(creatureVoice, ramAudioClips, false, 1f, 0);
RoundManager.PlayRandomClip(creatureVoice, ramAudioClips, false, 1f, 1);
CallDisappear();
}
}
@ -102,25 +122,26 @@ namespace ScarletMansion {
Plugin.logger.LogDebug($"Spawning ghost knight at {index}");
try {
var target = KnightSpawnManager.Instance.GetSpawnPointTransform(index);
target.gameObject.SetActive(false);
transform.position = target.position;
transform.rotation = target.rotation;
serverPosition = target.position;
if (agent == null) agent = GetComponentInChildren<NavMeshAgent>();
agent.Warp(target.position);
if (IsOwner) {
SyncPositionToClients();
}
spawnPoint = KnightSpawnManager.Instance.GetSpawnPoint(index);
KnightSpawnManager.Instance.SpawnEnemyOnSpawnPoint(this, spawnPoint);
} catch (Exception e){
Plugin.logger.LogError($"Tried to ghost knight spawn at {index}, but completely failed");
Plugin.logger.LogError(e);
}
}
[ClientRpc]
public void SyncKnightAtPositionClientRpc(Vector3 position, Quaternion rotation){
Plugin.logger.LogDebug($"Spawning ghost knight at {position}");
try {
KnightSpawnManager.Instance.SpawnEnemyOnSpawnPoint(this, position, rotation);
} catch (Exception e){
Plugin.logger.LogError($"Tried to ghost knight spawn at {position}, but completely failed");
Plugin.logger.LogError(e);
}
}
public void FindAndTunnelPlayer(Vector3 searchPosition){
var validPlayers = StartOfRound.Instance.allPlayerScripts.Where(p => p != null && PlayerIsTargetable(p));

View File

@ -48,6 +48,9 @@ namespace ScarletMansion.GamePatch.Enemies {
// Token: 0x04000F2A RID: 3882
private bool inCooldownAnimation;
public KnightSpawnPoint spawnPoint;
public bool activated;
public override void Start(){
base.Start();
@ -56,7 +59,10 @@ namespace ScarletMansion.GamePatch.Enemies {
if (IsOwner && KnightSpawnManager.Instance) {
var index = KnightSpawnManager.Instance.GetSpawnPointIndex();
if (index == -1) return;
Plugin.logger.LogInfo($"Selected index {index}");
if (index == -1) {
return;
}
SyncKnightReplacementClientRpc(index);
}
@ -95,6 +101,7 @@ namespace ScarletMansion.GamePatch.Enemies {
base.DoAIInterval();
if (StartOfRound.Instance.allPlayersDead) return;
if (isEnemyDead) return;
if (timeSinceSpawn < 1f) return;
var currentBehaviourStateIndex = this.currentBehaviourStateIndex;
if (currentBehaviourStateIndex != 0) {
@ -242,6 +249,12 @@ namespace ScarletMansion.GamePatch.Enemies {
base.Update();
if (isEnemyDead) return;
if (hitPlayerTimer >= 0f) hitPlayerTimer -= Time.deltaTime;
if (timeSinceSpawn < 1f) return;
if (!activated) {
activated = true;
creatureAnimator.SetBool("Activate", true);
}
if (!IsOwner) {
stopMovementTimer = 5f;
@ -419,6 +432,8 @@ namespace ScarletMansion.GamePatch.Enemies {
return;
}
if (timeSinceSpawn < 1f) return;
var playerControllerB = MeetsStandardPlayerCollisionConditions(other, false, false);
if (playerControllerB != null) {
hitPlayerTimer = 0.2f;
@ -443,19 +458,9 @@ namespace ScarletMansion.GamePatch.Enemies {
Plugin.logger.LogDebug($"Spawning knight at {index}");
try {
var target = KnightSpawnManager.Instance.GetSpawnPointTransform(index);
target.gameObject.SetActive(false);
spawnPoint = KnightSpawnManager.Instance.GetSpawnPoint(index);
KnightSpawnManager.Instance.SpawnEnemyOnSpawnPoint(this, spawnPoint);
transform.position = target.position;
transform.rotation = target.rotation;
serverPosition = target.position;
if (agent == null) agent = GetComponentInChildren<NavMeshAgent>();
agent.Warp(target.position);
if (IsOwner) {
SyncPositionToClients();
}
} catch (Exception e){
Plugin.logger.LogError($"Tried to knight spawn at {index}, but completely failed");
Plugin.logger.LogError(e);

View File

@ -221,26 +221,7 @@ namespace ScarletMansion {
}
public void SyncKnightReplacement(int index){
Plugin.logger.LogDebug($"Spawning knight at {index}");
try {
var target = KnightSpawnManager.Instance.GetSpawnPointTransform(index);
target.gameObject.SetActive(false);
transform.position = target.position;
transform.rotation = target.rotation;
serverPosition = target.position;
if (agent == null) agent = GetComponentInChildren<NavMeshAgent>();
agent.Warp(target.position);
if (IsOwner) {
SyncPositionToClients();
}
} catch (Exception e){
Plugin.logger.LogError($"Tried to knight spawn at {index}, but completely failed");
Plugin.logger.LogError(e);
}
}

View File

@ -5,6 +5,7 @@ using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using HarmonyLib;
using OdinSerializer;
namespace ScarletMansion.GamePatch {
[HarmonyPatch(typeof(EnemyVent))]
@ -35,14 +36,25 @@ namespace ScarletMansion.GamePatch {
public static void OpenVentClientRpcPatchPost(ref EnemyVent __instance){
if (setActive == false) return;
var comp = __instance.GetComponent<ScarletVent>();
if (comp) {
var scarlet = __instance as ScarletVent;
if (scarlet && (scarlet.enemyType != scarlet.ignoreEnemyType || !KnightSpawnManager.WillSpawnOnSpawnPoint())) {
Plugin.logger.LogDebug("Doing scalet open vent");
comp.OpenVentClientRpc();
scarlet.OpenVentClientRpc();
}
setActive = false;
}
[HarmonyPrefix]
[HarmonyPatch("Update")]
public static bool UpdatePrePatch(ref EnemyVent __instance){
var scarlet = __instance as ScarletVent;
if (scarlet != null) {
scarlet.ScarletUpdate();
return false;
}
return true;
}
}
}

View File

@ -5,6 +5,8 @@ using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using DunGen;
using static LethalLevelLoader.ExtendedEvent;
using UnityEngine.AI;
namespace ScarletMansion {
@ -22,6 +24,13 @@ namespace ScarletMansion {
Instance = this;
}
public static bool WillSpawnOnSpawnPoint(){
if (Instance) {
return !Instance.disableNextKnightSpecialSpawn && Instance.spawnPoints.Count > 0;
}
return false;
}
public void OnDungeonComplete(Dungeon dungeon) {
var points = dungeon.GetComponentsInChildren<KnightSpawnPoint>();
spawnPoints = points.ToList();
@ -60,10 +69,29 @@ namespace ScarletMansion {
return item.index;
}
public Transform GetSpawnPointTransform(int index){
return spawnPoints[index].transform;
public KnightSpawnPoint GetSpawnPoint(int index){
return spawnPoints[index];
}
public void SpawnEnemyOnSpawnPoint(EnemyAI enemyAI, KnightSpawnPoint spawnPoint){
SpawnEnemyOnSpawnPoint(enemyAI, spawnPoint.transform.position, spawnPoint.transform.rotation);
spawnPoint.OnStop();
}
public void SpawnEnemyOnSpawnPoint(EnemyAI enemyAI, Vector3 position, Quaternion rotation){
enemyAI.transform.position = position;
enemyAI.transform.rotation = rotation;
enemyAI.serverPosition = position;
if (enemyAI.agent == null) enemyAI.agent = GetComponentInChildren<NavMeshAgent>();
enemyAI.agent.Warp(position);
if (enemyAI.IsOwner) {
enemyAI.SyncPositionToClients();
}
ScarletNetworkManager.Instance?.CreateSpawnAudioPrefabLocal(position);
}
}
}

View File

@ -16,6 +16,10 @@ namespace ScarletMansion.GamePatch.Managers {
public static ScarletGenericManager Instance { get; private set; }
[Header("Settings")]
public AudioClip[] radioAudioClips;
[Header("Debug")]
public List<Transform> roomsOfInterest;
public List<ScarletBedroom> bedrooms;
@ -42,7 +46,8 @@ namespace ScarletMansion.GamePatch.Managers {
}
void Update(){
if (!StartOfRound.Instance.IsServer) return;
if (!StartOfRound.Instance) return;
if (StartOfRound.Instance.IsServer) return;
if (selfDestroyTargets == null) return;
var time = Utility.GetTime();

View File

@ -10,7 +10,6 @@ using GameNetcodeStuff;
using ScarletMansion.GamePatch.Items;
using ScarletMansion.GamePatch;
using ScarletMansion.GamePatch.Components;
using static LethalLevelLoader.ExtendedEvent;
using ScarletMansion.GamePatch.Managers;
using ScarletMansion.Configs;
@ -306,7 +305,7 @@ namespace ScarletMansion {
public void CreateSpawnAudioPrefab(Vector3 positon, ulong playerId) {
CreateSpawnAudioPrefabServerRpc(positon, playerId);
CreateSpawnAudioPrefab(positon);
CreateSpawnAudioPrefabLocal(positon);
}
[ServerRpc(RequireOwnership = false)]
@ -317,10 +316,10 @@ namespace ScarletMansion {
[ClientRpc]
public void CreateSpawnAudioPrefabClientRpc(Vector3 position, ulong playerId){
if (StartOfRound.Instance.localPlayerController.actualClientId == playerId) return;
CreateSpawnAudioPrefab(position);
CreateSpawnAudioPrefabLocal(position);
}
private void CreateSpawnAudioPrefab(Vector3 position) {
public void CreateSpawnAudioPrefabLocal(Vector3 position) {
var copy = Instantiate(Assets.networkObjectList.yukariSpawnPrefab, position, Quaternion.identity);
var audioSource = copy.GetComponentInChildren<AudioSource>();
audioSource.time = 0.5f;

View File

@ -187,6 +187,7 @@
<Compile Include="GamePatch\Components\ScarletProp.cs" />
<Compile Include="GamePatch\Components\ScarletRadio.cs" />
<Compile Include="GamePatch\Components\ScarletVent.cs" />
<Compile Include="GamePatch\Components\ScarletVentVisuals.cs" />
<Compile Include="GamePatch\Components\ScarletYukariTrigger.cs" />
<Compile Include="GamePatch\Components\TreasureRoom\TreasureRoom.cs" />
<Compile Include="GamePatch\Components\TreasureRoom\TreasureRoomBookSwitch.cs" />