manager/Manager.Difficulty.cs

Manager partial class file for difficulty handling. It stores and persists which difficulties have been beaten, provides difficulty names, descriptions and label colors, manages current Difficulty with networked RPC to set it, and offers gameplay modifiers like enemy health and late-game damage scaling.

File AccessNetworking
using Sandbox;
using Sandbox.Diagnostics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;

public partial class Manager
{
	private static string FilePath => "difficulty_wins.json";

	[Sync] public int Difficulty { get; set; }

	public const int MinDifficulty = 0;
	public const int MaxDifficulty = 2;

	private Dictionary<int, bool> DifficultyVictories = new();

	public int FirstDifficultyWithoutPauseChoosing = 3;
	public int FirstDifficultyWithCurses = 2;

	//private GameObject _effectsObj;

	[Property] public Dictionary<int, GameObject> DifficultyEffects { get; set; }

	public void RegisterVictoryForDifficulty( int difficulty )
	{
		DifficultyVictories[difficulty] = true;
		SaveDifficultyVictories();
	}

	void SaveDifficultyVictories()
	{
		FileSystem.Data.WriteJson( FilePath, DifficultyVictories );
	}

	void LoadDifficultyVictories()
	{
		DifficultyVictories = FileSystem.Data.ReadJsonSafe<Dictionary<int, bool>>( FilePath, new Dictionary<int, bool>() );
	}

	public bool HasBeatenDifficulty( int difficulty )
	{
		//return true;
		//return false;
		return DifficultyVictories.ContainsKey( difficulty ) && DifficultyVictories[difficulty];
	}

	public static string GetNameForDifficulty( int difficulty )
	{
		switch ( difficulty )
		{
			case -1: return "All Difficulties";
			case 0: default: return "Normal Difficulty";
			case 1: return "Expert Difficulty";
			case 2: return "Cursed Difficulty";
		}
	}

	public static string GetDescriptionForDifficulty( int difficulty )
	{
		switch ( difficulty )
		{
			case -1: return "";
			case 0: default: return "";
			case 1: return "";
			case 2: return "";
		}
	}

	public static Color GetDifficultyLabelColor( int difficulty )
	{
		switch ( difficulty )
		{
			case -1: return new Color( 1f, 1f, 1f, 0.3f );
			case 0: default:  return new Color( 1f, 1f, 1f, 0.5f );
			case 1: return new Color( 1f, 0f, 0f, 0.7f );
			case 2: return new Color( 0.7f, 0.2f, 1f, 0.7f );
		}
	}

	[Rpc.Broadcast( NetFlags.Reliable )]
	public void SetDifficulty( int diff )
	{
		for ( int i = 0; i <= MaxDifficulty; i++ )
		{
			LobbyMaps[i]?.Enabled = diff == i;
			LobbyMapObjects[i]?.Enabled = diff == i;
		}

		Fade( fadeIn: true );

		if ( !Networking.IsHost )
			return;

		if( Difficulty == diff )
			return;

		Difficulty = diff;

		GameSettingsSystem.Save();

		//Log.Info( $"Set difficulty to {diff} ({GetNameForDifficulty( diff )})" );
	}

	//[Rpc.Broadcast] public void SetDifficultyEffect( int diff )
	//{
	//	if ( _effectsObj != null )
	//	{
	//		_effectsObj.Enabled = false;
	//		_effectsObj = null;
	//	}

	//	if ( DifficultyEffects != null && DifficultyEffects.ContainsKey( diff ) && DifficultyEffects[diff] != null )
	//	{
	//		_effectsObj = DifficultyEffects[diff];
	//		_effectsObj.Enabled = true;
	//	}
	//}

	public float GetEnemyHealthModifier()
	{
		switch ( Players.Count )
		{
			case 1: default: return 1f;
			case 2: return 1.25f;
			case 3: return 1.5f;
		}
	}

	/// <summary>Returns a multiplier for non-self damage taken by the player. 1x until 20 min, ramps to 3x at 50 min, continues linearly forever.</summary>
	public float GetLateDamageMultiplier()
	{
		var minutes = ElapsedTime / 60f;
		if ( minutes <= 20f )
			return 1f;
		return 1f + (minutes - 20f) * (2f / 30f);
	}
}