PerkManager and PerkAttribute definitions for the game's perk system. PerkAttribute stores metadata for perks. PerkManager contains logic to filter, weight and pick random perks for players, determine allowed perks/rewards, create Perk instances, map type identities, and provide rarity colors/names and utility functions.
using System;
using System.Diagnostics.SymbolStore;
using Sandbox;
public class PerkAttribute : Attribute
{
public Rarity Rarity;
public bool IncludedAtStart;
public MultiplayerMode MultiplayerMode;
public bool AlwaysOfferDebug;
public bool Disabled;
public bool Curse;
public bool Locked;
public int MinDifficulty;
public int MaxDifficulty;
public bool OnlyUnpausedChoosing;
public ControlMode ControlMode;
public PerkCategory[] IncludedCategories { get; set; }
public int MinUnlocksReq;
public PerkAttribute( Rarity rarity, bool includedAtStart = true, MultiplayerMode multiplayerMode = MultiplayerMode.Always, bool alwaysOfferDebug = false, bool disabled = false, bool curse = false,
int minDifficulty = Manager.MinDifficulty, int maxDifficulty = Manager.MaxDifficulty, bool onlyUnpausedChoosing = false, ControlMode controlMode = ControlMode.All, bool locked = false, int minUnlocksReq = 0 )
{
Rarity = rarity;
IncludedAtStart = includedAtStart;
MultiplayerMode = multiplayerMode;
AlwaysOfferDebug = alwaysOfferDebug;
Disabled = disabled;
Curse = curse;
Locked = locked;
MinDifficulty = minDifficulty;
MaxDifficulty = maxDifficulty;
OnlyUnpausedChoosing = onlyUnpausedChoosing;
ControlMode = controlMode;
MinUnlocksReq = minUnlocksReq;
}
}
public enum CurseSelection { NoCurses, AllowCurses, OnlyCurses }
public enum ControlMode { All, MouseAndKeyboard, Controller }
public class PerkManager
{
public static List<TypeDescription> GetRandomPerks( Player player, int numPerks, Rarity rarity, CurseSelection curseSelection, bool isChoice, List<TypeDescription> notAllowed = null, bool isReward = false, bool includeLocked = false )
{
var manager = Manager.Instance;
List<(TypeDescription Type, float Weight)> valid = new();
foreach ( var typeIdent in player.ValidPerks )
{
TypeDescription type = IdentityToType( typeIdent );
var attrib = type.GetAttribute<PerkAttribute>();
if ( attrib == null )
continue;
if ( attrib.Disabled )
continue;
var alwaysOfferDebug = Game.IsEditor && attrib.AlwaysOfferDebug;
//if ( attrib.Locked && !includeLocked && !alwaysOfferDebug && !Manager.HideProgressionSystem && !ProgressManager.IsLockedPerkUnlocked( type ) )
// continue;
if ( attrib.MultiplayerMode == MultiplayerMode.OnlyMultiplayer && !manager.IsMultiplayer && !(alwaysOfferDebug && isChoice) )
continue;
if ( attrib.MultiplayerMode == MultiplayerMode.OnlySingleplayer && manager.IsMultiplayer && !(alwaysOfferDebug && isChoice) )
continue;
if ( manager.Difficulty < attrib.MinDifficulty || manager.Difficulty > attrib.MaxDifficulty )
continue;
if ( attrib.OnlyUnpausedChoosing && !manager.IsUnpausedChoosing && !alwaysOfferDebug )
continue;
if ( attrib.ControlMode == ControlMode.Controller && !Input.UsingController )// && !alwaysOfferDebug )
continue;
if ( attrib.ControlMode == ControlMode.MouseAndKeyboard && Input.UsingController )// && !alwaysOfferDebug )
continue;
if ( rarity != Rarity.None && rarity != attrib.Rarity )
continue;
if ( ((curseSelection == CurseSelection.NoCurses && attrib.Curse) || (curseSelection == CurseSelection.OnlyCurses && !attrib.Curse)) && !alwaysOfferDebug )
continue;
int existingLevel = player.GetPerkLevel( type );
if ( existingLevel >= GetMaxLevelForRarity( attrib.Rarity ) )
continue;
if ( notAllowed != null && notAllowed.Contains( type ) )
continue;
if ( !IsPerkAllowed( type, player, isChoice ) )// && !alwaysOfferDebug )
continue;
if ( isReward && !IsPerkAllowedAsReward( type, player ) )
continue;
float weight = GetWeightForRarity( attrib.Rarity, player, attrib.Curse );
if ( existingLevel > 0 && player.Stats[PlayerStat.ExistingPerkChance] > 0f )
weight *= (1f + existingLevel * player.Stats[PlayerStat.ExistingPerkChance]);
if ( alwaysOfferDebug )
weight = 99999999999f;
valid.Add( (type, weight) );
}
if ( isChoice )
PerkSeeOtherChoices.AdjustForSeenPerks( player, valid, numPerks );
List<TypeDescription> output = new List<TypeDescription>();
int validCount = valid.Count;
while ( output.Count < Math.Min( numPerks, validCount ) )
{
float totalWeight = valid.Sum( x => x.Weight );
var rand = Game.Random.Float( 0f, totalWeight );
for ( int i = valid.Count - 1; i >= 0; i-- )
{
var (type, weight) = valid[i];
rand -= weight;
if ( rand < 0f )
{
output.Add( type );
valid.Remove( (type, weight) );
break;
}
}
}
return output;
}
//public static List<TypeDescription> GetRandomPerks( Player player, int numPerks, Rarity rarity, CurseSelection curseSelection, bool isChoice, List<TypeDescription> notAllowed = null )
//{
// List<TypeDescription> output = new();
// while ( output.Count < numPerks )
// {
// var type = GetRandomPerk( player, rarity, curseSelection, isChoice, output );
// // todo: if null, find another valid perk of a different rarity
// if( type != null )
// {
// output.Add( GetRandomPerk( player, rarity, curseSelection, isChoice, output ) );
// }
// }
// return output;
//}
//public static TypeDescription GetRandomPerk( Player player, Rarity rarity, CurseSelection curseSelection, bool isChoice, List<TypeDescription> notAllowed = null )
//{
// if ( rarity == Rarity.None )
// rarity = GetRarityBasedOnChance( player, isCurse: curseSelection == CurseSelection.OnlyCurses );
// List<(TypeDescription Type, float Weight)> valid = new();
// foreach ( var typeIdent in player.ValidPerks )
// {
// TypeDescription type = IdentityToType( typeIdent );
// var attrib = type.GetAttribute<PerkAttribute>();
// if ( attrib == null )
// continue;
// if ( attrib.Disabled )
// continue;
// if ( attrib.OnlyForMultiplayer && !Manager.Instance.IsMultiplayer && !(attrib.AlwaysOfferDebug && isChoice) )
// continue;
// if ( Manager.Instance.Difficulty < attrib.MinDifficulty || Manager.Instance.Difficulty > attrib.MaxDifficulty )
// continue;
// if ( rarity != attrib.Rarity )
// continue;
// if ( ((curseSelection == CurseSelection.NoCurses && attrib.Curse) || (curseSelection == CurseSelection.OnlyCurses && !attrib.Curse)) && !attrib.AlwaysOfferDebug )
// continue;
// int existingLevel = player.GetPerkLevel( type );
// if ( existingLevel >= GetMaxLevelForRarity( attrib.Rarity ) )
// continue;
// if ( notAllowed != null && notAllowed.Contains( type ) )
// continue;
// if ( !IsPerkAllowed( type, player, isChoice ) )
// continue;
// float weight = 1f;
// if ( existingLevel > 0 && player.Stats[PlayerStat.ExistingPerkChance] > 0f )
// weight *= (1f + existingLevel * player.Stats[PlayerStat.ExistingPerkChance]);
// if ( attrib.AlwaysOfferDebug )
// weight = 99999999999f;
// valid.Add( (type, weight) );
// }
// float totalWeight = valid.Sum( x => x.Weight );
// var rand = Game.Random.Float( 0f, totalWeight );
// for ( int i = valid.Count - 1; i >= 0; i-- )
// {
// var (type, weight) = valid[i];
// rand -= weight;
// if ( rand < 0f )
// {
// return type;
// }
// }
// return null;
//}
public static bool IsPerkAllowed( TypeDescription type, Player player, bool isChoice )
{
if ( type == TypeLibrary.GetType( typeof( CursePiercing ) ) && player.Stats[PlayerStat.BulletNumPiercing] == 0f && player.Stats[PlayerStat.BulletExtraPierceChance] == 0f ) return false;
if ( type == TypeLibrary.GetType( typeof( CurseCritChance ) ) && !(player.Stats[PlayerStat.CritChance] > 0f ) ) return false;
if ( type == TypeLibrary.GetType( typeof( CurseCritMultiplier ) ) && !(player.Stats[PlayerStat.CritMultiplier] > 0f) ) return false;
if ( type == TypeLibrary.GetType( typeof( CursePiercing ) ) && !( player.Stats[PlayerStat.BulletNumPiercing] > 0f || player.Stats[PlayerStat.BulletExtraPierceChance] > 0f ) ) return false;
if ( type == TypeLibrary.GetType( typeof( CurseMaxHp ) ) && !(player.Stats[PlayerStat.MaxHp] > 1f) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkBanishOtherChoices ) ) && !isChoice ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkDashChangeDirection ) ) && (player.Stats[PlayerStat.Blink] > 0f || player.Stats[PlayerStat.JumpNotDash] > 0f) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkDashLength ) ) && player.Stats[PlayerStat.BlinkToCursor] > 0f ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkDashSlash ) ) && (player.Stats[PlayerStat.Blink] > 0f || player.Stats[PlayerStat.JumpNotDash] > 0f) && player.GetPerkLevel( TypeLibrary.GetType( typeof( PerkDashSlash ) ) ) == 0 ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkPacifistDmgUnderKills ) ) && (int)player.Stats[PlayerStat.NumEnemiesKilled] >= 100 ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkMaxAmmoDownside ) ) && player.Perks.Count <= 1 && player.HasPerk( TypeLibrary.GetType( typeof( PerkMaxAmmoDownside ) ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkCleanse ) ) && player.Perks.Count <= (player.HasPerk( TypeLibrary.GetType( typeof( PerkCleanse ) ) ) ? 2 : 1 ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkNoInvulnDash ) ) && player.HasPerk( TypeLibrary.GetType( typeof( CurseNoDashInvuln ) ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( CurseNoDashInvuln ) ) && player.HasPerk( TypeLibrary.GetType( typeof( PerkNoInvulnDash ) ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( CurseNumDashes ) ) && player.Stats[PlayerStat.NumDashes] <= 0f ) return false;
if ( type == TypeLibrary.GetType( typeof( CurseMaxAmmo ) ) && player.Stats[PlayerStat.MaxAmmoCount] <= 0f ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkLessAmmo ) ) && player.Stats[PlayerStat.MaxAmmoCount] <= 0f ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkLessAmmoArcBullets ) ) && player.Stats[PlayerStat.MaxAmmoCount] <= 0f ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkDashLessAmmo ) ) && player.Stats[PlayerStat.MaxAmmoCount] <= 0f ) return false;
if ( player.Stats[PlayerStat.NumPerkChoices] <= 0f && (type == TypeLibrary.GetType( typeof( PerkNumChoices ) ) || type == TypeLibrary.GetType( typeof( CurseNumChoices ) ) || type == TypeLibrary.GetType( typeof( PerkSkipChoices ) ) || type == TypeLibrary.GetType( typeof( PerkRandomChoice ) )) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkCommunism ) ) && Manager.Instance.IsCommunismActive ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkHandoutsRemove ) ) && player.Perks.Count <= 1 && player.HasPerk( TypeLibrary.GetType( typeof( PerkHandoutsRemove ) ) ) ) return false;
if ( player.PerkCategories[PerkCategory.Dodge].Contains( type.TargetType ) && !((player.Stats[PlayerStat.DodgeChance] > 0f) || player.Stats[PlayerStat.DodgeGuaranteedNum] > 0f) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkRandomFavorite ) ) && player.FavoritePerks.Count <= 0 ) return false;
if ( type == TypeLibrary.GetType( typeof( CurseMoveRelativeToAimDir ) ) && player.Stats[PlayerStat.FpsMode] > 0f ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkFpsMode ) ) && player.Stats[PlayerStat.MovementRelativeToAimDir] > 0f ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkPerspective ) ) && player.Stats[PlayerStat.PerspectiveCamera] > 0f ) return false;
// todo: if have reduced crit chance curse, maybe don't allow reduce crit multiplier curse and vice versa? or at least make it less likely?
if ( type == TypeLibrary.GetType( typeof( PerkBossSpawnEarly ) ) && Manager.Instance.HasSpawnedBoss ) return false;
if ( type == TypeLibrary.GetType( typeof( CurseProjectileBounceFence ) ) && Manager.Instance.EnemyProjectileBounceFenceLevel > 0 ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkMaxHealth ) ) && player.Stats[PlayerStat.MaxHpCap] > 0f ) return false;
if ( type == TypeLibrary.GetType( typeof( CursePreventPausing ) ) && player.Stats[PlayerStat.UnpausedChoosing] > 0f ) return false;
//if ( player.Stats[PlayerStat.PunchBullets] > 0f && DoesPerkNotWorkWithPunch( type ) ) return false;
return true;
}
public static bool IsPerkAllowedAsReward( TypeDescription type, Player player )
{
if ( type == TypeLibrary.GetType( typeof( PerkHandouts ) ) ) return false;
//if ( type == TypeLibrary.GetType( typeof( PerkHandoutsCommon ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkHandoutsRemove ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkMaxAmmoDownside ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkCleanse ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkEvilChest ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkIncreaseEarliestPerk ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkTimerDigitDamage ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkNumChoicesCantMove ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkRerolls ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkNumChoices ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkLessAmmoArcBullets ) ) && player.Stats[PlayerStat.MaxAmmoCount] <= 0f ) return false;
// for the remaining perks, if we've already chosen it, then we can allow it as reward
if ( player.GetPerkLevel( type ) > 0 )
return true;
if ( type == TypeLibrary.GetType( typeof( PerkPunch ) ) && !(player.Stats[PlayerStat.PunchBullets] > 0f) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkBulletKickback ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkBulletNegativeKickback ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkArtillery ) ) && !(player.Stats[PlayerStat.ExplosionDamageMultiplier] > 0f || player.Stats[PlayerStat.ExplosionSizeMultiplier] > 0f) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkLandmine ) ) && !(player.Stats[PlayerStat.ExplosionDamageMultiplier] > 0f || player.Stats[PlayerStat.ExplosionSizeMultiplier] > 0f) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkAlternateAimDir ) ) && !(player.Stats[PlayerStat.BulletHomingRadius] > 0f) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkAutoReroll ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkBerserk ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkBossSpawnEarly ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkRandomExisting ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkBoomerangHurtSelf ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkBulletHurtSelfBounce ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkBulletHomingHurt ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkBulletHomingGround ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkBulletHomingCursed ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkChooseTimeLimit ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkClickRemove ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkCommunism ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkDashCapMaxHp ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkDashRandomlyWhenHit ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkDashReload ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkDashToBlink ) ) && player.Stats[PlayerStat.DashSlashBulletDamagePercent] > 0f ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkDashToJump ) ) && player.Stats[PlayerStat.DashSlashBulletDamagePercent] > 0f ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkDashSlash ) ) && (player.Stats[PlayerStat.JumpNotDash] > 0f || player.Stats[PlayerStat.Blink] > 0f) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkDoubleDashes ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkDoubleMaxHp ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkDrainHealOtherPlayers ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkDrainUntil1Hp ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkFpsMode ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkFriendlyDmgCoin ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkFullHealthAoe ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkHealOnKill ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkHealthPackMaxHp ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkHealthPackXp ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkHurtSelfOnKill ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkLessAmmo ) ) && player.GetPerkLevel( TypeLibrary.GetType( typeof( PerkLessAmmoLongerLifetime ) ) ) == 0 ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkLessAmmoLongerLifetime ) ) && player.GetPerkLevel( TypeLibrary.GetType( typeof( PerkLessAmmo ) ) ) == 0 ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkLessChoiceMoreDamage ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkLoseHpBeforeArmorAt1Hp ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkLoseWhenGainXp ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkMoreRerolls ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkMoreRerollsLoseHp ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkMoveDrainHealth ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkMoveSlowerWhileShooting ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkMoveSpeedAlignment ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkMoveToSpawnFire ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkNoCommons ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkNoInvulnDash ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkNoReqs ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkNumChoicesTimed ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkPacifistDmgUnderKills ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkRadiation ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkRandomUnique ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkMiniPlayer ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkRestartChoices ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkSelfDmgDash ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkShieldMinDmg ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkShootOnlyWhenClick ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkShotgun ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkShuffleSideToSide ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkUpsideDown ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkXpGainMultiplier ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkZoomContinuously ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkZoomWobble ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkPierce ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkNumProjectiles ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkAttackSpeedLessDamage ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkRecoil) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkZoomIn ) ) ) return false;
if ( type == TypeLibrary.GetType( typeof( PerkBulletDmgFacing ) ) ) return false;
return true;
}
public static Perk CreatePerk( TypeDescription type )
{
var Perk = type.Create<Perk>();
var attrib = type.GetAttribute<PerkAttribute>();
if ( attrib != null )
Perk.MaxLevel = GetMaxLevelForRarity( attrib.Rarity );
return Perk;
}
public static int TypeToIdentity( TypeDescription type )
{
return type.Identity;
}
public static TypeDescription IdentityToType( int typeIdentity )
{
return TypeLibrary.GetTypeByIdent( typeIdentity );
}
public static Color GetCardRarityColor( Rarity rarity, bool curse = false, float alpha = 1f )
{
Color color;
switch ( rarity )
{
case Rarity.Common: default: color = new Color( 0.75f, 0.75f, 0.75f ); break;
case Rarity.Uncommon: color = new Color( 0.6f, 0.6f, 0.9f ); break;
case Rarity.Rare: color = new Color( 1f, 0.3f, 0.6f ); break;
case Rarity.Epic: color = new Color( 1f, 0.5f, 0f ); break;
case Rarity.Mythic: color = new Color( 0.9f, 0f, 1f ); break;
case Rarity.Legendary: color = new Color( 0f, 0.8f, 0f ); break;
case Rarity.Unique: color = new Color( 1f, 1f, 0.3f ); break;
}
if ( curse )
color = Color.Lerp( new Color( 0.9f, 0f, 1f ), Color.Black, 0.5f );
return color.WithAlpha( alpha );
}
public static Color GetFontRarityColor( Rarity rarity, bool curse = false, float alpha = 1f )
{
Color color;
switch ( rarity )
{
case Rarity.Common: default: color = new Color( 0.75f, 0.75f, 0.75f ); break;
case Rarity.Uncommon: color = new Color( 0.6f, 0.6f, 0.9f ); break;
case Rarity.Rare: color = new Color( 1f, 0.3f, 0.6f ); break;
case Rarity.Epic: color = new Color( 1f, 0.5f, 0f ); break;
case Rarity.Mythic: color = new Color( 0.9f, 0f, 1f ); break;
case Rarity.Legendary: color = new Color( 0f, 0.8f, 0f ); break;
case Rarity.Unique: color = new Color( 1f, 1f, 0.3f ); break;
}
if ( curse )
color = Color.Lerp( new Color( 0.9f, 0f, 1f ), Color.Black, 0.25f );
return color.WithAlpha( alpha );
}
public static string GetCardRarityName( Rarity rarity, bool curse = false )
{
string name;
switch ( rarity )
{
case Rarity.Common: default: name = "Common"; break;
case Rarity.Uncommon: name = "Uncommon"; break;
case Rarity.Rare: name = "Rare"; break;
case Rarity.Epic: name = "Epic"; break;
case Rarity.Mythic: name = "Mythic"; break;
case Rarity.Legendary: name = "Legendary"; break;
case Rarity.Unique: name = "Unique"; break;
}
if ( curse )
name = "Curse";
return name;
}
public static float GetWeightForRarity( Rarity rarity, Player player, bool isCurse )
{
float weight;
if ( !isCurse )
{
switch ( rarity )
{
////case Rarity.Common: default: weight = Utils.Map( Manager.Instance.ElapsedTime, 0f, 30f, 600f, 200f ) * (1f + player.Stats[PlayerStat.RarityIncreaseCommon]); break;
//case Rarity.Common: default: weight = Utils.Map( Manager.Instance.ElapsedTime, 0f, 30f, 450f, 320f ) * (1f + player.Stats[PlayerStat.RarityIncreaseCommon]); break;
//case Rarity.Uncommon: weight = 160f * (1f + player.Stats[PlayerStat.RarityIncreaseUncommon]); break;
//case Rarity.Rare: weight = 60f * (1f + player.Stats[PlayerStat.RarityIncreaseRare]); break;
//case Rarity.Epic: weight = 35f * (1f + player.Stats[PlayerStat.RarityIncreaseEpic]); break;
//case Rarity.Mythic: weight = 14.5f * (1f + player.Stats[PlayerStat.RarityIncreaseMythic]); break;
//case Rarity.Legendary: weight = 8.0f * (1f + player.Stats[PlayerStat.RarityIncreaseLegendary]); break;
//case Rarity.Unique: weight = 2.05f * (1f + player.Stats[PlayerStat.RarityIncreaseUnique]); break;
// //case Rarity.Curse: weight = 250f * (1f + player.Stats[PlayerStat.RarityIncreaseCurse]); break;
case Rarity.Common: default: weight = 425f * (1f + player.Stats[PlayerStat.RarityIncreaseCommon]); break;
case Rarity.Uncommon: weight = 183f * (1f + player.Stats[PlayerStat.RarityIncreaseUncommon]); break;
case Rarity.Rare: weight = 75f * (1f + player.Stats[PlayerStat.RarityIncreaseRare]); break;
case Rarity.Epic: weight = 46f * (1f + player.Stats[PlayerStat.RarityIncreaseEpic]); break;
case Rarity.Mythic: weight = 25f * (1f + player.Stats[PlayerStat.RarityIncreaseMythic]); break;
case Rarity.Legendary: weight = 15f * (1f + player.Stats[PlayerStat.RarityIncreaseLegendary]); break;
case Rarity.Unique: weight = 3f * (1f + player.Stats[PlayerStat.RarityIncreaseUnique]); break;
}
}
else
{
weight = 200f;
}
return weight;
}
public static int GetMaxLevelForRarity( Rarity rarity )
{
switch ( rarity )
{
case Rarity.Common: default: return 7;
case Rarity.Uncommon: return 6;
case Rarity.Rare: return 5;
case Rarity.Epic: return 4;
case Rarity.Mythic: return 3;
case Rarity.Legendary: return 2;
case Rarity.Unique: return 1;
}
}
public static int GetAvailableCurseCount( Player player )
{
var manager = Manager.Instance;
int count = 0;
// List<string> availableCurseNames = new();
foreach ( var typeIdent in player.ValidPerks )
{
TypeDescription type = IdentityToType( typeIdent );
var attrib = type.GetAttribute<PerkAttribute>();
if ( attrib == null )
continue;
if ( attrib.Disabled )
continue;
if ( !attrib.Curse )
continue;
if ( attrib.MultiplayerMode == MultiplayerMode.OnlyMultiplayer && !manager.IsMultiplayer )
continue;
if ( attrib.MultiplayerMode == MultiplayerMode.OnlySingleplayer && manager.IsMultiplayer )
continue;
if ( manager.Difficulty < attrib.MinDifficulty || manager.Difficulty > attrib.MaxDifficulty )
continue;
if ( !IsPerkAllowed( type, player, isChoice: true ) )
continue;
int existingLevel = player.GetPerkLevel( type );
if ( existingLevel < GetMaxLevelForRarity( attrib.Rarity ) )
{
count++;
// availableCurseNames.Add( type.Name );
}
}
// Log.Info( $"Available curses ({count}): {string.Join( ", ", availableCurseNames )}" );
return count;
}
public static Rarity GetRarityBasedOnChance( Player player, bool isCurse )
{
Dictionary<Rarity, float> weightedValues = new();
for ( int i = 1; i <= 7; i++ )
{
Rarity rarity = (Rarity)i;
weightedValues.Add( rarity, GetWeightForRarity( rarity, player, isCurse ) );
}
var total = 0f;
foreach ( var pair in weightedValues )
total += pair.Value;
var rand = Game.Random.Float( 0f, total );
var currentTotal = 0f;
foreach ( var pair in weightedValues )
{
var rarity = pair.Key;
currentTotal += pair.Value;
if ( rand < currentTotal )
{
return rarity;
}
}
return Rarity.None;
}
/// <summary>
/// Some perks don't work with punches, but the punching player can still spawn normal bullets other ways...
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static bool DoesPerkNotWorkWithPunch( TypeDescription type )
{
if (
type == TypeLibrary.GetType( typeof( PerkBulletHomingRadius ) ) ||
type == TypeLibrary.GetType( typeof( PerkBulletHomingHurt ) ) ||
type == TypeLibrary.GetType( typeof( PerkBulletHomingGround ) ) ||
type == TypeLibrary.GetType( typeof( PerkOnly1HpBulletHoming ) ) ||
type == TypeLibrary.GetType( typeof( PerkBulletHomingCursed ) ) ||
type == TypeLibrary.GetType( typeof( PerkBulletArcStill ) ) ||
type == TypeLibrary.GetType( typeof( PerkBulletAimAtCursor ) ) ||
type == TypeLibrary.GetType( typeof( PerkBulletOverflow ) ) ||
type == TypeLibrary.GetType( typeof( PerkBulletReturn ) ) ||
type == TypeLibrary.GetType( typeof( PerkBounceTarget ) ) ||
type == TypeLibrary.GetType( typeof( PerkPierce ) ) ||
type == TypeLibrary.GetType( typeof( PerkBounce ) ) ||
type == TypeLibrary.GetType( typeof( PerkPierceChance ) ) ||
type == TypeLibrary.GetType( typeof( PerkBounceChance ) ) ||
type == TypeLibrary.GetType( typeof( PerkBulletHealTeammate ) ) || // todo: enable punch to heal teammates (should be green colored punch)
type == TypeLibrary.GetType( typeof( PerkBulletMoveRandomly ) ) ||
type == TypeLibrary.GetType( typeof( PerkBounceDamage ) ) ||
type == TypeLibrary.GetType( typeof( PerkBulletSplash ) ) ||
type == TypeLibrary.GetType( typeof( PerkBounceResetLifetime) ) ||
type == TypeLibrary.GetType( typeof( PerkBulletDistanceDamage ) ) ||
type == TypeLibrary.GetType( typeof( PerkBulletGrow) )
// ...
)
return true;
return false;
}
}