LadyAliceMargatroid fd731baf2f Added configs for items and enemies
Critical damage renamed to marked for death and all share the same mechanic now
Revelant now slows down to speed over a fixed short amount instead the nonworking jank before
Moved item/enemy injection to DunGenPlus
2024-12-15 09:23:53 -08:00

543 lines
20 KiB
C#

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 GameNetcodeStuff;
using ScarletMansion.GamePatch.Items;
using ScarletMansion.GamePatch;
using ScarletMansion.GamePatch.Components;
using static LethalLevelLoader.ExtendedEvent;
using ScarletMansion.GamePatch.Managers;
namespace ScarletMansion {
public struct ScarletNetworkParams : INetworkSerializeByMemcpy {
public int scrapValue;
public int specialValue;
}
public class ItemReference {
public Item item;
public int itemId;
public int value;
public ItemReference(Item item, int itemId, int value){
this.item = item;
this.itemId = itemId;
this.value = value;
}
public override string ToString() {
var itemString = item ? item.name : "NULL";
return $"{itemString}:{itemId} ({value})";
}
}
public class EnemyReference {
public EnemyType enemy;
public int index;
public EnemyReference(EnemyType enemy, int index){
this.enemy = enemy;
this.index = index;
}
public override string ToString() {
var enemyString = enemy ? enemy.name : "NULL";
return $"{enemyString}:{index}";
}
}
public class EnemyReferenceSpawnLogic : EnemyReference {
public enum SpawnLogic { None, Special };
public SpawnLogic logic;
public EnemyReferenceSpawnLogic(EnemyType enemy, int index, SpawnLogic logic) : base(enemy, index) {
this.logic = logic;
}
public void ApplySpawnLogic(){
if (logic == SpawnLogic.None) return;
var enemyName = enemy.name.ToLowerInvariant();
if (enemyName == "knight")
KnightSpawnManager.Instance.disableNextKnightSpecialSpawn = true;
else if (enemyName == "jester")
JesterAIPatch.active = true;
}
public override string ToString() {
var b = base.ToString();
return $"{b} [{logic.ToString()}]";
}
}
public class ScarletNetworkManager : NetworkBehaviour {
public static ScarletNetworkManager Instance { get; private set; }
void Awake(){
Instance = this;
}
[ServerRpc(RequireOwnership = false)]
public void DestroyPlayerItemInSlotServerRpc(NetworkBehaviourReference playerRef, int itemSlot, ServerRpcParams callParams = default(ServerRpcParams)){
if (playerRef.TryGet<PlayerControllerB>(out var player)){
Plugin.logger.LogDebug($"P{player.OwnerClientId}, S{callParams.Receive.SenderClientId}");
DestroyPlayerItemInSlotClientRpc(playerRef, itemSlot);
return;
}
Plugin.logger.LogError($"Error trying to get player script (SERVERRPC)");
}
[ClientRpc]
public void DestroyPlayerItemInSlotClientRpc(NetworkBehaviourReference playerRef, int itemSlot){
if (playerRef.TryGet<PlayerControllerB>(out var player) && !player.IsOwner){
player.DestroyPlayerItemInSlot_SDM(itemSlot);
return;
}
if (player == null)
Plugin.logger.LogError($"Error trying to get player script (CLIENTRPC)");
}
[ServerRpc(RequireOwnership = false)]
public void CreateItemServerRpc(int itemId, Vector3 position, ScarletNetworkParams callParams = default(ScarletNetworkParams)){
CreateItem(itemId, false, position, null, callParams);
}
[ServerRpc(RequireOwnership = false)]
public void CreateItemServerRpc(int itemId, Vector3 position, NetworkBehaviourReference playerRef, ScarletNetworkParams callParams = default(ScarletNetworkParams)){
playerRef.TryGet<PlayerControllerB>(out var player);
CreateItem(itemId, false, position, player, callParams);
}
[ServerRpc(RequireOwnership = false)]
public void CreateScrapItemServerRpc(int itemId, Vector3 position, ScarletNetworkParams callParams = default(ScarletNetworkParams)){
CreateItem(itemId, true, position, null, callParams);
}
[ServerRpc(RequireOwnership = false)]
public void CreateScrapItemServerRpc(int itemId, Vector3 position, NetworkBehaviourReference playerRef, ScarletNetworkParams callParams = default(ScarletNetworkParams)){
playerRef.TryGet<PlayerControllerB>(out var player);
CreateItem(itemId, true, position, player, callParams);
}
private void CreateItem(int itemId, bool fromScrapArray, Vector3 position, PlayerControllerB player, ScarletNetworkParams callParams = default(ScarletNetworkParams)){
GameObject prefab;
if (fromScrapArray) {
prefab = Utility.GetDungeonItems()[itemId].spawnableItem.spawnPrefab;
} else {
prefab = StartOfRound.Instance.allItemsList.itemsList[itemId].spawnPrefab;
}
var comp = CreateGrabbableObject(prefab, player, position);
StartCoroutine(WaitForEndOfFrameToUpdateItemInitialProperities(comp));
UpdateItemFallingProperites(comp, position);
UpdateItemElevator(comp, player);
var scarletItem = comp as IScarletItem;
if (scarletItem != null) scarletItem.UpdateSpecialProperties(callParams.specialValue);
var scrapValue = callParams.scrapValue;
if (scrapValue > 0) {
UpdateItemValueProperties(comp, scrapValue);
}
comp.NetworkObject.Spawn(false);
if (player == null) CreateItemClientRpc(comp, position, callParams);
else CreateItemClientRpc(comp, position, player, callParams);
}
[ClientRpc]
public void CreateItemClientRpc(NetworkBehaviourReference itemRef, Vector3 position, ScarletNetworkParams callParams = default(ScarletNetworkParams)){
if (IsServer) return;
StartCoroutine(WaitForItem(itemRef, (c) => CreateItem(c, position, null, callParams)));
}
[ClientRpc]
public void CreateItemClientRpc(NetworkBehaviourReference itemRef, Vector3 position, NetworkBehaviourReference playerRef, ScarletNetworkParams callParams = default(ScarletNetworkParams)){
if (IsServer) return;
StartCoroutine(WaitForItemAndPlayer(itemRef, playerRef, (c, p) => CreateItem(c, position, p, callParams)));
}
private IEnumerator WaitForItem(NetworkBehaviourReference itemRef, Action<GrabbableObject> action){
var t = Time.realtimeSinceStartup + 8f;
GrabbableObject comp;
while(!itemRef.TryGet(out comp)){
yield return null;
if (Time.realtimeSinceStartup > t) {
Plugin.logger.LogError("Failed to find network object (ITEM)");
yield break;
}
}
yield return new WaitForEndOfFrame();
action.Invoke(comp);
}
private IEnumerator WaitForItemAndPlayer(NetworkBehaviourReference itemRef, NetworkBehaviourReference playerRef, Action<GrabbableObject, PlayerControllerB> action){
var t = Time.realtimeSinceStartup + 8f;
GrabbableObject comp;
while(!itemRef.TryGet(out comp)){
yield return null;
if (Time.realtimeSinceStartup > t) {
Plugin.logger.LogError("Failed to find network object (ITEM)");
yield break;
}
}
PlayerControllerB player;
while(!playerRef.TryGet(out player)){
yield return null;
if (Time.realtimeSinceStartup > t) {
Plugin.logger.LogError("Failed to find network object (PLAYER)");
yield break;
}
}
yield return new WaitForEndOfFrame();
action.Invoke(comp, player);
}
private void CreateItem(GrabbableObject item, Vector3 position, PlayerControllerB player, ScarletNetworkParams callParams = default(ScarletNetworkParams)) {
UpdateItemFallingProperites(item, position);
UpdateItemElevator(item, player);
var scarletItem = item as IScarletItem;
if (scarletItem != null) scarletItem.UpdateSpecialProperties(callParams.specialValue);
var scrapValue = callParams.scrapValue;
if (scrapValue > 0) {
UpdateItemValueProperties(item, scrapValue);
}
}
private GrabbableObject CreateGrabbableObject(GameObject prefab, PlayerControllerB player, Vector3 position) {
var parent = GetItemSpawnTransform(player);
var gameObject = GameObject.Instantiate(prefab, position, Quaternion.identity, parent);
return gameObject.GetComponent<GrabbableObject>();
}
private Transform GetItemSpawnTransform(PlayerControllerB player) {
if (((player != null && player.isInElevator) || StartOfRound.Instance.inShipPhase) && RoundManager.Instance.spawnedScrapContainer != null) {
return RoundManager.Instance.spawnedScrapContainer;
}
return StartOfRound.Instance.elevatorTransform;
}
private IEnumerator WaitForEndOfFrameToUpdateItemInitialProperities(GrabbableObject comp) {
yield return new WaitForEndOfFrame();
UpdateItemInitialProperites(comp);
}
private void UpdateItemInitialProperites(GrabbableObject comp) {
comp.reachedFloorTarget = false;
comp.hasHitGround = false;
comp.fallTime = 0f;
}
private void UpdateItemFallingProperites(GrabbableObject comp, Vector3 position) {
comp.startFallingPosition = position;
comp.targetFloorPosition = comp.GetItemFloorPosition(position);
}
private void UpdateItemValueProperties(GrabbableObject comp, int value) {
comp.SetScrapValue(value);
RoundManager.Instance.totalScrapValueInLevel += value;
}
private void UpdateItemElevator(GrabbableObject comp, PlayerControllerB player){
if (player != null && player.isInHangarShipRoom) {
player.SetItemInElevator(true, true, comp);
}
}
public void RequestEvilSkinApply(NetworkObjectReference reference, string enemyName) {
if (PluginConfig.Instance.paintingEnemyEvilSkinValue && ScarletBedroom.ENEMY_EVIL_LIST.Contains(enemyName)) {
RequestEvilSkinApplyClientRpc(reference);
}
}
[ClientRpc]
public void RequestEvilSkinApplyClientRpc(NetworkObjectReference reference){
void ApplyMaterialToRenderers<T>(T[] renderers) where T: Renderer{
foreach(var render in renderers) {
var mats = render.materials;
for(var i = 0; i < mats.Length; i++) {
var material = new Material(Assets.networkObjectList.ghostMaterial);
material.mainTexture = mats[i].mainTexture;
mats[i] = material;
}
render.materials = mats;
}
}
if (reference.TryGet(out var obj)){
var enemy = obj.GetComponentInParent<EnemyAI>();
if (enemy == null) return;
var enemyName = enemy.name;
try {
Plugin.logger.LogDebug($"Applying evil material to {enemyName}");
ApplyMaterialToRenderers(enemy.GetComponentsInChildren<SkinnedMeshRenderer>());
ApplyMaterialToRenderers(enemy.GetComponentsInChildren<MeshRenderer>());
} catch (Exception e){
Plugin.logger.LogWarning($"Failed to apply evil material to {enemyName}");
Plugin.logger.LogWarning(e.ToString());
}
}
}
public void CreateSpawnAudioPrefab(Vector3 positon, ulong playerId) {
CreateSpawnAudioPrefabServerRpc(positon, playerId);
CreateSpawnAudioPrefab(positon);
}
[ServerRpc(RequireOwnership = false)]
public void CreateSpawnAudioPrefabServerRpc(Vector3 position, ulong playerId){
CreateSpawnAudioPrefabClientRpc(position, playerId);
}
[ClientRpc]
public void CreateSpawnAudioPrefabClientRpc(Vector3 position, ulong playerId){
if (StartOfRound.Instance.localPlayerController.actualClientId == playerId) return;
CreateSpawnAudioPrefab(position);
}
private void CreateSpawnAudioPrefab(Vector3 position) {
var copy = Instantiate(Assets.networkObjectList.yukariSpawnPrefab, position, Quaternion.identity);
var audioSource = copy.GetComponentInChildren<AudioSource>();
audioSource.time = 0.5f;
Destroy(copy, 5f);
}
[ClientRpc]
public void SetupBookPathsClientRpc(){
if (ScarletGenericManager.Instance == null) return;
var bookPaths = ScarletGenericManager.Instance.bookPaths;
foreach(var b in bookPaths) {
b.ResetPath();
}
}
[ClientRpc]
public void CallSelfDestroyTargetClientRpc(int index){
if (ScarletGenericManager.Instance == null) return;
ScarletGenericManager.Instance.selfDestroyTargets[index].Destroy();
}
}
public static class ScarletNetworkManagerUtility {
public static int GetMarkedDamageToPlayer(PlayerControllerB player) {
if (IsPlayerMarkedForDeath(player)) return player.health;
if (!player.criticallyInjured) {
return player.health - 5;
}
return player.health;
}
public static bool IsPlayerMarkedForDeath(PlayerControllerB player){
var scarletPlayer = ScarletPlayerControllerB.GetScarletPlayerScript(player);
if (scarletPlayer) return scarletPlayer.markedForDeath;
return false;
}
public static void MarkPlayerForDeath(PlayerControllerB player){
var scarletPlayer = ScarletPlayerControllerB.GetScarletPlayerScript(player);
if (scarletPlayer) scarletPlayer.markedForDeath = true;
}
public static int GetFlashlightId(FlashlightItem flashlightItem){
var flashlight = Assets.GetFlashlight(flashlightItem.itemProperties);
if (flashlight != null) return flashlight.itemId;
return -1;
}
public static NetworkObjectReference CreateEnemyWithRef(EnemyReferenceSpawnLogic enemy, Vector3 position, float yRotation) {
var roundManager = RoundManager.Instance;
if (roundManager == null || !roundManager.IsHost) return default;
return roundManager.SpawnEnemyGameObject(position, yRotation, enemy.index);
}
public static T CreateEnemyWithRef<T>(EnemyReferenceSpawnLogic enemy, Vector3 position, float yRotation) where T: EnemyAI {
var spawnedEnemy = CreateEnemyWithRef(enemy, position, yRotation);
if (spawnedEnemy.TryGet(out var obj)) {
return obj.GetComponentInChildren<T>();
}
return null;
}
public static NetworkObjectReference CreateEnemyWithType(EnemyType enemy, Vector3 position, float yRotation) {
var roundManager = RoundManager.Instance;
if (roundManager == null || !roundManager.IsHost) return default;
return roundManager.SpawnEnemyGameObject(position, yRotation, -1, enemy);
}
public static T CreateEnemyWithType<T>(EnemyType enemy, Vector3 position, float yRotation) where T: EnemyAI {
var spawnedEnemy = CreateEnemyWithType(enemy, position, yRotation);
if (spawnedEnemy.TryGet(out var obj)) {
return obj.GetComponentInChildren<T>();
}
return null;
}
public static bool CreateFlashlight(PlayerControllerB player, FlashlightItem flashLight, FlandreCrystal crystal){
var color = crystal.colorIndex;
var position = crystal.transform.position + Vector3.up * 0.25f;
var id = GetFlashlightId(flashLight);
var scrapValue = crystal.scrapValue;
var flashlightValue = scrapValue;
var nextCrystal = crystal.targetTransformation;
if (nextCrystal) {
flashlightValue = Mathf.RoundToInt(flashlightValue * 0.5f);
}
if (id == -1) {
var flashString = flashLight ? flashLight.itemProperties.itemName : "NULL";
Plugin.logger.LogError($"Failed to find a corresponding flashlight for {flashString}");
return false;
}
player.DestroyPlayerItemAndSync_SDM(flashLight);
player.DestroyPlayerItemAndSync_SDM(crystal);
ScarletNetworkParams callParams = new ScarletNetworkParams() { scrapValue = flashlightValue, specialValue = color};
ScarletNetworkManager.Instance.CreateItemServerRpc(id, position, player, callParams);
if (nextCrystal) {
var brokenCrystal = Assets.GetGlobalItem(nextCrystal);
if (brokenCrystal == null) {
Plugin.logger.LogError($"Failed to find a corresponding global item for {nextCrystal.itemName}");
return true;
}
var brokenCrystalId= brokenCrystal.itemId;
if (brokenCrystalId == -1) {
Plugin.logger.LogError($"Failed to find a corresponding id for {nextCrystal.itemName}");
return true;
}
var colorLength = FlandreCrystal.colorVariants.Length;
var nextScrapColorValue = scrapValue % colorLength;
var nextScrapValue = flashlightValue - (flashlightValue % colorLength) + nextScrapColorValue;
ScarletNetworkParams nextCallParams = new ScarletNetworkParams() { scrapValue = nextScrapValue};
ScarletNetworkManager.Instance.CreateItemServerRpc(brokenCrystalId, position, player, nextCallParams);
}
return true;
}
public static void DestroyPlayerItemAndSync_SDM(this PlayerControllerB player, GrabbableObject obj){
if (!player.IsOwner) return;
var itemSlots = player.ItemSlots;
for(var i = 0; i < itemSlots.Length; ++i){
if (itemSlots[i] == obj) {
DestroyPlayerItemInSlotAndSync_SDM(player, i);
return;
}
}
var grabObjString = obj ? obj.itemProperties.itemName : "NULL";
Plugin.logger.LogError($"Player {player.playerUsername}:{player.actualClientId} tried to destroy item {grabObjString} which is empty or not owned by player");
}
public static void DestroyPlayerItemInSlotAndSync_SDM(this PlayerControllerB player, int itemSlot){
if (!player.IsOwner) return;
var itemSlots = player.ItemSlots;
if (itemSlot >= itemSlots.Length || itemSlots[itemSlot] == null) {
Plugin.logger.LogError($"Player {player.playerUsername}:{player.actualClientId} tried to destroy item in slot {itemSlot} which is empty or overflowed");
return;
}
player.timeSinceSwitchingSlots = 0f;
player.DestroyPlayerItemInSlot_SDM(itemSlot);
ScarletNetworkManager.Instance.DestroyPlayerItemInSlotServerRpc(player, itemSlot);
}
public static void DestroyPlayerItemInSlot_SDM(this PlayerControllerB player, int itemSlot){
// base game does this
// better safe and slow then sorry
if (GameNetworkManager.Instance.localPlayerController == null || NetworkManager.Singleton == null || NetworkManager.Singleton.ShutdownInProgress) return;
Plugin.logger.LogDebug($"Destroying player {player.playerUsername}:{player.actualClientId}'s item in slot {itemSlot}");
var heldObj = player.currentlyHeldObjectServer;
var heldObjString = heldObj ? heldObj.itemProperties.itemName : "NULL";
var grabObj = player.ItemSlots[itemSlot];
var grabObjString = grabObj ? grabObj.itemProperties.itemName : "NULL";
Plugin.logger.LogDebug($"Held item {player.currentItemSlot}:{heldObjString}");
Plugin.logger.LogDebug($"Target item {itemSlot}:{grabObjString}");
// fix weight and values
player.carryWeight -= Mathf.Clamp(grabObj.itemProperties.weight - 1f, 0f, 10f);
RoundManager.Instance.totalScrapValueInLevel -= grabObj.scrapValue;
// destroy radar
if (grabObj.radarIcon) {
GameObject.Destroy(grabObj.radarIcon.gameObject);
}
// fix character animations and UI
// if target item is the same as held item
if (player.isHoldingObject) {
if (player.currentItemSlot == itemSlot) {
player.isHoldingObject = false;
player.twoHanded = false;
if (player.IsOwner) {
player.playerBodyAnimator.SetBool("cancelHolding", true);
player.playerBodyAnimator.SetTrigger("Throw");
HUDManager.Instance.holdingTwoHandedItem.enabled = false;
HUDManager.Instance.ClearControlTips();
player.activatingItem = false;
}
}
if (heldObj != null && heldObj == grabObj) {
if (player.IsOwner) {
player.SetSpecialGrabAnimationBool(false, heldObj);
}
player.currentlyHeldObjectServer = null;
}
}
// no matter what, we need these to be called
if (player.IsOwner) {
HUDManager.Instance.itemSlotIcons[itemSlot].enabled = false;
grabObj.DiscardItemOnClient();
}
player.ItemSlots[itemSlot] = null;
if (player.IsServer) grabObj.NetworkObject.Despawn(true);
}
}
}