s&box Package Code Search
Search C# source code, UI razor templates, shaders, and configs across s&box packages.
link Raw Facepunch API Link: https://public.facepunch.com/sbox/code/search/1/?ident=facepunch.ss1&take=20
Showing code results for query:
*
(222 total matches found)
Game
game
InfoPanel {
position: absolute;
width: 280px;
left: 20px;
bottom: 20px;
top: 20px;
align-self: flex-end;
pointer-events: none;
justify-content: flex-start;
flex-direction: column-reverse;
gap: 10px;
//background-color: red;
.info_dash_container {
width: 100%;
//flex-direction: column-reverse;
flex-direction: row;
align-items: center;
gap: 8px;
}
.dash {
position: relative;
flex-shrink: 1;
width: 100%;
height: 28px;
background-color: #0005;
}
.info_bar {
position: relative;
width: 100%;
overflow: hidden;
height: 28px;
background-color: #00000099;
//border-left: 2px solid #fff;
//border-right: 2px solid #fff;
flex-shrink: 1;
//border: 2px solid black;
.info_bar_overlay {
position: absolute;
left: 0px;
width: 100%;
height: 100%;
}
}
.data_container {
width: 100%;
height: 100%;
flex-direction: row-reverse;
}
.info_bar_label {
position: absolute;
color: #fff;
font-size: 26px;
align-items: center;
font-weight: bolder;
font-family: "Mini Pixel-7";
//width: 100%;
left: 0px;
justify-content: flex-start;
padding-left: 5px;
padding-top: 1px;
text-stroke: 5px black;
opacity: 0.7;
}
.data {
position: relative;
color: #ffffffaa;
font-size: 26px;
align-items: center;
font-family: "Mini Pixel-7";
//left: 0px;
padding-right: 5px;
padding-top: 1px;
text-stroke: 5px black;
opacity: 0.9;
//letter-spacing: 1px;
}
//.data_center {
// position: absolute;
// color: #ffffff88;
// font-size: 26px;
// align-items: center;
// justify-content: center;
// font-family: "Mini Pixel-7";
//left: 0px;
// width: 100%;
// padding-left: 5px;
// padding-top: 1px;
// text-stroke: 3px #00000099;
// opacity: 0.9;
//}
}
Game
game
using Sandbox;
public class Camera2D : Component
{
[Property] public CameraComponent Camera { get; set; }
public Vector2 TargetPos { get; set; }
public Vector2 CameraPos { get; set; }
protected override void OnUpdate()
{
base.OnUpdate();
//Vector2 newPos = Vector2.Lerp( (Vector2)WorldPosition, TargetPos, Time.Delta * 3f );
CameraPos = Utils.DynamicEaseTo( CameraPos, TargetPos, 0.2f, Time.Delta );
var newPos = CameraPos + Utils.GetRandomVector() * Manager.Instance.Player.CamShakeAmount;
float bounds_zoom = 1f + Manager.Instance.Player.Stats[PlayerStat.ZoomAmount] * 0.44f;
var XDIST = 10.75f / bounds_zoom;
var Y_MIN = -8.8f / bounds_zoom;
var Y_MAX = 8.9f / bounds_zoom;
newPos = new Vector2( MathX.Clamp( newPos.x, -XDIST, XDIST ), MathX.Clamp( newPos.y, Y_MIN, Y_MAX ) );
WorldPosition = ((Vector3)newPos).WithZ( WorldPosition.z );
}
public void SetPos( Vector2 pos )
{
CameraPos = pos;
TargetPos = pos;
WorldPosition = ((Vector3)pos).WithZ( WorldPosition.z );
}
}
Game
game
using Sandbox;
public class FloatingDamageNumber : Component
{
private RealTimeSince _timeSince;
protected override void OnAwake()
{
base.OnAwake();
_timeSince = 0f;
}
protected override void OnUpdate()
{
base.OnUpdate();
if ( _timeSince > 1f )
GameObject.Destroy();
}
}
Game
game
using Sandbox.UI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using static Manager;
public class LavaPuddle : Component
{
[Property] public Sprite2D Sprite { get; set; }
public TimeSince TimeSinceSpawn { get; set; }
public float Lifetime { get; set; }
public float Radius { get; set; }
public float FullRadius { get; set; }
private float _timeOffset;
private TimeSince _timeSinceDamagePlayer;
private const float DAMAGE_INTERVAL = 0.25f;
private float _expandTime;
public float DamageToPlayer { get; set; }
public Color ColorA { get; set; }
public Color ColorB { get; set; }
public Vector2 Position2D
{
get { return (Vector2)WorldPosition; }
set { WorldPosition = new Vector3( value.x, value.y, WorldPosition.z ); }
}
protected override void OnAwake()
{
base.OnAwake();
if ( Game.Random.Float( 0f, 1f ) < 0.5f )
Sprite.SpriteFlags = SpriteFlags.HorizontalFlip;
Radius = 0f;
LocalScale = new Vector3( Radius * 1f, Radius * 1.06f, 1f ) * 2f * Globals.SPRITE_SCALE;
//LocalScale = new Vector3( Game.Random.Float( 0.4f, 0.6f ), Game.Random.Float( 0.15f, 0.25f ), 1f ) * Globals.SPRITE_SCALE;
//LocalScale = new Vector3( Game.Random.Float( 0.33f, 0.48f ), Game.Random.Float( 0.4f, 0.63f ), 1f ) * 5f * Globals.SPRITE_SCALE;
_timeOffset = Game.Random.Float( 0f, 99f );
TimeSinceSpawn = 0f;
_expandTime = Game.Random.Float( 0.4f, 0.5f );
DamageToPlayer = 2f + Math.Min(Manager.Instance.Difficulty, 5);
}
protected override void OnUpdate()
{
base.OnUpdate();
float FADE_IN_TIME = 0.3f;
var color = Color.Lerp( ColorA, ColorB, 0.5f + Utils.FastSin( TimeSinceSpawn * 16f ) * 0.5f );
var opacity = 0.3f
* Utils.Map( TimeSinceSpawn, 0f, FADE_IN_TIME, 0f, 1f, EasingType.SineOut )
* Utils.Map( TimeSinceSpawn, Lifetime - 1f, Lifetime, 1f, 0f, EasingType.SineIn )
* ( 0.85f + Utils.FastSin( _timeOffset + Time.Now * 16f ) * 0.15f );
Sprite.Tint = color.WithAlpha( opacity );
//Gizmo.Draw.Color = Color.White.WithAlpha( 0.05f );
//Gizmo.Draw.LineSphere( WorldPosition, Radius, 20 );
if (TimeSinceSpawn < _expandTime )
{
Radius = Utils.Map( TimeSinceSpawn, 0f, _expandTime, FullRadius * 0.05f, FullRadius, EasingType.QuadOut );
LocalScale = new Vector3( Radius * 0.98f, Radius * 1.06f, 1f ) * 2f * Globals.SPRITE_SCALE;
}
if ( TimeSinceSpawn > Lifetime )
{
Manager.Instance.RemoveLavaPuddle( this );
GameObject.Destroy();
return;
}
if( TimeSinceSpawn > FADE_IN_TIME && TimeSinceSpawn < Lifetime - 0.5f )
{
var player = Manager.Instance.Player;
float distSqr = (player.Position2D - Position2D).LengthSquared;
if( distSqr < MathF.Pow(Radius, 1.9f) && _timeSinceDamagePlayer > DAMAGE_INTERVAL && player.TimeSinceHurtLava > DAMAGE_INTERVAL && !player.IsDead )
{
float currDmg = Utils.Map( TimeSinceSpawn, FADE_IN_TIME, FADE_IN_TIME * 2f, 1f, DamageToPlayer );
float dmg = player.CheckDamageAmount( currDmg, DamageType.LavaPuddle );
if ( !player.IsInvulnerable && !player.IsTimePausedForChoosing )
{
player.Damage( dmg );
_timeSinceDamagePlayer = 0f;
player.TimeSinceHurtLava = 0f;
Manager.Instance.PlaySfxNearby( "lava_puddle_03", player.Position2D, pitch: Game.Random.Float( 0.9f, 1.1f ), volume: 1f, maxDist: 4f );
}
}
}
}
}
Game
game
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
[Status( 5, 0, 1f, 0, false )]
public class BulletLifetimeDamageStatus : Status
{
public BulletLifetimeDamageStatus()
{
Title = "Growing Bullets";
IconPath = "textures/icons/bullet_lifetime_damage.png";
}
public override void Init( Player player )
{
base.Init( player );
}
public override void Refresh()
{
Description = GetDescription( Level );
Player.Modify( this, PlayerStat.BulletDamageGrow, GetAddForLevel( Level ), ModifierType.Add );
}
public override string GetDescription( int newLevel )
{
return string.Format( "Bullets grow their damage by {0} per second", GetPrintAmountForLevel( Level ) );
}
public override string GetUpgradeDescription( int newLevel )
{
return newLevel > 1 ? string.Format( "Bullets grow their damage by {0}→{1} per second", GetPrintAmountForLevel( newLevel - 1 ), GetPrintAmountForLevel( newLevel ) ) : GetDescription( newLevel );
}
public float GetAddForLevel( int level )
{
return 0.2f + 1.5f * level + (level == 7 ? 1.5f : 0f);
}
public string GetPrintAmountForLevel( int level )
{
return string.Format( "{0:0.0}", GetAddForLevel( level ) );
}
}
Game
game
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
[Status( 7, 0, 1f, 0, false )]
public class CritChanceStatus : Status
{
public CritChanceStatus()
{
Title = "Sharp Bullets";
IconPath = "textures/icons/crit_chance.png";
}
public override void Init( Player player )
{
base.Init( player );
}
public override void Refresh()
{
Description = GetDescription( Level );
Player.Modify( this, PlayerStat.CritChance, GetAddForLevel( Level ), ModifierType.Add );
}
public override string GetDescription( int newLevel )
{
return string.Format( "Increase critical chance from 5%→{0}%", GetPercentForLevel( Level ) );
}
public override string GetUpgradeDescription( int newLevel )
{
return newLevel > 1 ? string.Format( "Increase critical chance from {0}%→{1}%", GetPercentForLevel( newLevel - 1 ), GetPercentForLevel( newLevel ) ) : GetDescription( newLevel );
}
public float GetAddForLevel( int level )
{
return 0.05f + 0.08f * level + (level == 7 ? 0.04f : 0f);
}
public float GetPercentForLevel( int level )
{
return 5 + 8 * level + (level == 7 ? 4 : 0);
}
}
Game
game
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
[Status(4, 0, 0.5f, 0, true)]
public class CurseAimDir : Status
{
public CurseAimDir()
{
Title = "Random Aim";
IconPath = "textures/icons/curse_shoot_random_dir.png";
}
public override void Init(Player player)
{
base.Init(player);
}
public override void Refresh()
{
Description = GetDescription(Level);
Player.Modify(this, PlayerStat.ShootRandomDirChance, GetChanceForLevel(Level), ModifierType.Add);
}
public override string GetDescription( int newLevel )
{
return string.Format( "{0}% chance to shoot in a random direction", GetPercentForLevel( Level ) );
}
public override string GetUpgradeDescription( int newLevel )
{
return newLevel > 1 ? string.Format( "{0}%→{1}% chance to shoot in a random direction", GetPercentForLevel( newLevel - 1 ), GetPercentForLevel( newLevel ) ) : GetDescription( newLevel );
}
public float GetChanceForLevel( int level )
{
return 0.25f * level;
}
public float GetPercentForLevel( int level )
{
return 25 * level;
}
}
Game
game
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
[Status( 7, 0, 1f, 0, true )]
public class CurseAttackSpeedStatus : Status
{
public CurseAttackSpeedStatus()
{
Title = "Lazy Shooting";
IconPath = "textures/icons/curse_attack_speed.png";
}
public override void Init( Player player )
{
base.Init( player );
}
public override void Refresh()
{
Description = GetDescription( Level );
Player.Modify( this, PlayerStat.AttackSpeed, GetMultForLevel( Level ), ModifierType.Mult );
}
public override string GetDescription( int newLevel )
{
return string.Format( "Decrease attack speed by {0}%", GetPercentForLevel( Level ) );
}
public override string GetUpgradeDescription( int newLevel )
{
return newLevel > 1 ? string.Format( "Decrease attack speed by {0}%→{1}%", GetPercentForLevel( newLevel - 1 ), GetPercentForLevel( newLevel ) ) : GetDescription( newLevel );
}
public float GetMultForLevel( int level )
{
return 1f - 0.20f * level;
}
public float GetPercentForLevel( int level )
{
return 20 * level;
}
}
Game
game
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
[Status( 4, 0, 1f, 0, false )]
public class DashStrengthStatus : Status
{
public DashStrengthStatus()
{
Title = "Leg Day";
IconPath = "textures/icons/dash_strength.png";
}
public override void Init( Player player )
{
base.Init( player );
}
public override void Refresh()
{
Description = GetDescription( Level );
Player.Modify( this, PlayerStat.DashStrength, GetMultForLevel( Level ), ModifierType.Mult );
Player.Modify( this, PlayerStat.DashInvulnTime, GetMultForLevel( Level ), ModifierType.Mult );
}
public override string GetDescription( int newLevel )
{
return string.Format( "You dash {0}% longer", GetPercentForLevel( Level ) );
}
public override string GetUpgradeDescription( int newLevel )
{
return newLevel > 1 ? string.Format( "You dash {0}%→{1}% longer", GetPercentForLevel( newLevel - 1 ), GetPercentForLevel( newLevel ) ) : GetDescription( newLevel );
}
public float GetMultForLevel( int level )
{
return 1f + 0.18f * level + (level == 4 ? 0.03f : 0f);
}
public float GetPercentForLevel( int level )
{
return 18 * level + (level == 4 ? 3 : 0);
}
}
Game
game
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
[Status( 7, 0, 1f, 0, false, typeof( DashFearStatus ), typeof( GrenadeFearStatus ) )]
public class FearDropGrenadeStatus : Status
{
public FearDropGrenadeStatus()
{
Title = "Bomb Curse";
IconPath = "textures/icons/fear_drop_grenade.png";
}
public override void Init( Player player )
{
base.Init( player );
}
public override void Refresh()
{
Description = GetDescription( Level );
Player.Modify( this, PlayerStat.FearDropGrenadeChance, GetAddForLevel( Level ), ModifierType.Add ); ;
}
public override string GetDescription( int newLevel )
{
return string.Format( "Enemies you scare have a {0}% chance to drop a grenade on death", GetPercentForLevel( Level ) );
}
public override string GetUpgradeDescription( int newLevel )
{
return newLevel > 1 ? string.Format( "Enemies you scare have a {0}%→{1}% chance to drop a grenade on death", GetPercentForLevel( newLevel - 1 ), GetPercentForLevel( newLevel ) ) : GetDescription( newLevel );
}
public float GetAddForLevel( int level )
{
return level * 0.07f;
}
public float GetPercentForLevel( int level )
{
return level * 7;
}
}
Game
game
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
[Status( 5, 0, 1f, 0, false, typeof( DashFearStatus ), typeof( GrenadeFearStatus ) )]
public class FearPainStatus : Status
{
public FearPainStatus()
{
Title = "Killer Stress";
IconPath = "textures/icons/fear_pain.png";
}
public override void Init( Player player )
{
base.Init( player );
}
public override void Refresh()
{
Description = GetDescription( Level );
Player.Modify( this, PlayerStat.FearPainPercent, GetAddForLevel( Level ), ModifierType.Add );
}
public override string GetDescription( int newLevel )
{
return string.Format( "Enemies you scare lose {0}% of their remaining HP each second", GetPercentForLevel( Level ) );
}
public override string GetUpgradeDescription( int newLevel )
{
return newLevel > 1 ? string.Format( "Enemies you scare lose {0}%→{1}% of their remaining HP each second", GetPercentForLevel( newLevel - 1 ), GetPercentForLevel( newLevel ) ) : GetDescription( newLevel );
}
public float GetAddForLevel( int level )
{
return 0.02f + level * 0.07f + (level == 5 ? 0.01f : 0f);
}
public float GetPercentForLevel( int level )
{
return 2 + level * 7 + (level == 5 ? 1 : 0);
}
}
Game
game
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
[Status(4, 0, 1f, 0, false, typeof(GrenadeShootReloadStatus), typeof(FearDropGrenadeStatus))]
public class GrenadeFearStatus : Status
{
public GrenadeFearStatus()
{
Title = "Terrorism";
IconPath = "textures/icons/grenade_fear.png";
}
public override void Init(Player player)
{
base.Init(player);
}
public override void Refresh()
{
Description = GetDescription(Level);
Player.Modify(this, PlayerStat.GrenadeFearChance, GetAddForLevel(Level), ModifierType.Add); ;
}
public override string GetDescription(int newLevel)
{
return string.Format("Your grenades have a {0}% chance to scare enemies they hurt", GetPercentForLevel(Level));
}
public override string GetUpgradeDescription(int newLevel)
{
return newLevel > 1 ? string.Format("Your grenades have a {0}%→{1}% chance to scare enemies they hurt", GetPercentForLevel(newLevel - 1), GetPercentForLevel(newLevel)) : GetDescription(newLevel);
}
public float GetAddForLevel(int level)
{
return level == 4 ? 1f : 0.3f * level;
}
public float GetPercentForLevel(int level)
{
return level == 4 ? 100 : 30 * level;
}
}
Game
game
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
[Status(5, 3, 1f, 0, false )]
public class KillHealStatus : Status
{
public KillHealStatus()
{
Title = "Vampire";
IconPath = "textures/icons/kill_heal.png";
}
public override void Init(Player player)
{
base.Init(player);
}
public override void Refresh()
{
Description = GetDescription(Level);
Player.Modify(this, PlayerStat.HealthRegen, -GetHpDrainAmountForLevel(Level), ModifierType.Add);
}
public override string GetDescription(int newLevel)
{
return string.Format("Heal for {0} whenever you kill an enemy within 2 meters but lose {1} HP/s", GetPrintAmountForLevel(Level), GetHpDrainPrintAmountForLevel(Level));
}
public override string GetUpgradeDescription(int newLevel)
{
return newLevel > 1 ? string.Format( "Heal for {0}→{1} whenever you kill an enemy within 2 meters but lose {2}→{3} HP/s", GetPrintAmountForLevel(newLevel - 1), GetPrintAmountForLevel(newLevel), GetHpDrainPrintAmountForLevel(newLevel - 1), GetHpDrainPrintAmountForLevel(newLevel)) : GetDescription(newLevel);
}
public override void OnKill(Enemy enemy)
{
var distSqr = (enemy.Position2D - Player.Position2D).LengthSquared;
if ( distSqr > 2f * 2f )
return;
var amount = GetAmountForLevel( Level );
if ( Player.Health < Player.Stats[PlayerStat.MaxHp] )
Player.TimeSinceChangeHP = 0f;
Player.RegenHealth( amount );
//DamageNumbersLegacy.Create( amount, Player.Position2D + new Vector2( 0.2f + Game.Random.Float( -0.1f, 0.1f ), Player.Radius * 3f + Game.Random.Float( -0.2f, 0.3f ) ), color: Color.Green, sizeMultiplier: 0.7f );
}
public float GetAmountForLevel(int level)
{
return 0.1f + level * 0.5f + (level == 5 ? 0.05f : 0f);
}
public string GetPrintAmountForLevel(int level)
{
return string.Format("{0:0.00}", GetAmountForLevel(level));
}
public float GetHpDrainAmountForLevel(int level)
{
return level * 0.4f;
}
public string GetHpDrainPrintAmountForLevel(int level)
{
return string.Format("{0:0.00}", GetHpDrainAmountForLevel(level));
}
}
Game
game
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
[Status(3, 0, 1f, maxDifficulty: 2, isCurse: false )]
public class MoreRerollsStatus : Status
{
public MoreRerollsStatus()
{
Title = "More Rerolls";
IconPath = "textures/icons/more_rerolls.png";
}
public override void Init(Player player)
{
base.Init(player);
}
public override void Refresh()
{
Description = GetDescription(Level);
Player.Modify(this, PlayerStat.NumRerollsPerLevel, GetAddForLevel(Level), ModifierType.Add);
}
public override string GetDescription(int newLevel)
{
return string.Format("Gain {0} addition reroll each level", GetAddForLevel(Level));
}
public override string GetUpgradeDescription(int newLevel)
{
return newLevel > 1 ? string.Format("Gain {0}→{1} addition rerolls each level", GetAddForLevel(newLevel - 1), GetAddForLevel(newLevel)) : GetDescription(newLevel);
}
public float GetAddForLevel(int level)
{
return 1f * level;
}
}
Game
game
using Sandbox;
[Status( 7, 0, 1f, 0, false )]
public class MovespeedStatus : Status
{
public MovespeedStatus()
{
Title = "Fast Shoes";
IconPath = "textures/icons/shoe.png";
}
public override void Init( Player player )
{
base.Init( player );
}
public override void Refresh()
{
Description = GetDescription( Level );
Player.Modify( this, PlayerStat.MoveSpeed, GetMultForLevel( Level ), ModifierType.Mult );
}
public override string GetDescription( int newLevel )
{
return string.Format( "Increase movespeed by {0}%", GetPercentForLevel( Level ) );
}
public override string GetUpgradeDescription( int newLevel )
{
return newLevel > 1 ? string.Format( "Increase movespeed by {0}%→{1}%", GetPercentForLevel( newLevel - 1 ), GetPercentForLevel( newLevel ) ) : GetDescription( newLevel );
}
public float GetMultForLevel( int level )
{
return 1f + 0.2f * level + (level == 7 ? 0.2f : 0f);
}
public float GetPercentForLevel( int level )
{
return 20 * level + (level == 7 ? 20 : 0);
}
}
Game
game
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
public class Status
{
public bool ShouldUpdate { get; protected set; }
public Player Player { get; protected set; }
public int Level { get; set; }
public int MaxLevel { get; set; }
public TimeSince ElapsedTime { get; protected set; }
public string Title { get; protected set; }
public string Description { get; protected set; }
public string IconPath { get; protected set; }
public string DataString { get; protected set; }
public Status()
{
Level = 1;
}
public virtual void Init( Player player )
{
Player = player;
ElapsedTime = 0f;
ShouldUpdate = false;
}
// when gaining or leveling up
public virtual void Refresh()
{
}
public virtual void Update( float dt )
{
//if (ElapsedTime > 10f)
// Player.RemoveStatus(this);
}
public virtual void Remove()
{
}
public virtual string GetDescription( int newLevel )
{
return "...";
}
public virtual string GetUpgradeDescription( int newLevel )
{
return "...";
}
public virtual void Colliding( Thing other, float percent, float dt ) { }
public virtual void OnDashStarted() { }
public virtual void OnDashFinished() { }
public virtual void OnDashRecharged() { }
public virtual void OnReload() { }
//public virtual void OnBurn( Enemy enemy ) { }
//public virtual void OnFreeze( Enemy enemy ) { }
//public virtual void OnFear( Enemy enemy ) { }
public virtual void OnKill( Enemy enemy ) { }
public virtual void OnHurt( float amount ) { }
public virtual void OnGainExperience( int xp ) { }
public virtual void OnLevelUp() { }
public virtual void OnReroll() { }
public virtual void OnGainShield() { }
public virtual void OnLoseShield() { }
public virtual void OnAddStatus( int typeIdentity ) { }
}
Game
game
using Sandbox;
using Sandbox.ModelEditor.Nodes;
using System.Net.NetworkInformation;
public class Charger : Enemy
{
private TimeSince _damageTime;
private const float DAMAGE_TIME = 1f;
protected float _chargeDelayTimer;
private const float CHARGE_DELAY_MIN = 2f;
private const float CHARGE_DELAY_MAX = 6f;
public bool IsPreparingToCharge { get; private set; }
public bool IsCharging { get; private set; }
private float _prepareTimer;
private const float PREPARE_TIME = 1f;
protected float _chargeTimer;
protected float CHARGE_TIME_MIN = 1.8f;
protected float CHARGE_TIME_MAX = 2.5f;
private float _chargeTime;
private float _nextRedirectTime;
private TimeSince _timeSinceRedirect;
protected Vector2 _chargeDir;
protected Vector2 _chargeVel;
private TimeSince _chargeCloudTimer;
public override float HeightVariance => 0.03f;
public override float WidthVariance => 0.015f;
public float ChargeRange { get; set; }
protected float REDIRECT_DELAY_MIN = 0.3f;
protected float REDIRECT_DELAY_MAX = 2.7f;
protected override void OnAwake()
{
//OffsetY = -0.57f;
ShadowScale = 1.25f;
ShadowFullOpacity = 0.8f;
ShadowOpacity = 0f;
Scale = 1.25f;
base.OnAwake();
//Sprite.Texture = Texture.Load("textures/sprites/charger.vtex");
//Sprite.Size = new Vector2( 1f, 1f ) * Scale;
PushStrength = 25f;
Radius = 0.275f;
Health = 75f;
if ( Manager.Instance.Difficulty < 0 )
Health = 55f;
MaxHealth = Health;
DamageToPlayer = 10f;
CoinValueMin = 2;
CoinValueMax = 5;
CoinChance = 0.7f;
Sprite.PlayAnimation( AnimSpawnPath );
if ( IsProxy )
return;
CollideWith.Add( typeof( Enemy ) );
CollideWith.Add( typeof( Player ) );
_damageTime = DAMAGE_TIME;
_chargeDelayTimer = Game.Random.Float( CHARGE_DELAY_MIN, CHARGE_DELAY_MAX );
ChargeRange = 4.2f;
}
protected override void UpdatePosition( float dt )
{
//Gizmo.Draw.Color = Color.White.WithAlpha(0.5f);
//Gizmo.Draw.Text( $"IsCharging: {IsCharging}", new global::Transform( (Vector3)Position2D + new Vector3( 0f, -0.7f, 0f ) ) );
base.UpdatePosition( dt );
var targetPos = Target.IsValid() ? Target.Position2D : (IsCharmed ? Manager.Instance.Player.Position2D : Position2D);
if ( IsPreparingToCharge )
{
_prepareTimer -= dt;
if ( _prepareTimer < 0f )
{
Charge();
return;
}
}
else if ( IsCharging )
{
_chargeTimer -= dt;
if ( _chargeTimer < 0f )
{
IsCharging = false;
Sprite.PlayAnimation( AnimIdlePath );
CanTurn = true;
DontChangeAnimSpeed = false;
}
else
{
HandleCharging( dt );
}
WorldPosition += (Vector3)(_chargeVel + Velocity) * dt;
if ( Manager.Instance.Difficulty >= 1 && _timeSinceRedirect > _nextRedirectTime )
{
_chargeDir = (targetPos - Position2D).Normal;
if(Math.Abs( targetPos.x - Position2D.x ) > 0.15f )
FlipX = targetPos.x > Position2D.x;
_nextRedirectTime = Game.Random.Float( REDIRECT_DELAY_MIN, REDIRECT_DELAY_MAX );
_timeSinceRedirect = 0f;
}
if ( _chargeCloudTimer > 0.25f )
{
SpawnCloudClient( Position2D + new Vector2( 0f, 0.25f ), -_chargeDir * Game.Random.Float( 0.2f, 0.8f ) );
_chargeCloudTimer = Game.Random.Float( 0f, 0.075f );
}
}
else
{
Velocity += (targetPos - Position2D).Normal * dt * (IsFeared ? -1f : 1f);
float speed = 0.75f * (IsAttacking ? 1.3f : 0.7f) + Utils.FastSin( MoveTimeOffset + Time.Now * (IsAttacking ? 15f : 7.5f) ) * (IsAttacking ? 0.66f : 0.35f);
if ( Manager.Instance.Difficulty < 0 )
speed *= 0.85f;
WorldPosition += (Vector3)Velocity * speed * dt;
}
var player_dist_sqr = (targetPos - Position2D).LengthSquared;
if ( !IsPreparingToCharge && !IsCharging && !IsAttacking && player_dist_sqr < ChargeRange * ChargeRange && Target.IsValid() )
{
_chargeDelayTimer -= dt;
if ( _chargeDelayTimer < 0f )
{
PrepareToCharge();
}
}
}
protected virtual void HandleCharging(float dt)
{
float chargeSpeed = Manager.Instance.Difficulty >= 1 ? 12f : 4f; ;
_chargeVel += _chargeDir * chargeSpeed * Utils.MapReturn( _chargeTimer, _chargeTime, 0f, 0f, 1f, EasingType.Linear ) * dt;
TempWeight += Utils.MapReturn( _chargeTimer, _chargeTime, 0f, 1f, 6f, EasingType.Linear ) * dt;
float BUFFER = 0.01f;
var x_min = Manager.Instance.BOUNDS_MIN.x + Radius + BUFFER;
var x_max = Manager.Instance.BOUNDS_MAX.x - Radius - BUFFER;
var y_min = Manager.Instance.BOUNDS_MIN.y + BUFFER;
var y_max = Manager.Instance.BOUNDS_MAX.y - Radius - BUFFER;
if ( Position2D.x < x_min && _chargeDir.x < 0f )
{
_chargeDir = _chargeDir.WithX( Math.Abs( _chargeDir.x ) );
_chargeVel = _chargeVel.WithX( Math.Abs( _chargeVel.x ) * 0.1f );
FlipX = true;
Sprite.SpriteFlags = SpriteFlags.HorizontalFlip;
}
else if ( Position2D.x > x_max && _chargeDir.x > 0f )
{
_chargeDir = _chargeDir.WithX( -Math.Abs( _chargeDir.x ) );
_chargeVel = _chargeVel.WithX( -Math.Abs( _chargeVel.x ) * 0.1f );
FlipX = false;
Sprite.SpriteFlags = SpriteFlags.None;
}
if ( Position2D.y < y_min && _chargeDir.y < 0f )
{
_chargeDir = _chargeDir.WithY( Math.Abs( _chargeDir.y ) );
_chargeVel = _chargeDir.WithY( Math.Abs( _chargeVel.y ) * 0.1f );
}
else if ( Position2D.y > y_max && _chargeDir.y > 0f )
{
_chargeDir = _chargeDir.WithY( -Math.Abs( _chargeDir.y ) );
_chargeVel = _chargeDir.WithY( -Math.Abs( _chargeVel.y ) * 0.1f );
}
}
protected override void HandleDeceleration( float dt )
{
if ( IsCharging )
{
Velocity *= (1f - dt * 1.75f);
float decel = Manager.Instance.Difficulty >= 1 ? 3f : 0.5f;
_chargeVel *= (1f - dt * decel);
}
else
{
base.HandleDeceleration( dt );
}
}
protected override void UpdateSprite( Thing target )
{
if ( !IsCharging )
base.UpdateSprite( target );
}
protected override void HandleAttacking( Thing target, float dt )
{
if ( !IsPreparingToCharge && !IsCharging )
base.HandleAttacking( target, dt );
}
public void PrepareToCharge()
{
_prepareTimer = PREPARE_TIME;
IsPreparingToCharge = true;
Manager.Instance.PlaySfxNearby( "enemy.roar.prepare", Position2D, pitch: Game.Random.Float( 0.95f, 1.05f ), volume: 1f, maxDist: 5f );
Sprite.PlayAnimation( "charge_start" );
CanTurn = false;
CanAttack = false;
CanAttackAnim = false;
DontChangeAnimSpeed = true;
if ( Manager.Instance.Difficulty >= 1 )
{
_nextRedirectTime = Game.Random.Float( REDIRECT_DELAY_MIN, REDIRECT_DELAY_MAX );
_timeSinceRedirect = 0f;
}
}
public void Charge()
{
var target_pos = Target.IsValid() && !IsFeared
? Target.Position2D + Target.Velocity * Game.Random.Float( 0.5f, 1.75f )
: Position2D + Utils.GetRandomVector() * 0.5f;
_chargeDir = Utils.RotateVector( (target_pos - Position2D).Normal, Game.Random.Float( -10f, 10f ) );
IsPreparingToCharge = false;
IsCharging = true;
_chargeTime = Game.Random.Float( CHARGE_TIME_MIN, CHARGE_TIME_MAX ) * (Manager.Instance.Difficulty >= 1 ? Game.Random.Float(1.25f, Utils.Map(Manager.Instance.Difficulty, 1, 10, 1.75f, 3f, EasingType.SineIn)) : 1f);
_chargeTimer = _chargeTime;
CanAttack = true;
CanAttackAnim = true;
_chargeDelayTimer = Game.Random.Float( CHARGE_DELAY_MIN, CHARGE_DELAY_MAX ) * (Manager.Instance.Difficulty < 0 ? 1.4f : 1f);
_chargeVel = Vector2.Zero;
Sprite.PlayAnimation( "charge_loop" );
AnimSpeed = 3f;
FlipX = _chargeDir.x > 0f;
Sprite.SpriteFlags = FlipX ? SpriteFlags.HorizontalFlip : SpriteFlags.None;
//Sprite.SpriteFlags = target_pos.x > Position2D.x ? SpriteFlags.HorizontalFlip : SpriteFlags.None;
Manager.Instance.PlaySfxNearby( "enemy.roar", Position2D, pitch: Game.Random.Float( 0.925f, 1.075f ), volume: 1f, maxDist: 8f );
}
public override void Colliding( Thing other, float percent, float dt )
{
base.Colliding( other, percent, dt );
if ( other is Enemy enemy && !enemy.IsDying )
{
var spawnFactor = Utils.Map( enemy.TimeSinceSpawn, 0f, enemy.SpawnTime, 0f, 1f, EasingType.QuadIn );
Velocity += (Position2D - enemy.Position2D).Normal * Utils.Map( percent, 0f, 1f, 0f, 1f ) * enemy.PushStrength * (1f + enemy.TempWeight) * spawnFactor * dt;
if ( (IsAttacking || IsCharging) && IsCharmed != enemy.IsCharmed && _damageTime > (DAMAGE_TIME / TimeScale) )
{
var dmg = DamageToPlayer;
if ( IsCharmed )
dmg *= CharmDamageDealtMultiplier;
enemy.Damage( dmg, null, addVel: Vector2.Zero, addTempWeight: 0f, isCrit: false, DamageType.Melee );
enemy.Target = this;
_damageTime = 0f;
Manager.Instance.PlaySfxNearby( "zombie.attack.player", Position2D, pitch: Utils.Map( enemy.Health, enemy.MaxHealth, 0f, 0.95f, 1.15f, EasingType.QuadIn ), volume: 0.6f, maxDist: 4.5f );
}
}
// todo: move collision check to player instead to prevent laggy hits?
else if ( other is Player player )
{
if ( !player.IsDead )
{
Velocity += (Position2D - player.Position2D).Normal * Utils.Map( percent, 0f, 1f, 0f, 1f ) * player.Stats[PlayerStat.PushStrength] * (1f + player.TempWeight) * dt;
if ( (IsAttacking || IsCharging) && _damageTime > (DAMAGE_TIME / TimeScale) && !IsCharmed )
{
float dmg = player.CheckDamageAmount( DamageToPlayer, DamageType.Melee );
if ( !player.IsInvulnerable && !player.IsTimePausedForChoosing )
{
Manager.Instance.PlaySfxNearby( "zombie.attack.player", Position2D, pitch: Utils.Map( player.Health, player.Stats[PlayerStat.MaxHp], 0f, 0.95f, 1.15f, EasingType.QuadIn ), volume: 1f, maxDist: 5.5f );
player.Damage( dmg );
if ( dmg > 0f )
OnDamagePlayer( player, dmg );
}
_damageTime = 0f;
}
}
}
}
public override void Celebrate()
{
base.Celebrate();
CelebrateAsync();
}
async void CelebrateAsync()
{
await Task.Delay( Game.Random.Int( 0, 500 ) );
Sprite.PlaybackSpeed = Game.Random.Float( 1f, 2.5f );
Sprite.PlayAnimation( "cheer_start" );
await Task.Delay( Game.Random.Int( 200, 400 ) );
Sprite.PlayAnimation( "cheer" );
}
}
Game
game
using Sandbox;
public class RunnerEliteSpecial : RunnerElite
{
public override float FullOpacity => 0f;
protected override void OnAwake()
{
base.OnAwake();
Radius = 0.5075f;
Scale = 2.275f;
Sprite.LocalScale = new Vector3( Scale * Game.Random.Float( 1f - HeightVariance, 1f + HeightVariance ), Scale * Game.Random.Float( 1f - WidthVariance, 1f + WidthVariance ), 1f ) * Globals.SPRITE_SCALE;
Health = 300f;
MaxHealth = Health;
ShadowScale = 2.275f;
ShadowFullOpacity = 0f;
ShadowSpriteDirty = true;
PushStrength = 55f;
AggroRange = 2.25f;
DamageToPlayer = 5f;
CoinChance = 1f;
CoinValueMin = 4;
CoinValueMax = 8;
Sprite.Tint = Color.White.WithAlpha( FullOpacity );
MoveSpeed = 0.35f;
JUMP_DELAY_MIN = 4f;
JUMP_DELAY_MAX = 10f;
}
protected override void SpawnLandingClouds()
{
for ( int i = 0; i < Game.Random.Int( 2, 3 ); i++ )
{
var dir = Utils.GetRandomVector();
SpawnCloudClient( Position2D + dir * Game.Random.Float(0.4f, 0.6f), dir * Game.Random.Float( 0.3f, 1.5f ) );
}
}
public override void DealDamage()
{
Flash( 0.1f, Color.Red.WithAlpha( 0.25f ) );
}
public override void Celebrate()
{
if ( IsSpawning )
FinishSpawning();
if ( _isJumping )
return;
CelebrateAsync();
}
async void CelebrateAsync()
{
await Task.Delay( Game.Random.Int( 0, 500 ) );
Sprite.PlaybackSpeed = Game.Random.Float( 2f, 4f );
Sprite.Tint = Color.Red.WithAlpha( 0.25f );
Sprite.PlayAnimation( "cheer" );
}
}
Game
game
using Sandbox;
using static Sandbox.VertexLayout;
public class Spiker : Enemy
{
private TimeSince _damageTime;
private const float DAMAGE_TIME = 0.75f;
private float _shootDelayTimer;
private const float SHOOT_DELAY_MIN = 2f;
private const float SHOOT_DELAY_MAX = 4f;
public bool IsShooting { get; private set; }
private float _shotTimer;
private const float SHOOT_TIME = 4f;
private bool _hasShot;
private TimeSince _prepareStartTime;
private bool _hasReversed;
public override float HeightVariance => 0.02f;
public override float WidthVariance => 0.01f;
private bool _moveClockwise;
public static int SpikerNum { get; set; }
private float _perpendicularMaxDist;
private float _digDelayTimer;
private const float DIG_DELAY_MIN = 4f;
private const float DIG_DELAY_MAX = 13f;
public bool IsDigging { get; private set; }
private TimeSince _timeSinceStartDigging;
private const float DIG_TIME = 1.2f;
public float ShootRange { get; set; }
public float MoveSpeed { get; set; }
protected override void OnAwake()
{
//OffsetY = -0.58f;
ShadowScale = 1.2f;
ShadowFullOpacity = 0.8f;
ShadowOpacity = 0f;
Scale = 1.4f;
base.OnAwake();
//AnimSpeed = 4f;
//Sprite.Texture = Texture.Load("textures/sprites/spiker.vtex");
PushStrength = 8f;
Deceleration = 2.57f;
DecelerationAttacking = 2.35f;
AggroRange = 0.75f;
Radius = 0.27f;
Health = 80f;
if ( Manager.Instance.Difficulty < 0 )
Health = 70f;
MaxHealth = Health;
DamageToPlayer = 14f;
CoinValueMin = 2;
CoinValueMax = 4;
CoinChance = 0.75f;
Sprite.PlayAnimation( AnimSpawnPath );
if ( IsProxy )
return;
CollideWith.Add( typeof( Enemy ) );
CollideWith.Add( typeof( Player ) );
_damageTime = DAMAGE_TIME;
_shootDelayTimer = Game.Random.Float( SHOOT_DELAY_MIN, SHOOT_DELAY_MAX );
_digDelayTimer = Game.Random.Float( DIG_DELAY_MIN, DIG_DELAY_MAX );
_moveClockwise = SpikerNum % 2 == 0;
SpikerNum++;
_perpendicularMaxDist = Game.Random.Float( 3.5f, 6.5f );
ShootRange = 4.5f;
MoveSpeed = 0.9f;
}
protected override void UpdatePosition( float dt )
{
base.UpdatePosition( dt );
//if(!IsDigging)
//{
// Gizmo.Draw.Color = Color.White;
// Gizmo.Draw.Text( $"{_digDelayTimer}", new global::Transform( (Vector3)Position2D + new Vector3( 0f, -0.4f, 0f ) ) );
//}
var targetPos = GetTargetPos();
if ( IsShooting )
{
Velocity *= (1f - dt * (IsAttacking ? DecelerationAttacking : Deceleration));
if ( !_hasShot && _prepareStartTime > 1f )
{
CreateSpike();
_hasShot = true;
}
if ( !_hasReversed && _prepareStartTime > 3f )
{
_hasReversed = true;
Sprite.PlayAnimation( "shoot_reverse" );
}
Velocity *= (1f - dt * 4f);
_shotTimer -= dt;
if ( _shotTimer < 0f )
{
FinishShooting();
return;
}
}
else if ( IsDigging )
{
Velocity *= (1f - dt * 6f);
if ( _timeSinceStartDigging > DIG_TIME )
{
Vector2 pos;
if ( Target.IsValid() )
{
pos = targetPos + Target.Velocity * Game.Random.Float( 0f, 2f ) + Utils.GetRandomVector() * Game.Random.Float( 1.5f, 5.5f);
if ( (Position2D - pos).LengthSquared < MathF.Pow( 0.5f, 2f ) )
pos = Position2D + Utils.GetRandomVector() * Game.Random.Float( 3f, 5f );
}
else
{
pos = Position2D + Game.Random.Float( 4f, 5f );
}
FinishDigging( Manager.Instance.ClampToBounds( pos ) );
}
else
{
float progress = Utils.Map( _timeSinceStartDigging, 0f, DIG_TIME, 0f, 1f );
//ZPos = Utils.Map( progress, 0f, 1f, 0f, DIG_DEPTH );
//PlaybackRate = Utils.Map( progress, 0f, 1f, 0f, 1f ) * _personalSpeedScale;
float shadowOpacity = Utils.Map( progress, 0f, 1f, ShadowOpacity, 0f, EasingType.QuadIn );
ShadowSprite.Tint = Color.Black.WithAlpha( shadowOpacity );
VfxOpacity = Utils.Map( progress, 0f, 1f, 1f, 0f, EasingType.QuadIn );
//Gizmo.Draw.Color = Color.White;
//Gizmo.Draw.Text( $"{progress}", new global::Transform( (Vector3)Position2D + new Vector3( 0f, -0.4f, 0f ) ) );
IgnoreCollision = progress > 0.3f;
if ( _spawnCloudTime > (0.3f / TimeScale) )
{
var cloud = Manager.Instance.SpawnCloud( Position2D + new Vector2( 0f, 0.05f ) );
cloud.Velocity = new Vector2( Game.Random.Float( -1f, 1f ), Game.Random.Float( -1f, 1f ) ).Normal * Game.Random.Float( 0.2f, 0.6f );
_spawnCloudTime = Game.Random.Float( 0f, 0.15f );
}
}
return;
}
else
{
Vector2 toTarget = (targetPos - Position2D).Normal;
if(Manager.Instance.Difficulty >= 1)
{
if ( (targetPos - Position2D).LengthSquared < MathF.Pow( _perpendicularMaxDist, 2f ) )
toTarget = Vector2.Lerp( toTarget, new Vector2( toTarget.y, -toTarget.x ) * (_moveClockwise ? -1f : 1f), Utils.Map( (targetPos - Position2D).LengthSquared, MathF.Pow( _perpendicularMaxDist, 2f ), MathF.Pow( 1.5f, 2f ), 0f, 1f ) );
}
Velocity += toTarget * 1.0f * dt * (IsFeared ? -1f : 1f);
}
float speed = MoveSpeed * (IsAttacking ? 1.3f : 0.7f) + Utils.FastSin( MoveTimeOffset + Time.Now * (IsAttacking ? 15f : 7.5f) ) * (IsAttacking ? 0.66f : 0.35f);
if ( Manager.Instance.Difficulty < 0 )
speed *= 0.85f;
WorldPosition += (Vector3)Velocity * speed * dt;
if ( !IsShooting && !IsDigging && (!IsAttacking || IsCharmed) && !Manager.Instance.IsGameOver )
{
var target_dist_sqr = ((Target.IsValid() ? Target.Position2D : targetPos) - Position2D).LengthSquared;
var rangeSqr = ShootRange * ShootRange;
if ( Manager.Instance.Difficulty < 0 )
rangeSqr *= 0.7f;
if ( target_dist_sqr < rangeSqr )
{
_shootDelayTimer -= dt;
if ( _shootDelayTimer < 0f )
StartShooting();
}
if ( target_dist_sqr < MathF.Pow( 12f, 2f ) && Manager.Instance.Difficulty > 0 )
{
_digDelayTimer -= dt;
if ( _digDelayTimer < 0f )
StartDigging();
}
}
}
protected virtual Vector2 GetTargetPos()
{
return Target.IsValid() ? Target.Position2D : (IsCharmed ? Manager.Instance.Player.Position2D : Position2D);
}
protected override void UpdateSprite( Thing target )
{
if ( Sprite.CurrentAnimation.Name.Contains( "shoot" ) || IsDigging )
return;
base.UpdateSprite( target );
}
public void StartShooting()
{
_shotTimer = SHOOT_TIME;
IsShooting = true;
CanAttack = false;
CanTurn = false;
CanAttackAnim = false;
_hasShot = false;
_hasReversed = false;
_prepareStartTime = 0f;
Velocity *= 0.25f;
DontChangeAnimSpeed = true;
AnimSpeed = 1f;
Sprite.PlayAnimation( "shoot" );
ShouldUpdateAfterGameOver = true;
}
public virtual void CreateSpike()
{
var target_pos = Target.IsValid()
? Target.Position2D + Target.Velocity * Game.Random.Float( 0.2f, 2f ) + new Vector2( Game.Random.Float( -1f, 1f ), Game.Random.Float( -1f, 1f ) ) * 0.8f
: Position2D + Utils.GetRandomVector() * Game.Random.Float( 3f, 6f );
var BUFFER = 0.1f;
var spike = Manager.Instance.SpawnEnemySpike( new Vector2( Math.Clamp( target_pos.x, Manager.Instance.BOUNDS_MIN.x + BUFFER, Manager.Instance.BOUNDS_MAX.x - BUFFER ), Math.Clamp( target_pos.y, Manager.Instance.BOUNDS_MIN.y + BUFFER, Manager.Instance.BOUNDS_MAX.y - BUFFER ) ) );
if(IsCharmed)
{
spike.BecomeCharmed();
}
Manager.Instance.PlaySfxNearby( "spike.prepare", target_pos, pitch: Game.Random.Float( 0.95f, 1.05f ), volume: 1.5f, maxDist: 5f );
}
public void FinishShooting()
{
_shootDelayTimer = Game.Random.Float( SHOOT_DELAY_MIN, SHOOT_DELAY_MAX ) * (Manager.Instance.Difficulty < 0 ? 2.5f : 1f);
IsShooting = false;
CanAttack = true;
CanAttackAnim = true;
CanTurn = true;
DontChangeAnimSpeed = false;
Sprite.PlayAnimation( AnimIdlePath );
ShouldUpdateAfterGameOver = false;
if ( Manager.Instance.IsGameOver && !Manager.Instance.ShouldUpdateThings )
Celebrate();
}
public override void Colliding( Thing other, float percent, float dt )
{
base.Colliding( other, percent, dt );
if ( other is Enemy enemy && !enemy.IsDying )
{
var spawnFactor = Utils.Map( enemy.TimeSinceSpawn, 0f, enemy.SpawnTime, 0f, 1f, EasingType.QuadIn );
Velocity += (Position2D - enemy.Position2D).Normal * Utils.Map( percent, 0f, 1f, 0f, 1f ) * enemy.PushStrength * (1f + enemy.TempWeight) * spawnFactor * dt;
if ( IsAttacking && IsCharmed != enemy.IsCharmed && _damageTime > (DAMAGE_TIME / TimeScale) )
{
var dmg = DamageToPlayer;
if ( IsCharmed )
dmg *= CharmDamageDealtMultiplier;
enemy.Damage( dmg, null, addVel: Vector2.Zero, addTempWeight: 0f, isCrit: false, DamageType.Melee );
enemy.Target = this;
_damageTime = 0f;
Manager.Instance.PlaySfxNearby( "zombie.attack.player", Position2D, pitch: Utils.Map( enemy.Health, enemy.MaxHealth, 0f, 0.95f, 1.15f, EasingType.QuadIn ), volume: 0.6f, maxDist: 4.5f );
}
}
// todo: move collision check to player instead to prevent laggy hits?
else if ( other is Player player )
{
if ( !player.IsDead )
{
Velocity += (Position2D - player.Position2D).Normal * Utils.Map( percent, 0f, 1f, 0f, 1f ) * player.Stats[PlayerStat.PushStrength] * (1f + player.TempWeight) * dt;
if ( IsAttacking && _damageTime > (DAMAGE_TIME / TimeScale) && !IsCharmed )
{
float dmg = player.CheckDamageAmount( DamageToPlayer, DamageType.Melee );
if ( !player.IsInvulnerable && !player.IsTimePausedForChoosing )
{
Manager.Instance.PlaySfxNearby( "zombie.attack.player", Position2D, pitch: Utils.Map( player.Health, player.Stats[PlayerStat.MaxHp], 0f, 0.95f, 1.15f, EasingType.QuadIn ), volume: 1f, maxDist: 5.5f );
player.Damage( dmg );
if( dmg > 0f )
OnDamagePlayer( player, dmg );
}
_damageTime = 0f;
}
}
}
}
public override void Damage( float damage, Player player, Vector2 addVel, float addTempWeight, bool isCrit = false, DamageType damageType = DamageType.PlayerBullet )
{
base.Damage( damage, player, addVel, addTempWeight, isCrit, damageType );
if ( Game.Random.Float( 0f, 1f ) < Utils.Map( damage, 1f, 20f, 0.1f, 0.7f ) )
_digDelayTimer *= Game.Random.Float( 0.6f, 0.95f );
}
public void StartDigging()
{
IsDigging = true;
_timeSinceStartDigging = 0f;
CanAttack = false;
CanAttackAnim = false;
CanTurn = false;
Velocity *= 0.5f;
Sprite.PlayAnimation( "dig" );
Manager.Instance.PlaySfxNearby( "zombie.dirt", Position2D, pitch: Game.Random.Float( 0.5f, 0.55f ), volume: 0.6f, maxDist: 7.5f );
//SS2Game.PlaySfx( "zombie.dirt", Position, pitch: Game.Random.Float( 0.6f, 0.8f ), volume: 0.7f );
//SS2Game.Current.DustCloudClient( Position2D );
ShouldUpdateAfterGameOver = true;
}
void FinishDigging( Vector2 pos )
{
Position2D = pos;
//WorldPosition = ((Vector3)Position2D).WithZ( ZPos );
Transform.ClearInterpolation();
IsDigging = false;
CanAttack = true;
CanAttackAnim = true;
CanTurn = true;
IgnoreCollision = false;
//SetAnim( "Attack1" );
_moveClockwise = !_moveClockwise;
_digDelayTimer = Game.Random.Float( DIG_DELAY_MIN, DIG_DELAY_MAX );
//SS2Game.PlaySfx( "zombie.dirt", Position, pitch: Game.Random.Float( 0.6f, 0.8f ), volume: 0.7f );
//SS2Game.Current.DustCloudClient( Position2D );
IsSpawning = true;
//_hasDug = true;
TimeSinceSpawn = 0f;
//SpawnProgress = 0f;
//ShadowRadiusModifier = 1.5f;
//ShadowOpacityModifier = 0f;
Manager.Instance.PlaySfxNearby( "zombie.dirt", Position2D, pitch: Game.Random.Float( 0.85f, 0.9f ), volume: 0.6f, maxDist: 7.5f );
ShadowSprite.Tint = Color.Black.WithAlpha( 0f );
VfxOpacity = 0f;
}
public override void Celebrate()
{
base.Celebrate();
if ( IsShooting || IsDigging )
return;
CelebrateAsync();
}
async void CelebrateAsync()
{
await Task.Delay( Game.Random.Int( 0, 500 ) );
Sprite.PlaybackSpeed = Game.Random.Float( 1.5f, 3.5f );
Sprite.PlayAnimation( "cheer_start" );
await Task.Delay( Game.Random.Int( 200, 400 ) );
Sprite.PlayAnimation( "cheer" );
}
protected override void FinishSpawning()
{
base.FinishSpawning();
ShouldUpdateAfterGameOver = false;
if ( Manager.Instance.IsGameOver && !Manager.Instance.ShouldUpdateThings )
Celebrate();
}
}
Game
game
using static Manager;
public enum ModifierType { Set, Add, Mult }
public class ModifierData
{
public float value;
public ModifierType type;
public float priority;
public ModifierData( float _value, ModifierType _type, float _priority = 0f )
{
value = _value;
type = _type;
priority = _priority;
}
}
public enum PlayerStat
{
AttackTime, AttackSpeed, ReloadTime, ReloadSpeed, MaxAmmoCount, BulletDamage, BulletForce, Recoil, MoveSpeed, NumProjectiles, BulletSpread, BulletInaccuracy, BulletSpeed, BulletLifetime,
BulletNumPiercing, CritChance, CritMultiplier, LowHealthDamageMultiplier, NumUpgradeChoices, HealthRegen, HealthRegenStill, DamageReductionPercent, PushStrength, CoinAttractRange, CoinAttractStrength, Luck, MaxHp,
NumDashes, DashInvulnTime, DashCooldown, DashProgress, DashStrength, ThornsPercent, ShootFireIgniteChance, FireDamage, FireLifetime, FireSpreadChance, ShootFreezeChance, FreezeLifetime,
FreezeTimeScale, FreezeOnMeleeChance, FreezeFireDamageMultiplier, LastAmmoDamageMultiplier, FearLifetime, FearDamageMultiplier, FearOnMeleeChance, BulletDamageGrow, BulletDamageShrink,
BulletDistanceDamage, NumRerollsPerLevel, FullHealthDamageMultiplier, DamagePerEarlierShot, DamageForSpeed, OverallDamageMultiplier, ExplosionSizeMultiplier, GrenadeVelocity, ExplosionDamageMultiplier,
BulletDamageMultiplier, ExplosionDamageReductionPercent, NonExplosionDamageIncreasePercent, GrenadeStickyPercent, GrenadeFearChance, FearDrainPercent, FearPainPercent, CrateChanceAdditional,
AttackSpeedStill, FearDropGrenadeChance, FrozenShardsNum, NoDashInvuln, BulletFlatDamageAddition, GrenadesCanCrit, BulletHealTeammateAmount, HomingBulletChance, PauseWhileChoosing,
BulletNumBouncing, DashCharm, CharmedEnemyDmgTakenMultiplier, CharmedEnemyDmgDealtMultiplier, BossArrivalTime, RadiusMultiplier, SpecialistStatusAmount, ZoomAmount, MaxFireStacks, XpRepel, HealthPackAmount,
MaxBulletSpread, ShootRandomDirChance, DashRandomDirChance, MoveSelfDmg, MoveSelfDmgReqDist, MoveSelfDmgAmount, SelfDmgDistanceMoved, IncreasedDmgTaken, ReverseControls, SelfCritChance,
}
public enum DamageType { Melee, Ranged, Explosion, Fire, PlayerBullet, Self, Generic, LavaPuddle, FearPain, }
public enum PlayerDamageType { Enemy, Self, Grenade }
public struct CamShakeData
{
public float strength;
public float startTime;
public float time;
public EasingType easingType;
public bool useRealTime;
public CamShakeData( float _strength, float _startTime, float _time, EasingType _easingType, bool _useRealTime )
{
strength = _strength;
startTime = _startTime;
time = _time;
easingType = _easingType;
useRealTime = _useRealTime;
}
}
public class Player : Thing
{
[Property] public GameObject Body { get; set; }
[Property] public GameObject ArrowAimerPrefab { get; set; }
[Property] public GameObject BulletPrefab { get; set; }
[Property] public Sprite EasySprite { get; set; }
[Property] public Sprite Difficulty1Sprite { get; set; }
[Property] public Sprite Difficulty2Sprite { get; set; }
[Property] public Sprite Difficulty3Sprite { get; set; }
[Property] public Sprite Difficulty4Sprite { get; set; }
[Property] public Sprite Difficulty5Sprite { get; set; }
[Property] public Sprite Difficulty6Sprite { get; set; }
[Property] public Sprite Difficulty7Sprite { get; set; }
[Property] public Sprite Difficulty8Sprite { get; set; }
[Property] public Sprite Difficulty9Sprite { get; set; }
[Property] public Sprite Difficulty10Sprite { get; set; }
[Property] public Sprite Difficulty11Sprite { get; set; }
[Property] public Sprite Difficulty12Sprite { get; set; }
[Property] public Sprite Difficulty13Sprite { get; set; }
[Property] public Sprite Difficulty14Sprite { get; set; }
[Property] public Sprite Difficulty15Sprite { get; set; }
[Sync] public float Health { get; set; }
private float _regenHpAccumulated;
private float _drainHpAccumulated;
[Sync] public Vector2 InputVector { get; set; }
public GameObject ArrowAimer { get; private set; }
public Sprite2D ArrowSprite { get; private set; }
public Vector2 AimDir { get; private set; }
private Vector2 _controllerAimTarget;
private const float CONTROLLER_AIM_DEADZONE = 0.25f;
[Sync] public bool IsDead { get; private set; }
public float Timer { get; protected set; }
[Sync] public bool IsReloading { get; protected set; }
[Sync] public float ReloadProgress { get; protected set; }
public const float BASE_MOVE_SPEED = 15f;
private int _shotNum;
[Sync] public int Level { get; protected set; }
public int ExperienceTotal { get; protected set; }
public int ExperienceCurrent { get; protected set; }
public int ExperienceRequired { get; protected set; }
public bool IsChoosingLevelUpReward { get; protected set; }
public TimeSince TimeSinceLevelUp { get; set; }
public RealTimeSince RealTimeSinceChoseUpgrade { get; set; }
public List<Status> LevelUpChoices { get; private set; }
[Sync] public float DashTimer { get; private set; }
[Sync] public bool IsDashing { get; private set; }
[Sync] public Vector2 DashVelocity { get; private set; }
[Sync] public float DashInvulnTimer { get; private set; }
private TimeSince _dashCloudTime;
public float DashProgress { get; protected set; }
[Sync] public float DashRechargeProgress { get; protected set; }
[Sync] public int NumDashesAvailable { get; set; }
public int AmmoCount { get; protected set; }
public bool IsMoving => Velocity.LengthSquared > 0.05f && !IsDashing;
//public bool IsMoving => (Position2D - _lastPos2D).LengthSquared > 0.000001f;
public bool IsInputtingMove => Input.AnalogMove.LengthSquared > 0.01f;
public bool IsInvulnerable => IsDashing && Stats[PlayerStat.NoDashInvuln] <= 0f;
public bool IsTimePausedForChoosing => IsChoosingLevelUpReward && Stats[PlayerStat.PauseWhileChoosing] > 0f;
private float _flashTimer;
private bool _isFlashing;
public TimeSince TimeSinceHurt { get; private set; }
public TimeSince TimeSinceChangeHP { get; set; }
private GameObject _shieldVfx;
[Sync] public int NumRerollAvailable { get; set; }
[Sync] public NetDictionary<PlayerStat, float> Stats { get; private set; } = new();
public Dictionary<int, Status> Statuses { get; private set; }
private Dictionary<Status, Dictionary<PlayerStat, ModifierData>> _modifiers_stat = new Dictionary<Status, Dictionary<PlayerStat, ModifierData>>();
private Dictionary<PlayerStat, float> _original_properties_stat = new Dictionary<PlayerStat, float>();
private bool _doneFirstUpdate;
private TimeSince _timeSinceShoot;
private TimeSince _timeSinceSpawn;
private Vector2 _lastPos2D;
private TimeSince _timeSinceTouchLeftSide;
private bool _hasUnlockedSprinterAchievement;
private bool _hasUnlockedExperiencedAchievement;
public TimeSince TimeSinceInputMove { get; set; }
private bool _hasUnlockedNoMoveAchievement;
private List<CamShakeData> _camShakeDatas = new();
public float CamShakeAmount { get; set; }
public int ChoiceHash { get; set; }
public TimeSince TimeSinceHurtLava { get; set; }
public RealTimeSince RealTimeSinceDeath { get; set; }
private float _arrowDeathAlphaStart;
private bool _hasPlayedDeathSfx;
protected override void OnAwake()
{
base.OnAwake();
Scale = 1f;
ShadowOpacity = 0.8f;
ShadowScale = 1.12f;
Statuses = new Dictionary<int, Status>();
LevelUpChoices = new List<Status>();
InitializeStats();
if ( IsProxy )
return;
CollideWith.Add( typeof( Enemy ) );
CollideWith.Add( typeof( Player ) );
ArrowAimer = ArrowAimerPrefab.Clone( WorldPosition );
ArrowAimer.SetParent( GameObject );
ArrowAimer.NetworkMode = NetworkMode.Never;
ArrowSprite = ArrowAimer.Components.Get<Sprite2D>();
ArrowSprite.Tint = Color.White.WithAlpha( 0f );
_timeSinceShoot = 999f;
_timeSinceSpawn = 0f;
if ( Manager.Instance.Difficulty < 0 ) Sprite.Sprite = EasySprite;
else if ( Manager.Instance.Difficulty == 1 ) Sprite.Sprite = Difficulty1Sprite;
else if ( Manager.Instance.Difficulty == 2 ) Sprite.Sprite = Difficulty2Sprite;
else if ( Manager.Instance.Difficulty == 3 ) Sprite.Sprite = Difficulty3Sprite;
else if ( Manager.Instance.Difficulty == 4 ) Sprite.Sprite = Difficulty4Sprite;
else if ( Manager.Instance.Difficulty == 5 ) Sprite.Sprite = Difficulty5Sprite;
else if ( Manager.Instance.Difficulty == 6 ) Sprite.Sprite = Difficulty6Sprite;
else if ( Manager.Instance.Difficulty == 7 ) Sprite.Sprite = Difficulty7Sprite;
else if ( Manager.Instance.Difficulty == 8 ) Sprite.Sprite = Difficulty8Sprite;
else if ( Manager.Instance.Difficulty == 9 ) Sprite.Sprite = Difficulty9Sprite;
else if ( Manager.Instance.Difficulty == 10 ) Sprite.Sprite = Difficulty10Sprite;
else if ( Manager.Instance.Difficulty == 11 ) Sprite.Sprite = Difficulty11Sprite;
else if ( Manager.Instance.Difficulty == 12 ) Sprite.Sprite = Difficulty12Sprite;
else if ( Manager.Instance.Difficulty == 13 ) Sprite.Sprite = Difficulty13Sprite;
else if ( Manager.Instance.Difficulty == 14 ) Sprite.Sprite = Difficulty14Sprite;
else if ( Manager.Instance.Difficulty == 15 ) Sprite.Sprite = Difficulty15Sprite;
//Sprite.LocalScale = 0.5f * Globals.SPRITE_SCALE;
}
public void InitializeStats()
{
_original_properties_stat.Clear();
if ( Network.Active )
{
RemoveShieldVfx();
}
else
{
if ( _shieldVfx != null )
{
_shieldVfx.Destroy();
_shieldVfx = null;
}
}
Level = 0;
ExperienceRequired = GetExperienceReqForLevel( Level + 1 );
ExperienceTotal = 0;
ExperienceCurrent = 0;
Stats[PlayerStat.AttackTime] = 0.15f;
AmmoCount = 5;
Stats[PlayerStat.MaxAmmoCount] = AmmoCount;
Stats[PlayerStat.ReloadTime] = 1.5f;
Stats[PlayerStat.ReloadSpeed] = 1f;
Stats[PlayerStat.AttackSpeed] = 1f;
Stats[PlayerStat.BulletDamage] = 5f;
Stats[PlayerStat.BulletForce] = 0.55f;
Stats[PlayerStat.Recoil] = 0f;
Stats[PlayerStat.MoveSpeed] = 1f;
Stats[PlayerStat.NumProjectiles] = 1f;
Stats[PlayerStat.BulletSpread] = 35f;
Stats[PlayerStat.BulletInaccuracy] = 5f;
Stats[PlayerStat.BulletSpeed] = 4.5f;
Stats[PlayerStat.BulletLifetime] = 0.8f;
Stats[PlayerStat.Luck] = 1f;
Stats[PlayerStat.CritChance] = 0.05f;
Stats[PlayerStat.CritMultiplier] = 1.5f;
Stats[PlayerStat.LowHealthDamageMultiplier] = 1f;
Stats[PlayerStat.FullHealthDamageMultiplier] = 1f;
Stats[PlayerStat.ThornsPercent] = 0f;
Stats[PlayerStat.NumDashes] = 1f;
NumDashesAvailable = (int)MathF.Round( Stats[PlayerStat.NumDashes] );
Stats[PlayerStat.DashCooldown] = 3f;
Stats[PlayerStat.DashInvulnTime] = 0.25f;
Stats[PlayerStat.DashStrength] = 3f;
Stats[PlayerStat.BulletNumPiercing] = 0f;
Stats[PlayerStat.BulletNumBouncing] = 0f;
Stats[PlayerStat.DashCharm] = 0f;
Stats[PlayerStat.CharmedEnemyDmgTakenMultiplier] = 1f;
Stats[PlayerStat.CharmedEnemyDmgDealtMultiplier] = 1f;
Stats[PlayerStat.BossArrivalTime] = 15 * 60f;
//Stats[PlayerStat.BossArrivalTime] = (Manager.Instance.Difficulty >= 5 ? 10 : 15) * 60f;
Stats[PlayerStat.RadiusMultiplier] = 1f;
Stats[PlayerStat.SpecialistStatusAmount] = 0f;
Stats[PlayerStat.ZoomAmount] = 0f;
Stats[PlayerStat.MaxFireStacks] = 0f;
Stats[PlayerStat.XpRepel] = 0f;
Stats[PlayerStat.HealthPackAmount] = 20f;
Stats[PlayerStat.MaxBulletSpread] = 0f;
Stats[PlayerStat.ShootRandomDirChance] = 0f;
Stats[PlayerStat.DashRandomDirChance] = 0f;
Stats[PlayerStat.MoveSelfDmg] = 0f;
Stats[PlayerStat.MoveSelfDmgReqDist] = 0f;
Stats[PlayerStat.MoveSelfDmgAmount] = 0f;
Stats[PlayerStat.SelfDmgDistanceMoved] = 0f;
Stats[PlayerStat.IncreasedDmgTaken] = 0f;
Stats[PlayerStat.ReverseControls] = 0f;
Stats[PlayerStat.SelfCritChance] = 0f;
Health = 100f;
Stats[PlayerStat.MaxHp] = 100f;
//Health = 1f;
_regenHpAccumulated = 0f;
_drainHpAccumulated = 0f;
IsDead = false;
Radius = 0.10f;
GridPos = Manager.Instance.GetGridSquareForPos( Position2D );
AimDir = new Vector2( 0f, 1f );
_controllerAimTarget = Vector2.Zero;
NumRerollAvailable = Manager.Instance.Difficulty < 0 ? 4 : (Manager.Instance.Difficulty >= 3 ? 3 : 2);
Stats[PlayerStat.FireDamage] = 1f;
Stats[PlayerStat.FireLifetime] = 3f;
Stats[PlayerStat.ShootFireIgniteChance] = 0f;
Stats[PlayerStat.FireSpreadChance] = 0f;
Stats[PlayerStat.ShootFreezeChance] = 0f;
Stats[PlayerStat.FreezeLifetime] = 4f;
Stats[PlayerStat.FreezeTimeScale] = 0.55f;
Stats[PlayerStat.FreezeOnMeleeChance] = 0f;
Stats[PlayerStat.FreezeFireDamageMultiplier] = 1f;
Stats[PlayerStat.FearLifetime] = 4.5f;
Stats[PlayerStat.FearDamageMultiplier] = 1f;
Stats[PlayerStat.FearOnMeleeChance] = 0f;
Stats[PlayerStat.CoinAttractRange] = 1.7f;
Stats[PlayerStat.CoinAttractStrength] = 3.5f;
Stats[PlayerStat.NumUpgradeChoices] = 3f;
Stats[PlayerStat.HealthRegen] = Manager.Instance.Difficulty == -1 ? 0.4f : 0f;
Stats[PlayerStat.HealthRegenStill] = 0f;
//Stats[PlayerStat.HealthDrain] = 0f;
Stats[PlayerStat.DamageReductionPercent] = 0f;
Stats[PlayerStat.PushStrength] = 30f;
Stats[PlayerStat.LastAmmoDamageMultiplier] = 1f;
Stats[PlayerStat.BulletDamageGrow] = 0f;
Stats[PlayerStat.BulletDamageShrink] = 0f;
Stats[PlayerStat.BulletDistanceDamage] = 0f;
Stats[PlayerStat.NumRerollsPerLevel] = 1f;
Stats[PlayerStat.DamagePerEarlierShot] = 0f;
Stats[PlayerStat.DamageForSpeed] = 0f;
Stats[PlayerStat.OverallDamageMultiplier] = 1f;
Stats[PlayerStat.ExplosionSizeMultiplier] = 1f;
Stats[PlayerStat.GrenadeVelocity] = 8f;
Stats[PlayerStat.ExplosionDamageMultiplier] = 1f;
Stats[PlayerStat.BulletDamageMultiplier] = 1f;
Stats[PlayerStat.ExplosionDamageReductionPercent] = 0f;
Stats[PlayerStat.NonExplosionDamageIncreasePercent] = 0f;
Stats[PlayerStat.GrenadeStickyPercent] = 0f;
Stats[PlayerStat.GrenadeFearChance] = 0f;
Stats[PlayerStat.FearDrainPercent] = 0f;
Stats[PlayerStat.FearPainPercent] = 0f;
Stats[PlayerStat.CrateChanceAdditional] = 0f;
Stats[PlayerStat.AttackSpeedStill] = 1f;
Stats[PlayerStat.FearDropGrenadeChance] = 0f;
Stats[PlayerStat.FrozenShardsNum] = 0f;
Stats[PlayerStat.NoDashInvuln] = 0f;
Stats[PlayerStat.BulletFlatDamageAddition] = 0f;
Stats[PlayerStat.GrenadesCanCrit] = 0f;
Stats[PlayerStat.BulletHealTeammateAmount] = 0f;
Stats[PlayerStat.HomingBulletChance] = 0f;
Stats[PlayerStat.PauseWhileChoosing] = 0f;
Statuses.Clear();
_modifiers_stat.Clear();
_isFlashing = false;
Sprite.FlashTint = Color.White.WithAlpha( 0f );
Sprite.Tint = Color.White;
IsChoosingLevelUpReward = false;
IsDashing = false;
IsReloading = true;
Timer = Stats[PlayerStat.ReloadTime];
ReloadProgress = 0f;
DashProgress = 0f;
DashRechargeProgress = 1f;
TempWeight = 0f;
_shotNum = 0;
TimeSinceHurt = 999f;
TimeSinceChangeHP = 999f;
TimeSinceHurtLava = 999f;
ShadowOpacity = 0.8f;
ShadowSpriteDirty = true;
_timeSinceTouchLeftSide = 999f;
TimeSinceInputMove = 0f;
TimeSinceLevelUp = 999f;
RealTimeSinceChoseUpgrade = 999f;
_camShakeDatas.Clear();
ChoiceHash = 0;
}
public Vector2 AverageVelocity { get; private set; }
protected override void OnUpdate()
{
base.OnUpdate();
AverageVelocity += Velocity * Time.Delta * 8f;
//AverageVelocity = Utils.DynamicEaseTo( AverageVelocity, Vector2.Zero, 0.02f, Time.Delta );
AverageVelocity *= (1f - Time.Delta * 2.7f);
//Gizmo.Draw.Color = Color.Red.WithAlpha( 0.5f );
//Gizmo.Draw.Line( Position2D, Position2D + AverageVelocity );
//Gizmo.Draw.Color = Color.Red.WithAlpha( 0.5f );
//Gizmo.Draw.Line( Position2D, Position2D + AverageVelocity );
Vector2 anchor = Position2D + AverageVelocity * 0.1f;
Vector2 perp = Utils.GetPerpendicularVector( AverageVelocity ).Normal;
var perpA = anchor - perp * 10f;
var perpB = anchor + perp * 10f;
//Gizmo.Draw.Color = Color.Green.WithAlpha( 0.3f );
//Gizmo.Draw.Line( perpA, perpB );
//Gizmo.Draw.Color = Color.White.WithAlpha( 0.03f );
//Gizmo.Draw.LineSphere( (Vector3)Position2D, 2, 16 );
//Gizmo.Draw.Color = Color.White.WithAlpha( 0.5f );
//Gizmo.Draw.Text( $"{Stats[PlayerStat.FearPainPercent]}", new global::Transform( (Vector3)Position2D + new Vector3( 0f, -0.4f, 0f ) ) );
if ( !_doneFirstUpdate )
{
SpawnShadow( ShadowScale, ShadowOpacity );
Manager.Instance.Camera2D.SetPos( Position2D );
_doneFirstUpdate = true;
}
InputVector = new Vector2( -Input.AnalogMove.y, Input.AnalogMove.x );
if ( Stats[PlayerStat.ReverseControls] > 0f )
InputVector *= -1f;
//if ( Input.Pressed( "Menu" ) )
//{
// Manager.Instance.Restart();
// return;
//}
HandleCamShaking();
if ( IsDead )
{
Sprite.FlashTint = RealTimeSinceDeath < 0.1f
? Color.Red
: Color.White.WithAlpha( 0f );
ShadowOpacity = Utils.Map( RealTimeSinceDeath, 0f, Manager.FINAL_PANEL_WAIT_TIME, 0.8f, 0f, EasingType.Linear );
ShadowSprite.Tint = Color.Black.WithAlpha( ShadowOpacity );
ArrowSprite.Tint = Color.White.WithAlpha( Utils.Map( RealTimeSinceDeath, 0f, 0.4f, _arrowDeathAlphaStart, 0f, EasingType.SineOut ) );
if ( !_hasPlayedDeathSfx && RealTimeSinceDeath > 0.7f )
{
Manager.Instance.PlaySfxNearby( "zombie.attack.player", Position2D, pitch: Game.Random.Float( 0.55f, 0.65f ), volume: 1f, maxDist: 5.5f );
_hasPlayedDeathSfx = true;
}
}
if ( !Manager.Instance.ShouldUpdatePlayer )
return;
float dt = Time.Delta;
if ( MathF.Abs( Velocity.x ) > 0.01f )
Sprite.SpriteFlags = Velocity.x > 0f ? SpriteFlags.HorizontalFlip : SpriteFlags.None;
bool hurting = TimeSinceHurt < 0.25f;
bool attacking = !IsReloading;
bool moving = Velocity.LengthSquared > 0.01f && InputVector.LengthSquared > 0.1f;
string stateStr = "";
if ( IsDead )
stateStr = "ghost_";
else if ( hurting && attacking )
stateStr = "hurt_attack_";
else if ( hurting )
stateStr = "hurt_";
else if ( attacking )
stateStr = "attack_";
Sprite.PlayAnimation( $"{stateStr}{(moving ? "walk" : "idle")}" );
Sprite.PlaybackSpeed = moving ? Utils.Map( Velocity.Length, 0f, 2f, 1.5f, 2f ) : 0.66f;
Sprite.LocalRotation = new Angles( 0f, -90f + (Velocity.Length * Utils.FastSin( Time.Now * MathF.PI * 6f ) * 1.6f) * (Sprite.SpriteFlags.HasFlag( SpriteFlags.HorizontalFlip ) ? -1f : 1f), 0f );
if ( !IsDead )
{
HandleFlashing( dt );
}
if ( IsProxy )
return;
// ACHIEVEMENTS
if ( Manager.Instance.Difficulty >= 0 )
{
if ( _lastPos2D.x < -15.7f && WorldPosition.x >= -15.7f )
{
_timeSinceTouchLeftSide = 0f;
}
if ( _lastPos2D.x < 15.7f && WorldPosition.x >= 15.7f )
{
Log.Info( $"_timeSinceTouchLeftSide: {_timeSinceTouchLeftSide}" );
if ( _timeSinceTouchLeftSide < 5.5f )
{
if ( !_hasUnlockedSprinterAchievement )
Sandbox.Services.Achievements.Unlock( "sprinter" );
_hasUnlockedSprinterAchievement = true;
}
}
if ( !_hasUnlockedNoMoveAchievement )
{
if ( IsInputtingMove )
{
TimeSinceInputMove = 0f;
}
else if ( TimeSinceInputMove > 60f * 10f )
{
Sandbox.Services.Achievements.Unlock( "stand_ground" );
_hasUnlockedNoMoveAchievement = true;
}
}
}
//Gizmo.Draw.Color = Color.White;
//Gizmo.Draw.Text( $"ArrowAimer: {ArrowAimer}", new global::Transform( (Vector3)Position2D + new Vector3( 0f, -0.7f, 0f ) ) );
//Gizmo.Draw.ScreenText( $"_timeSinceTouchLeftSide: {_timeSinceTouchLeftSide}", new Vector2( 50, 50 ) );
_lastPos2D = Position2D;
var velocity = Velocity + (IsDashing ? DashVelocity : Vector2.Zero);
Position2D += velocity * dt;
if ( Stats[PlayerStat.MoveSelfDmg] > 0f )
{
Stats[PlayerStat.SelfDmgDistanceMoved] += velocity.Length * dt;
if ( !IsInvulnerable && Stats[PlayerStat.SelfDmgDistanceMoved] > Stats[PlayerStat.MoveSelfDmgReqDist] )
{
Stats[PlayerStat.SelfDmgDistanceMoved] -= Stats[PlayerStat.MoveSelfDmgReqDist];
Damage( Stats[PlayerStat.MoveSelfDmgAmount], PlayerDamageType.Self );
Manager.Instance.PlaySfxNearby( "zombie.attack.player", Position2D, pitch: Game.Random.Float( 1.25f, 1.45f ), volume: 0.9f, maxDist: 3f );
}
}
WorldPosition = WorldPosition.WithZ( Globals.GetZPos( Position2D.y ) );
//Velocity = Utils.DynamicEaseTo( Velocity, Vector2.Zero, 0.2f, dt );
Velocity *= Math.Max( 1f - dt * 12.9f, 0f );
if ( Velocity.LengthSquared < 0.001f )
Velocity = Vector2.Zero;
TempWeight *= (1f - dt * 4.7f);
if ( InputVector.LengthSquared > 0f )
{
Velocity += InputVector.Normal * Stats[PlayerStat.MoveSpeed] * BASE_MOVE_SPEED * dt;
//Log.Info( $"dt: {dt}" );
}
HandleBounds();
Manager.Instance.Camera2D.TargetPos = Position2D;
if ( Input.UsingController )
{
// raw stick rather than AnalogLook, which is scaled by look sensitivity and affected by invert-y (SDL stick y is positive downward)
var stick = new Vector2( Input.GetAnalog( InputAnalog.RightStickX ), -Input.GetAnalog( InputAnalog.RightStickY ) );
if ( stick.Length > CONTROLLER_AIM_DEADZONE )
_controllerAimTarget = stick.Normal;
if ( _controllerAimTarget.LengthSquared > 0f )
{
// reverse here rather than when storing the target, so it still flips when ReverseControls toggles while the stick is released
var target = Stats[PlayerStat.ReverseControls] > 0f ? -_controllerAimTarget : _controllerAimTarget;
// rotate by angle, since a linear lerp toward the exact opposite direction never changes direction
// keeps turning after the stick is released, so a quick flick still reaches the target
float currentDegrees = Utils.VectorToDegrees( AimDir );
float deltaDegrees = ((Utils.VectorToDegrees( target ) - currentDegrees + 540f) % 360f) - 180f;
AimDir = Utils.DegreesToVector( currentDegrees + deltaDegrees * MathF.Min( Time.Delta * 25f, 1f ) );
}
}
else
{
AimDir = (Manager.Instance.MouseWorldPos - (Position2D + new Vector2( 0f, 0.5f ))).Normal;
if ( Stats[PlayerStat.ReverseControls] > 0f )
AimDir *= -1f;
// forget the last stick direction, so switching back to the controller keeps the mouse aim instead of swinging back to it
_controllerAimTarget = Vector2.Zero;
}
if ( ArrowAimer != null && !Manager.Instance.IsPauseMenuOpen && !IsTimePausedForChoosing )
{
ArrowAimer.LocalRotation = new Angles( 0f, MathF.Atan2( AimDir.y, AimDir.x ) * (180f / MathF.PI) - 180f, 0f );
ArrowAimer.LocalPosition = new Vector2( 0f, 0.4f ) + AimDir * Utils.Map( _timeSinceShoot, 0f, 0.25f, 0.6f, 0.55f, EasingType.QuadOut );
ArrowAimer.LocalScale = new Vector3( Utils.Map( _timeSinceShoot, 0f, 0.25f, 1.25f, 0.75f, EasingType.QuadOut ), 1f, 1f ) * 0.005f;
ArrowSprite.Tint = Color.White.WithAlpha( Utils.Map( _timeSinceShoot, 0f, 0.3f, 1f, 0.3f, EasingType.QuadOut ) * Utils.Map( _timeSinceSpawn, 0f, 1f, 0f, 1f, EasingType.Linear ) );
}
for ( int dx = -1; dx <= 1; dx++ )
{
for ( int dy = -1; dy <= 1; dy++ )
{
Manager.Instance.HandleThingCollisionForGridSquare( this, new GridSquare( GridPos.x + dx, GridPos.y + dy ), dt );
}
}
if ( !IsDead )
{
HandleDashing( dt );
HandleStatuses( dt );
HandleShooting( dt );
HandleRegen( dt );
}
if ( IsChoosingLevelUpReward && !Manager.Instance.IsPauseMenuOpen )
{
if ( Input.Pressed( "reload" ) ) UseReroll();
else if ( Input.Pressed( "Slot1" ) ) UseChoiceHotkey( 1 );
else if ( Input.Pressed( "Slot2" ) ) UseChoiceHotkey( 2 );
else if ( Input.Pressed( "Slot3" ) ) UseChoiceHotkey( 3 );
else if ( Input.Pressed( "Slot4" ) ) UseChoiceHotkey( 4 );
else if ( Input.Pressed( "Slot5" ) ) UseChoiceHotkey( 5 );
else if ( Input.Pressed( "Slot6" ) ) UseChoiceHotkey( 6 );
}
if ( Input.Pressed( "use" ) )
{
//AddExperience( 10 );
}
}
void HandleRegen( float dt )
{
if ( Math.Abs( Stats[PlayerStat.HealthRegen] ) > 0f )
RegenHealth( Stats[PlayerStat.HealthRegen] * dt );
//if ( Math.Abs( Stats[PlayerStat.HealthDrain] ) > 0f )
// RegenHealth( Stats[PlayerStat.HealthDrain] * dt );
if ( Stats[PlayerStat.HealthRegenStill] > 0f && !IsMoving )
{
RegenHealth( Stats[PlayerStat.HealthRegenStill] * dt );
if ( !IsDashing && Health < Stats[PlayerStat.MaxHp] )
TimeSinceChangeHP = 0f;
}
}
public void RegenHealth( float amount )
{
float maxHp = Stats[PlayerStat.MaxHp];
float hpMissing = maxHp - Health;
if ( amount > 0f )
{
if ( hpMissing <= 0f )
return;
_regenHpAccumulated += amount;
if ( _regenHpAccumulated < 0.85f && hpMissing > _regenHpAccumulated )
return;
float hpRecovered = Math.Min( _regenHpAccumulated, hpMissing );
Health += hpRecovered;
//var particle = DamageNumbersLegacy.Create( hpRecovered, Position2D + new Vector2( 0.2f + Game.Random.Float( -0.1f, 0.1f ), Radius * 3f + Game.Random.Float( -0.2f, 0.3f ) ), color: Color.Green, sizeMultiplier: 0.8f );
//Vector3 velocity = new Vector3( Game.Random.Float(-0.5f, 0.5f), 0f, 0f );
//Vector3 gravity = new Vector3( 0f, 1.5f, 0f );
//particle.SetVector( 1, velocity );
//particle.SetNamedValue( "Gravity", gravity );
var pos = Position2D + new Vector2( Game.Random.Float( -0.1f, 0.1f ), Radius * 3f + Game.Random.Float( -0.2f, 0.3f ) );
float size = 1.1f;
Manager.Instance.SpawnDamageNumber( pos, amount, Color.Green, size, FloaterType.Heal );
_regenHpAccumulated = 0f;
}
else
{
_drainHpAccumulated += amount;
if ( _drainHpAccumulated < -1f )
{
var dmgAmount = MathF.Truncate( _drainHpAccumulated );
_drainHpAccumulated -= dmgAmount;
Damage( MathF.Abs( dmgAmount ), PlayerDamageType.Self );
Manager.Instance.PlaySfxNearby( "lava_puddle_03", Position2D, pitch: Game.Random.Float( 1.7f, 1.75f ), volume: 0.15f, maxDist: 4f );
}
}
}
void HandleDashing( float dt )
{
int numDashes = (int)MathF.Round( Stats[PlayerStat.NumDashes] );
if ( NumDashesAvailable < numDashes )
{
DashTimer -= dt;
DashRechargeProgress = Utils.Map( DashTimer, Stats[PlayerStat.DashCooldown], 0f, 0f, 1f );
if ( DashTimer <= 0f )
{
DashRecharged();
}
}
if ( DashInvulnTimer > 0f )
{
DashInvulnTimer -= dt;
DashProgress = Utils.Map( DashInvulnTimer, Stats[PlayerStat.DashInvulnTime], 0f, 0f, 1f );
if ( DashInvulnTimer <= 0f )
{
IsDashing = false;
//Sprite.Tint = Color.White;
Sprite.FlashTint = Color.White.WithAlpha( 0f );
DashFinished();
}
else
{
if ( Stats[PlayerStat.DashCharm] > 0f )
{
if ( IsInvulnerable )
Sprite.FlashTint = new Color( Game.Random.Float( 0.8f, 1f ), Game.Random.Float( 0f, 0.1f ), Game.Random.Float( 0.8f, 1f ), 0.9f );
else if ( !_isFlashing )
Sprite.FlashTint = new Color( Game.Random.Float( 0.9f, 1f ), Game.Random.Float( 0.1f, 0.3f ), Game.Random.Float( 0.9f, 1f ), 0.8f );
}
else
{
if ( IsInvulnerable )
Sprite.FlashTint = new Color( Game.Random.Float( 0f, 0.25f ), Game.Random.Float( 0f, 0.25f ), Game.Random.Float( 0.8f, 1f ), 0.9f );
}
if ( _dashCloudTime > Game.Random.Float( 0.1f, 0.2f ) )
{
SpawnDashCloudClient();
_dashCloudTime = 0f;
}
}
}
bool pressedDash = Input.Pressed( "Dash" ) && !Gamepad.IsAClickingUi;
if ( (pressedDash ||Input.Pressed( "RB" )) && !Manager.Instance.IsPauseMenuOpen )
{
//Position2D = Manager.Instance.MouseWorldPos;
////Manager.Instance.Camera2D.SetPos( Position2D );
//Transform.ClearInterpolation();
//return;
Dash();
}
}
public void Dash()
{
if ( NumDashesAvailable <= 0 || IsTimePausedForChoosing )
return;
Vector2 dashDir = Velocity.LengthSquared > 0f ? Velocity.Normal : AimDir;
if ( Game.Random.Float( 0f, 1f ) < Stats[PlayerStat.DashRandomDirChance] )
dashDir = Utils.GetRandomVector();
DashVelocity = dashDir * Stats[PlayerStat.DashStrength];
TempWeight = 2f;
if ( NumDashesAvailable == (int)Stats[PlayerStat.NumDashes] )
DashTimer = Stats[PlayerStat.DashCooldown];
NumDashesAvailable--;
IsDashing = true;
DashInvulnTimer = Stats[PlayerStat.DashInvulnTime];
DashProgress = 0f;
DashRechargeProgress = 0f;
Manager.Instance.PlaySfxNearby( "player.dash", Position2D + dashDir * 0.5f, pitch: Utils.Map( NumDashesAvailable, 0, 5, 1f, 0.9f ), volume: 1f, maxDist: 4f );
SpawnDashCloudClient();
_dashCloudTime = 0f;
ForEachStatus( status => status.OnDashStarted() );
}
public void DashFinished()
{
ForEachStatus( status => status.OnDashFinished() );
}
public void DashRecharged()
{
NumDashesAvailable++;
var numDashes = (int)MathF.Round( Stats[PlayerStat.NumDashes] );
if ( NumDashesAvailable > numDashes )
NumDashesAvailable = numDashes;
if ( NumDashesAvailable < numDashes )
{
DashTimer = Stats[PlayerStat.DashCooldown];
DashRechargeProgress = 0f;
}
else
{
DashRechargeProgress = 1f;
}
ForEachStatus( status => status.OnDashRecharged() );
Manager.Instance.PlaySfxNearby( "player.dash.recharge", Position2D, pitch: Utils.Map( NumDashesAvailable, 1, numDashes, 1f, 1.2f ), volume: 0.2f, maxDist: 5f );
}
void HandleBounds()
{
var x_min = Manager.Instance.BOUNDS_MIN.x + Radius;
var x_max = Manager.Instance.BOUNDS_MAX.x - Radius;
var y_min = Manager.Instance.BOUNDS_MIN.y;
var y_max = Manager.Instance.BOUNDS_MAX.y - Radius;
if ( Position2D.x < x_min )
Position2D = new Vector2( x_min, Position2D.y );
else if ( Position2D.x > x_max )
Position2D = new Vector2( x_max, Position2D.y );
if ( Position2D.y < y_min )
Position2D = new Vector2( Position2D.x, y_min );
else if ( Position2D.y > y_max )
Position2D = new Vector2( Position2D.x, y_max );
}
public int GetExperienceReqForLevel( int level )
{
switch ( Manager.Instance.Difficulty )
{
case -1:
return (int)MathF.Round( Utils.Map( level, 1, 150, 3f, 240f, EasingType.SineIn ) );
case 0:
default:
return (int)MathF.Round( Utils.Map( level, 1, 150, 3f, 320f, EasingType.SineIn ) );
}
}
public void Flash( float time )
{
if ( _isFlashing )
return;
//Sprite.Tint = new Color( 1f, 0f, 0f );
Sprite.FlashTint = new Color( 1f, 0f, 0f, 1f );
_isFlashing = true;
_flashTimer = time;
}
public void Heal( float amount, float flashTime )
{
//Sprite.Tint = new Color( 0f, 1f, 0f );
Sprite.FlashTint = new Color( 0f, 1f, 0f, 1f );
_isFlashing = true;
_flashTimer = flashTime;
if ( IsProxy )
return;
if ( Health < Stats[PlayerStat.MaxHp] )
TimeSinceChangeHP = 0f;
Health += amount;
if ( Health > Stats[PlayerStat.MaxHp] )
Health = Stats[PlayerStat.MaxHp];
}
void HandleFlashing( float dt )
{
if ( _isFlashing )
{
_flashTimer -= dt;
if ( _flashTimer < 0f )
{
_isFlashing = false;
//Sprite.Tint = Color.White;
Sprite.FlashTint = Color.White.WithAlpha( 0f );
}
}
}
[ConCmd( "give_status" )]
public static void GiveStatus( string name )
{
// Cheat only works in the editor
if ( !Game.IsEditor )
return;
var type = TypeLibrary.GetType( name );
if ( type == null )
{
Log.Info( $"No status with name '{name}' found!" );
return;
}
Manager.Instance?.GetLocalPlayer()?.AddStatus( type );
}
public void AddStatus( TypeDescription type )
{
Status status = null;
var typeIdentity = type.Identity;
ForEachStatus( status => status.OnAddStatus( typeIdentity ) );
if ( Statuses.ContainsKey( typeIdentity ) )
{
status = Statuses[typeIdentity];
status.Level++;
}
if ( status == null )
{
status = StatusManager.CreateStatus( type );
Statuses.Add( typeIdentity, status );
status.Init( this );
}
//Sandbox.Services.Stats.Increment( Client, "status", 1, $"{type.Name.ToLowerInvariant()}", new { Status = type.Name.ToLowerInvariant(), Level = status.Level } );
status.Refresh();
Manager.Instance.PlaySfxNearby( "click", Position2D, 0.9f, 0.75f, 5f );
LevelUpChoices.Clear();
IsChoosingLevelUpReward = false;
RealTimeSinceChoseUpgrade = 0f;
CheckForLevelUp();
}
public bool HasStatus( TypeDescription type )
{
return Statuses.ContainsKey( type.Identity );
}
public Status GetStatus( TypeDescription type )
{
if ( Statuses.ContainsKey( type.Identity ) )
return Statuses[type.Identity];
return null;
}
public int GetStatusLevel( TypeDescription type )
{
if ( Statuses.ContainsKey( type.Identity ) )
return Statuses[type.Identity].Level;
return 0;
}
public void Modify( Status caller, PlayerStat statType, float value, ModifierType type, float priority = 0f, bool update = true )
{
if ( !_modifiers_stat.ContainsKey( caller ) )
_modifiers_stat.Add( caller, new Dictionary<PlayerStat, ModifierData>() );
_modifiers_stat[caller][statType] = new ModifierData( value, type, priority );
if ( update )
UpdateProperty( statType );
}
public void AdjustBaseStat( PlayerStat statType, float amount, bool update = true )
{
if ( !_original_properties_stat.ContainsKey( statType ) )
_original_properties_stat.Add( statType, Stats[statType] );
_original_properties_stat[statType] += amount;
if ( update )
UpdateProperty( statType );
}
void UpdateProperty( PlayerStat statType )
{
if ( !_original_properties_stat.ContainsKey( statType ) )
{
_original_properties_stat.Add( statType, Stats[statType] );
}
float curr_value = _original_properties_stat[statType];
float curr_set = curr_value;
bool should_set = false;
float curr_priority = 0f;
float total_add = 0f;
float total_mult = 1f;
foreach ( Status caller in _modifiers_stat.Keys )
{
var dict = _modifiers_stat[caller];
if ( dict.ContainsKey( statType ) )
{
var mod_data = dict[statType];
switch ( mod_data.type )
{
case ModifierType.Set:
if ( mod_data.priority >= curr_priority )
{
curr_set = mod_data.value;
curr_priority = mod_data.priority;
should_set = true;
}
break;
case ModifierType.Add:
total_add += mod_data.value;
break;
case ModifierType.Mult:
total_mult *= mod_data.value;
break;
}
}
}
if ( should_set )
curr_value = curr_set;
curr_value += total_add;
curr_value *= total_mult;
Stats[statType] = curr_value;
if ( statType == PlayerStat.MaxHp )
Stats[statType] = Math.Max( curr_value, 1f );
}
public void AddExperience( int xp )
{
ExperienceTotal += xp;
ExperienceCurrent += xp;
//var particle = DamageNumbersLegacy.Create( xp, Position2D + new Vector2( 0.2f + Game.Random.Float( -0.1f, 0.1f ), Radius * 3f + Game.Random.Float( -0.2f, 0.3f ) ), color: new Color(0.1f, 0.1f, 1f), sizeMultiplier: 0.8f );
//Vector3 velocity = new Vector3( 0f, 0f, 0f );
//Vector3 gravity = new Vector3( 0f, 1f, 0f );
//particle.SetVector( 1, velocity );
//particle.SetNamedValue( "Gravity", gravity );
var pos = Position2D + new Vector2( Game.Random.Float( -0.1f, 0.1f ), Radius * 3f + Game.Random.Float( -0.2f, 0.3f ) );
float size = Utils.Map( xp, 1f, 4f, 0.95f, 1.1f, EasingType.Linear );
var color = new Color( 0.4f, 0.4f, 1f );
Manager.Instance.SpawnDamageNumber( pos, xp, color, size, FloaterType.Xp );
ForEachStatus( status => status.OnGainExperience( xp ) );
if ( !IsChoosingLevelUpReward )
CheckForLevelUp();
}
public void LoseExperience( int amount )
{
ExperienceCurrent = Math.Max( ExperienceCurrent - amount, 0 );
}
public void CheckForLevelUp()
{
//Log.Info("CheckForLevelUp: " + ExperienceCurrent + " / " + ExperienceRequired + " IsServer: " + Sandbox.Game.IsServer + " Level: " + Level);
if ( ExperienceCurrent >= ExperienceRequired && Manager.Instance.ShouldUpdatePlayer )
LevelUp();
}
public void LevelUp()
{
ExperienceCurrent -= ExperienceRequired;
Level++;
ExperienceRequired = GetExperienceReqForLevel( Level + 1 );
if ( Manager.Instance.Difficulty < 3 )
NumRerollAvailable += (int)Stats[PlayerStat.NumRerollsPerLevel];
Manager.Instance.PlaySfxNearby( "levelup", Position2D, Game.Random.Float( 0.95f, 1.05f ), 0.5f, 5f );
ForEachStatus( status => status.OnLevelUp() );
GenerateLevelUpChoices();
IsChoosingLevelUpReward = true;
TimeSinceLevelUp = 0f;
if ( Manager.Instance.Difficulty >= 0 && !_hasUnlockedExperiencedAchievement && Level >= 85 )
{
Sandbox.Services.Achievements.Unlock( "experienced" );
_hasUnlockedExperiencedAchievement = true;
}
}
public void UseReroll()
{
if ( NumRerollAvailable <= 0 )
{
// todo: sfx
return;
}
Manager.Instance.PlaySfxNearby( "reroll", Position2D, Utils.Map( NumRerollAvailable, 0, 20, 0.9f, 1.4f, EasingType.QuadIn ), 0.6f, 5f );
NumRerollAvailable--;
GenerateLevelUpChoices();
ForEachStatus( status => status.OnReroll() );
}
/// <summary>
/// Number of level up choices that have a hotkey (Slot1..Slot6 actions).
/// </summary>
public const int NUM_CHOICE_HOTKEYS = 6;
public void UseChoiceHotkey( int num )
{
var index = num - 1;
if ( !IsChoosingLevelUpReward || index >= LevelUpChoices.Count )
return;
AddStatus( TypeLibrary.GetType( LevelUpChoices[index].GetType() ) );
}
public float CheckDamageAmount( float damage, DamageType damageType )
{
if ( IsInvulnerable )
{
return 0f;
}
if ( HasStatus( TypeLibrary.GetType( typeof( ShieldStatus ) ) ) && damageType != DamageType.LavaPuddle )
{
var shieldStatus = GetStatus( TypeLibrary.GetType( typeof( ShieldStatus ) ) ) as ShieldStatus;
if ( shieldStatus != null && shieldStatus.IsShielded )
{
shieldStatus.LoseShield();
return 0f;
}
}
if ( Stats[PlayerStat.DamageReductionPercent] > 0f )
damage *= (1f - MathX.Clamp( Stats[PlayerStat.DamageReductionPercent], 0f, 1f ));
if ( Stats[PlayerStat.IncreasedDmgTaken] > 0f )
damage *= (1f + MathX.Clamp( Stats[PlayerStat.IncreasedDmgTaken], 0f, 1f ));
if ( damageType == DamageType.Explosion && Stats[PlayerStat.ExplosionDamageReductionPercent] > 0f )
damage *= (1f - MathX.Clamp( Stats[PlayerStat.ExplosionDamageReductionPercent], 0f, 1f ));
if ( damageType != DamageType.Explosion && Stats[PlayerStat.NonExplosionDamageIncreasePercent] > 0f )
damage *= (1f + Stats[PlayerStat.NonExplosionDamageIncreasePercent]);
if ( Manager.Instance.Difficulty < 0 )
damage *= 0.571429f; // so zombie's 7 dmg becomes 4 dmg
return damage;
}
public void Damage( float damage, PlayerDamageType playerDamageType = PlayerDamageType.Enemy )
{
if ( !Manager.Instance.ShouldUpdatePlayer )
return;
TimeSinceHurt = 0f;
bool isCrit = Game.Random.Float( 0f, 1f ) < Stats[PlayerStat.SelfCritChance];
if ( isCrit )
damage *= 2f;
if ( damage > 0f )
{
TimeSinceChangeHP = 0f;
Flash( 0.125f );
}
var offset = new Vector2(
Game.Random.Float( -0.1f, 0.1f ),
Radius * 3f + Game.Random.Float( -0.2f, 0.3f ) + (Health - damage < 0f ? -0.7f : 0f)
);
//DamageNumbers.Add( (int)damage, Position2D + Vector2.Up * Radius * 3f + new Vector2( Game.Random.Float( -1f, 1f ), Game.Random.Float( -1f, 1f ) ) * 0.2f, color: Color.Red );
//DamageNumbersLegacy.Create( damage, Position2D + offset, color: isCrit ? Color.Orange : Color.Red );
var pos = Position2D + offset;
float size;
if ( damage < 5f ) size = Utils.Map( damage, 1f, 5f, 1.1f, 1.25f, EasingType.QuadOut );
else if ( damage < 20f ) size = Utils.Map( damage, 5f, 20f, 1.25f, 1.6f, EasingType.Linear );
else size = Utils.Map( damage, 20f, 100f, 1.6f, 1.8f, EasingType.Linear );
var color = isCrit ? Color.Orange : Color.Red;
Manager.Instance.SpawnDamageNumber( pos, damage, color, size );
ForEachStatus( status => status.OnHurt( damage ) );
Health -= damage;
//if ( damage > 3f )
// ShakeCam( Utils.Map( damage, 3f, 25f, 0f, 0.1f ), Utils.Map( damage, 3f, 20f, 0.1f, 0.25f ), EasingType.QuadOut );
if ( Health <= 0f )
{
Die();
SpawnBlood( damage, sizeMultiplier: Game.Random.Float( 2.5f, 3f ), playbackSpeed: Game.Random.Float( 20f, 25f ), shouldUseRealTime: true );
}
else
{
SpawnBlood( damage );
}
}
public void AddVelocity( Vector2 vel )
{
if ( !Manager.Instance.ShouldUpdatePlayer )
return;
Velocity += vel;
}
public void SpawnBlood( float damage, float sizeMultiplier = 1f, float playbackSpeed = 0f, bool shouldUseRealTime = false )
{
var blood = Manager.Instance.SpawnBloodSplatter( Position2D );
if ( blood != null )
{
blood.LocalScale *= Utils.Map( damage, 1f, 20f, 0.5f, 1.2f, EasingType.QuadIn ) * Game.Random.Float( 0.8f, 1.2f ) * sizeMultiplier;
blood.Lifetime *= 0.3f;
blood.ShouldUseRealTime = shouldUseRealTime;
if ( playbackSpeed > 0f )
blood.Sprite.PlaybackSpeed = playbackSpeed;
}
}
public void Die()
{
if ( IsDead )
return;
IsDead = true;
_hasPlayedDeathSfx = false;
Sprite.Tint = new Color( 1f, 1f, 1f, 1f );
Sprite.FlashTint = new Color( 1f, 1f, 1f, 0f );
//ShadowOpacity = 0.2f;
_isFlashing = false;
IsReloading = false;
RealTimeSinceDeath = 0f;
_arrowDeathAlphaStart = ArrowSprite.Tint.a;
var pitch = Game.Random.Float( 1.25f, 1.3f ) * (Manager.Instance.Difficulty < 0 ? 2f : 1f);
Manager.Instance.PlaySfxNearby( "die", Position2D, pitch, volume: 1.5f, maxDist: 12f );
Sprite.LocalScale *= 2f;
Sprite.PlayAnimation( $"death" );
//Sprite.PlayAnimation( "ghost_idle" );
ShakeCam( Game.Random.Float( 0.025f, 0.065f ), Game.Random.Float( 0.3f, 0.5f ), EasingType.SineOut, useRealTime: true );
if ( IsProxy )
return;
Manager.Instance.PlayerDied( this );
}
public void Revive()
{
if ( !IsDead )
return;
IsDead = false;
IsChoosingLevelUpReward = false;
IsDashing = false;
IsReloading = true;
Sprite.Tint = Color.White;
ShadowOpacity = 0.8f;
if ( IsProxy )
return;
Timer = Stats[PlayerStat.ReloadTime];
ReloadProgress = 0f;
DashProgress = 0f;
ExperienceCurrent = 0;
Health = Stats[PlayerStat.MaxHp] * 0.33f;
}
public void ForEachStatus( Action<Status> action )
{
if ( IsProxy )
return;
foreach ( var (_, status) in Statuses )
{
action( status );
}
}
void HandleStatuses( float dt )
{
foreach ( KeyValuePair<int, Status> pair in Statuses )
{
Status status = pair.Value;
if ( status.ShouldUpdate )
status.Update( dt );
}
}
void HandleShooting( float dt )
{
if ( IsReloading )
{
ReloadProgress = Utils.Map( Timer, Stats[PlayerStat.ReloadTime], 0f, 0f, 1f );
Timer -= dt * Stats[PlayerStat.ReloadSpeed];
if ( Timer <= 0f )
{
Reload();
}
}
else
{
Timer -= dt * Stats[PlayerStat.AttackSpeed] * (IsMoving ? 1f : Stats[PlayerStat.AttackSpeedStill]);
if ( Timer <= 0f )
{
Shoot( isLastAmmo: AmmoCount == 1 );
AmmoCount--;
if ( AmmoCount <= 0 )
{
IsReloading = true;
Timer += Stats[PlayerStat.ReloadTime];
}
else
{
Timer += Stats[PlayerStat.AttackTime];
}
}
}
}
public void Shoot( bool isLastAmmo = false )
{
int num_bullets_int = (int)Stats[PlayerStat.NumProjectiles];
var aimDir = Game.Random.Float( 0f, 1f ) < Stats[PlayerStat.ShootRandomDirChance]
? Utils.GetRandomVector()
: AimDir;
var pos = Position2D + AimDir * 0.3f;
if ( Stats[PlayerStat.MaxBulletSpread] > 0f )
{
float increment = 360f / num_bullets_int;
for ( int i = 0; i < num_bullets_int; i++ )
{
var dir = Utils.RotateVector( aimDir, i * increment );
SpawnBullet( pos, dir, isLastAmmo );
}
}
else
{
float start_angle = MathF.Sin( -_shotNum * 2f ) * Stats[PlayerStat.BulletInaccuracy];
var spread = Stats[PlayerStat.BulletSpread] * num_bullets_int;
float currAngleOffset = num_bullets_int == 1 ? 0f : -spread * 0.5f;
float increment = num_bullets_int == 1 ? 0f : spread / (float)(num_bullets_int - 1);
for ( int i = 0; i < num_bullets_int; i++ )
{
var dir = Utils.RotateVector( aimDir, start_angle + currAngleOffset + increment * i );
SpawnBullet( pos, dir, isLastAmmo );
}
}
Manager.Instance.PlaySfxNearby( "shoot", pos, pitch: Utils.Map( _shotNum, 0f, (float)Stats[PlayerStat.MaxAmmoCount], 1f, 1.25f ), volume: 1f, maxDist: 4f );
Velocity -= aimDir * Stats[PlayerStat.Recoil];
_shotNum++;
_timeSinceShoot = 0f;
}
void SpawnBullet( Vector2 pos, Vector2 dir, bool isLastAmmo = false, float damageMult = 1f )
{
var damage = (Stats[PlayerStat.BulletDamage] * Stats[PlayerStat.BulletDamageMultiplier] + Stats[PlayerStat.BulletFlatDamageAddition]) * GetDamageMultiplier() * damageMult;
if ( isLastAmmo )
damage *= Stats[PlayerStat.LastAmmoDamageMultiplier];
if ( Stats[PlayerStat.DamagePerEarlierShot] > 0f )
damage += _shotNum * Stats[PlayerStat.DamagePerEarlierShot];
if ( Stats[PlayerStat.DamageForSpeed] > 0f )
{
damage += Stats[PlayerStat.DamageForSpeed] * Velocity.Length;
if ( IsDashing )
damage += Stats[PlayerStat.DamageForSpeed] * DashVelocity.Length;
}
var bulletObj = BulletPrefab.Clone( (Vector3)pos );
var bullet = bulletObj.Components.Get<Bullet>();
bullet.Velocity = dir * Stats[PlayerStat.BulletSpeed];
bullet.Shooter = this;
bullet.TempWeight = 3f;
bullet.Stats[BulletStat.Damage] = damage;
bullet.Stats[BulletStat.Force] = Stats[PlayerStat.BulletForce];
bullet.Stats[BulletStat.Lifetime] = Stats[PlayerStat.BulletLifetime];
bullet.Stats[BulletStat.NumPiercing] = (int)MathF.Round( Stats[PlayerStat.BulletNumPiercing] );
bullet.Stats[BulletStat.NumBouncing] = (int)MathF.Round( Stats[PlayerStat.BulletNumBouncing] );
bullet.Stats[BulletStat.WillIgnite] = Game.Random.Float( 0f, 1f ) < Stats[PlayerStat.ShootFireIgniteChance] ? 1f : 0f;
bullet.Stats[BulletStat.WillFreeze] = Game.Random.Float( 0f, 1f ) < Stats[PlayerStat.ShootFreezeChance] ? 1f : 0f;
bullet.Stats[BulletStat.GrowDamageAmount] = Stats[PlayerStat.BulletDamageGrow];
bullet.Stats[BulletStat.ShrinkDamageAmount] = Stats[PlayerStat.BulletDamageShrink];
bullet.Stats[BulletStat.DistanceDamageAmount] = Stats[PlayerStat.BulletDistanceDamage];
bullet.Stats[BulletStat.HealTeammateAmount] = Stats[PlayerStat.BulletHealTeammateAmount];
bullet.IsHoming = Game.Random.Float( 0f, 1f ) < Stats[PlayerStat.HomingBulletChance];
if ( Stats[PlayerStat.GrenadesCanCrit] <= 0f )
{
bullet.Stats[BulletStat.CriticalChance] = Stats[PlayerStat.CritChance];
bullet.Stats[BulletStat.CriticalMultiplier] = Stats[PlayerStat.CritMultiplier];
}
bullet.Init();
//bullet.GameObject.NetworkSpawn( Network.Owner );
}
void Reload()
{
AmmoCount = (int)Stats[PlayerStat.MaxAmmoCount];
IsReloading = false;
_shotNum = 0;
ReloadProgress = 0f;
ForEachStatus( status => status.OnReload() );
}
public float GetDamageMultiplier()
{
float damageMultiplier = Stats[PlayerStat.OverallDamageMultiplier];
if ( Stats[PlayerStat.LowHealthDamageMultiplier] > 1f )
damageMultiplier *= Utils.Map( Health, Stats[PlayerStat.MaxHp], 0f, 1f, Stats[PlayerStat.LowHealthDamageMultiplier] );
if ( Stats[PlayerStat.FullHealthDamageMultiplier] > 1f && !(Health < Stats[PlayerStat.MaxHp]) )
damageMultiplier *= Stats[PlayerStat.FullHealthDamageMultiplier];
return damageMultiplier;
}
public override void Colliding( Thing other, float percent, float dt )
{
base.Colliding( other, percent, dt );
if ( IsDead )
return;
ForEachStatus( status => status.Colliding( other, percent, dt ) );
if ( other is Enemy enemy && !enemy.IsDying )
{
if ( !Position2D.Equals( other.Position2D ) )
{
var spawnFactor = Utils.Map( enemy.TimeSinceSpawn, 0f, enemy.SpawnTime, 0f, 1f, EasingType.QuadIn );
Velocity += (Position2D - other.Position2D).Normal * Utils.Map( percent, 0f, 1f, 0f, 100f ) * (1f + other.TempWeight) * spawnFactor * dt;
}
}
else if ( other is Player player )
{
if ( !player.IsDead && !Position2D.Equals( other.Position2D ) )
{
Velocity += (Position2D - other.Position2D).Normal * Utils.Map( percent, 0f, 1f, 0f, 100f ) * (1f + other.TempWeight) * dt;
}
}
}
public void SpawnDashCloudClient()
{
Manager.Instance.SpawnCloud( Position2D + new Vector2( Game.Random.Float( -1f, 1f ), Game.Random.Float( -1f, 1f ) ) * 0.05f );
}
public void GenerateLevelUpChoices()
{
LevelUpChoices.Clear();
//if( Level == 1)
//{
// LevelUpChoices.Add( CreateStatus( TypeLibrary.GetType( typeof( PauseWhileChoosingStatus ) ) ) );
// LevelUpChoices.Add( CreateStatus( TypeLibrary.GetType( typeof( MysteryBoxStatus ) ) ) );
// LevelUpChoices.Add( CreateStatus( GetRandomStartingPerk() ) );
// LevelUpChoices.Shuffle();
// return;
//}
bool offerCurses = false;
if ( Manager.Instance.Difficulty >= 6 )
offerCurses = IsLevelCursed( Level );
int numChoices = Math.Clamp( (int)MathF.Round( Stats[PlayerStat.NumUpgradeChoices] ), 1, 6 );
List<TypeDescription> statusTypes = StatusManager.GetRandomStatuses( this, numChoices, offerCurses );
for ( int i = 0; i < statusTypes.Count; i++ )
LevelUpChoices.Add( CreateStatus( statusTypes[i] ) );
if ( Level == 1 )
{
bool alreadyOffered = false;
foreach ( var status in LevelUpChoices )
{
if ( status is PauseWhileChoosingStatus )
{
alreadyOffered = true;
break;
}
}
if ( !alreadyOffered )
{
LevelUpChoices.RemoveAt( 1 );
LevelUpChoices.Add( CreateStatus( TypeLibrary.GetType( typeof( PauseWhileChoosingStatus ) ) ) );
LevelUpChoices.Shuffle();
}
}
ChoiceHash++;
}
public bool IsLevelCursed( int level )
{
if ( Manager.Instance.Difficulty < 6 || level <= 1 )
return false;
switch ( Manager.Instance.Difficulty )
{
case 6: return level % 10 == 0;
case 7: return level % 9 == 0;
case 8: return level % 8 == 0;
case 9: return level % 7 == 0;
case 10: return level % 6 == 0;
case 11: return level % 5 == 0;
case 12: return level % 4 == 0;
case 13: return level % 3 == 0;
case 14: return level % 2 == 0;
case 15: return level % 3 != 1;
}
return false;
}
TypeDescription GetRandomStartingPerk()
{
List<(TypeDescription Type, float Weight)> perks = new List<(TypeDescription, float)>
{
(TypeLibrary.GetType( typeof( DamageStatus ) ), 5f),
(TypeLibrary.GetType( typeof( MovespeedStatus ) ), 3f),
(TypeLibrary.GetType( typeof( AttackSpeedStatus ) ), 2f),
(TypeLibrary.GetType( typeof( NumProjectileStatus ) ), 2f),
(TypeLibrary.GetType( typeof( PiercingStatus ) ), 3f),
(TypeLibrary.GetType( typeof( GrenadeShootReloadStatus ) ), 1f),
(TypeLibrary.GetType( typeof( NumDashesStatus ) ), 2f),
(TypeLibrary.GetType( typeof( FireIgniteStatus ) ), 2f),
(TypeLibrary.GetType( typeof( FreezeShootStatus ) ), 2f),
(TypeLibrary.GetType( typeof( FullHealthDamageStatus ) ), 2f),
(TypeLibrary.GetType( typeof( MoreRerollsStatus ) ), 4f),
(TypeLibrary.GetType( typeof( MoreChoicesStatus ) ), 3f),
(TypeLibrary.GetType( typeof( ReloadSpeedStatus ) ), 1f),
(TypeLibrary.GetType( typeof( XpDamageStatus ) ), 1f),
(TypeLibrary.GetType( typeof( HomingBulletStatus ) ), 4f),
(TypeLibrary.GetType( typeof( ThornsStatus ) ), 1f),
(TypeLibrary.GetType( typeof( BouncingBulletStatus ) ), 3f),
};
TypeDescription chosenPerk = null;
float totalWeight = perks.Sum( x => x.Weight );
var rand = Game.Random.Float( 0f, totalWeight );
for ( int i = perks.Count - 1; i >= 0; i-- )
{
var (type, weight) = perks[i];
rand -= weight;
if ( rand < 0f )
{
chosenPerk = type;
break;
}
}
return chosenPerk;
}
Status CreateStatus( TypeDescription type )
{
var status = StatusManager.CreateStatus( type );
var currLevel = GetStatusLevel( type );
status.Level = currLevel + 1;
return status;
}
public void Restart()
{
Sprite.PlayAnimation( "idle" );
Sprite.PlaybackSpeed = 0.66f;
Sprite.Tint = new Color( 1f, 1f, 1f, 1f );
Sprite.LocalScale = new Vector3( Globals.SPRITE_SCALE );
if ( IsProxy )
return;
Position2D = new Vector3( Game.Random.Float( -3f, 3f ), Game.Random.Float( -3f, 3f ) );
Manager.Instance.Camera2D.SetPos( Position2D );
InitializeStats();
Manager.Instance.PlaySfxNearby( "restart", Position2D, Game.Random.Float( 0.95f, 1.05f ), 0.66f, 4f );
}
public void SpawnBulletRing( Vector2 pos, int numBullets, Vector2 aimDir, float damageMultMin = 1f, float damageMultMax = 1f )
{
float increment = 360f / numBullets;
for ( int i = 0; i < numBullets; i++ )
{
var dir = Utils.RotateVector( aimDir, i * increment );
float damageMult = Game.Random.Float( damageMultMin, damageMultMax );
SpawnBullet( pos, dir, false, damageMult );
}
Manager.Instance.PlaySfxNearby( "shoot", pos, pitch: 1f, volume: 1f, maxDist: 3f );
}
public Grenade SpawnGrenade( Vector2 pos, Vector2 vel )
{
var grenadeObj = Manager.Instance.GrenadePrefab.Clone();
var grenade = grenadeObj.Components.Get<Grenade>();
grenade.Velocity = vel;
grenade.ExplosionSizeMultiplier = Stats[PlayerStat.ExplosionSizeMultiplier];
grenade.Player = this;
grenade.StickyPercent = Stats[PlayerStat.GrenadeStickyPercent];
grenade.FearChance = Stats[PlayerStat.GrenadeFearChance];
if ( Stats[PlayerStat.GrenadesCanCrit] > 0f )
{
grenade.CriticalChance = Stats[PlayerStat.CritChance];
grenade.CriticalMultiplier = Stats[PlayerStat.CritMultiplier];
}
//grenadeObj.NetworkSpawn( Network.Owner );
grenadeObj.WorldPosition = new Vector3( pos.x, pos.y, Globals.GetZPos( pos.y ) );
Manager.Instance.AddThing( grenade );
return grenade;
}
public void CreateShieldVfx()
{
_shieldVfx = Manager.Instance.ShieldVfxPrefab.Clone( WorldPosition );
_shieldVfx.Parent = GameObject;
_shieldVfx.LocalPosition = new Vector3( 0f, 0f, 0.1f );
_shieldVfx.LocalScale = new Vector3( 1f ) * 1.8f * Globals.SPRITE_SCALE;
_shieldVfx.LocalRotation = new Angles( 0f, -90f, 0f );
}
public void RemoveShieldVfx()
{
if ( _shieldVfx != null )
{
_shieldVfx.Destroy();
_shieldVfx = null;
}
}
public void PlaySfx( string name, Vector2 pos, float pitch, float volume )
{
var sfx = Sound.Play( name, new Vector3( pos.x, pos.y, Globals.SFX_DEPTH ) );
if ( sfx != null )
{
sfx.Volume = volume;
sfx.Pitch = pitch;
}
}
protected override void OnStart()
{
base.OnStart();
Manager.Instance.AddPlayer( this );
}
protected override void OnDestroy()
{
base.OnDestroy();
Manager.Instance.RemovePlayer( this );
}
void HandleCamShaking()
{
CamShakeAmount = 0f;
for ( int i = _camShakeDatas.Count - 1; i >= 0; i-- )
{
var data = _camShakeDatas[i];
var time = data.useRealTime ? RealTime.Now : Time.Now;
if ( time > data.startTime + data.time )
{
_camShakeDatas.RemoveAt( i );
}
else
{
float amount = Utils.Map( time, data.startTime, data.startTime + data.time, data.strength, 0f, data.easingType );
CamShakeAmount = MathF.Max( amount, CamShakeAmount );
}
}
}
public void ShakeCam( float strength, float time, EasingType easingType = EasingType.Linear, bool useRealTime = false )
{
var timeNow = useRealTime ? RealTime.Now : Time.Now;
_camShakeDatas.Add( new CamShakeData( strength, timeNow, time, easingType, useRealTime ) );
}
}
Debug: View Raw JSON Response
{
"TotalCount": 222,
"Files": [
{
"Ident": "facepunch.ss1",
"Path": "ui/panels/infopanel.razor.scss",
"FileName": "infopanel.razor.scss",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 421898,
"IsPrivate": false,
"Code": "InfoPanel {\r\n position: absolute;\r\n width: 280px;\r\n left: 20px;\r\n bottom: 20px;\r\n top: 20px;\r\n align-self: flex-end;\r\n pointer-events: none;\r\n justify-content: flex-start;\r\n flex-direction: column-reverse;\r\n gap: 10px;\r\n //background-color: red;\r\n\r\n .info_dash_container {\r\n width: 100%;\r\n //flex-direction: column-reverse;\r\n flex-direction: row;\r\n align-items: center;\r\n gap: 8px;\r\n }\r\n\r\n .dash {\r\n position: relative;\r\n flex-shrink: 1;\r\n width: 100%;\r\n height: 28px;\r\n background-color: #0005;\r\n }\r\n\r\n .info_bar {\r\n position: relative;\r\n width: 100%;\r\n overflow: hidden;\r\n height: 28px;\r\n background-color: #00000099;\r\n //border-left: 2px solid #fff;\r\n //border-right: 2px solid #fff;\r\n flex-shrink: 1;\r\n //border: 2px solid black;\r\n\r\n .info_bar_overlay {\r\n position: absolute;\r\n left: 0px;\r\n width: 100%;\r\n height: 100%;\r\n }\r\n }\r\n\r\n .data_container {\r\n width: 100%;\r\n height: 100%;\r\n flex-direction: row-reverse;\r\n }\r\n\r\n .info_bar_label {\r\n position: absolute;\r\n color: #fff;\r\n font-size: 26px;\r\n align-items: center;\r\n font-weight: bolder;\r\n font-family: \"Mini Pixel-7\";\r\n //width: 100%;\r\n left: 0px;\r\n justify-content: flex-start;\r\n padding-left: 5px;\r\n padding-top: 1px;\r\n text-stroke: 5px black;\r\n opacity: 0.7;\r\n }\r\n\r\n .data {\r\n position: relative;\r\n color: #ffffffaa;\r\n font-size: 26px;\r\n align-items: center;\r\n font-family: \"Mini Pixel-7\";\r\n //left: 0px;\r\n padding-right: 5px;\r\n padding-top: 1px;\r\n text-stroke: 5px black;\r\n opacity: 0.9;\r\n //letter-spacing: 1px;\r\n }\r\n //.data_center {\r\n // position: absolute;\r\n // color: #ffffff88;\r\n // font-size: 26px;\r\n // align-items: center;\r\n // justify-content: center;\r\n // font-family: \"Mini Pixel-7\";\r\n //left: 0px;\r\n // width: 100%;\r\n // padding-left: 5px;\r\n // padding-top: 1px;\r\n // text-stroke: 3px #00000099;\r\n // opacity: 0.9;\r\n //}\r\n}\r\n"
},
{
"Ident": "facepunch.ss1",
"Path": "Camera2D.cs",
"FileName": "Camera2D.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 421898,
"IsPrivate": false,
"Code": "using Sandbox;\r\n\r\npublic class Camera2D : Component\r\n{\r\n\t[Property] public CameraComponent Camera { get; set; }\r\n\r\n\tpublic Vector2 TargetPos { get; set; }\r\n\tpublic Vector2 CameraPos { get; set; }\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tbase.OnUpdate();\r\n\r\n\t\t//Vector2 newPos = Vector2.Lerp( (Vector2)WorldPosition, TargetPos, Time.Delta * 3f );\r\n\t\tCameraPos = Utils.DynamicEaseTo( CameraPos, TargetPos, 0.2f, Time.Delta );\r\n\r\n\t\tvar newPos = CameraPos + Utils.GetRandomVector() * Manager.Instance.Player.CamShakeAmount;\r\n\r\n\t\tfloat bounds_zoom = 1f + Manager.Instance.Player.Stats[PlayerStat.ZoomAmount] * 0.44f;\r\n\t\tvar XDIST = 10.75f / bounds_zoom;\r\n\t\tvar Y_MIN = -8.8f / bounds_zoom;\r\n\t\tvar Y_MAX = 8.9f / bounds_zoom;\r\n\t\tnewPos = new Vector2( MathX.Clamp( newPos.x, -XDIST, XDIST ), MathX.Clamp( newPos.y, Y_MIN, Y_MAX ) );\r\n\r\n\t\tWorldPosition = ((Vector3)newPos).WithZ( WorldPosition.z );\r\n\t}\r\n\r\n\tpublic void SetPos( Vector2 pos )\r\n\t{\r\n\t\tCameraPos = pos;\r\n\t\tTargetPos = pos;\r\n\t\tWorldPosition = ((Vector3)pos).WithZ( WorldPosition.z );\r\n\t}\r\n}\r\n"
},
{
"Ident": "facepunch.ss1",
"Path": "FloatingDamageNumber.cs",
"FileName": "FloatingDamageNumber.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 421898,
"IsPrivate": false,
"Code": "using Sandbox;\r\n\r\npublic class FloatingDamageNumber : Component\r\n{\r\n\tprivate RealTimeSince _timeSince;\r\n\r\n\tprotected override void OnAwake()\r\n\t{\r\n\t\tbase.OnAwake();\r\n\r\n\t\t_timeSince = 0f;\r\n\t}\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tbase.OnUpdate();\r\n\r\n\t\tif ( _timeSince > 1f )\r\n\t\t\tGameObject.Destroy();\r\n\t}\r\n}\r\n"
},
{
"Ident": "facepunch.ss1",
"Path": "LavaPuddle.cs",
"FileName": "LavaPuddle.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 421898,
"IsPrivate": false,
"Code": "using Sandbox.UI;\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Linq.Expressions;\r\nusing System.Text;\r\nusing System.Threading.Tasks;\r\nusing static Manager;\r\n\r\npublic class LavaPuddle : Component\r\n{\r\n\t[Property] public Sprite2D Sprite { get; set; }\r\n\r\n\tpublic TimeSince TimeSinceSpawn { get; set; }\r\n\tpublic float Lifetime { get; set; }\r\n\t\r\n\tpublic float Radius { get; set; }\r\n\tpublic float FullRadius { get; set; }\r\n\r\n\tprivate float _timeOffset;\r\n\tprivate TimeSince _timeSinceDamagePlayer;\r\n\tprivate const float DAMAGE_INTERVAL = 0.25f;\r\n\r\n\tprivate float _expandTime;\r\n\r\n\tpublic float DamageToPlayer { get; set; }\r\n\r\n\tpublic Color ColorA { get; set; }\r\n\tpublic Color ColorB { get; set; }\r\n\r\n\tpublic Vector2 Position2D\r\n\t{\r\n\t\tget { return (Vector2)WorldPosition; }\r\n\t\tset { WorldPosition = new Vector3( value.x, value.y, WorldPosition.z ); }\r\n\t}\r\n\r\n\tprotected override void OnAwake()\r\n\t{\r\n\t\tbase.OnAwake();\r\n\r\n\t\tif ( Game.Random.Float( 0f, 1f ) < 0.5f )\r\n\t\t\tSprite.SpriteFlags = SpriteFlags.HorizontalFlip;\r\n\r\n\t\tRadius = 0f;\r\n\t\tLocalScale = new Vector3( Radius * 1f, Radius * 1.06f, 1f ) * 2f * Globals.SPRITE_SCALE;\r\n\r\n\t\t//LocalScale = new Vector3( Game.Random.Float( 0.4f, 0.6f ), Game.Random.Float( 0.15f, 0.25f ), 1f ) * Globals.SPRITE_SCALE;\r\n\t\t//LocalScale = new Vector3( Game.Random.Float( 0.33f, 0.48f ), Game.Random.Float( 0.4f, 0.63f ), 1f ) * 5f * Globals.SPRITE_SCALE;\r\n\r\n\t\t_timeOffset = Game.Random.Float( 0f, 99f );\r\n\r\n\t\tTimeSinceSpawn = 0f;\r\n\t\t_expandTime = Game.Random.Float( 0.4f, 0.5f );\r\n\r\n\t\tDamageToPlayer = 2f + Math.Min(Manager.Instance.Difficulty, 5);\r\n\t}\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tbase.OnUpdate();\r\n\r\n\t\tfloat FADE_IN_TIME = 0.3f;\r\n\t\tvar color = Color.Lerp( ColorA, ColorB, 0.5f + Utils.FastSin( TimeSinceSpawn * 16f ) * 0.5f );\r\n\t\tvar opacity = 0.3f \r\n\t\t\t* Utils.Map( TimeSinceSpawn, 0f, FADE_IN_TIME, 0f, 1f, EasingType.SineOut ) \r\n\t\t\t* Utils.Map( TimeSinceSpawn, Lifetime - 1f, Lifetime, 1f, 0f, EasingType.SineIn ) \r\n\t\t\t* ( 0.85f + Utils.FastSin( _timeOffset + Time.Now * 16f ) * 0.15f );\r\n\t\tSprite.Tint = color.WithAlpha( opacity );\r\n\r\n\t\t//Gizmo.Draw.Color = Color.White.WithAlpha( 0.05f );\r\n\t\t//Gizmo.Draw.LineSphere( WorldPosition, Radius, 20 );\r\n\r\n\t\tif (TimeSinceSpawn < _expandTime )\r\n\t\t{\r\n\t\t\tRadius = Utils.Map( TimeSinceSpawn, 0f, _expandTime, FullRadius * 0.05f, FullRadius, EasingType.QuadOut );\r\n\t\t\tLocalScale = new Vector3( Radius * 0.98f, Radius * 1.06f, 1f ) * 2f * Globals.SPRITE_SCALE;\r\n\t\t}\r\n\r\n\t\tif ( TimeSinceSpawn > Lifetime )\r\n\t\t{\r\n\t\t\tManager.Instance.RemoveLavaPuddle( this );\r\n\t\t\tGameObject.Destroy();\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif( TimeSinceSpawn > FADE_IN_TIME && TimeSinceSpawn < Lifetime - 0.5f ) \r\n\t\t{\r\n\t\t\tvar player = Manager.Instance.Player;\r\n\t\t\tfloat distSqr = (player.Position2D - Position2D).LengthSquared;\r\n\r\n\t\t\tif( distSqr < MathF.Pow(Radius, 1.9f) && _timeSinceDamagePlayer > DAMAGE_INTERVAL && player.TimeSinceHurtLava > DAMAGE_INTERVAL && !player.IsDead )\r\n\t\t\t{\r\n\t\t\t\tfloat currDmg = Utils.Map( TimeSinceSpawn, FADE_IN_TIME, FADE_IN_TIME * 2f, 1f, DamageToPlayer );\r\n\t\t\t\tfloat dmg = player.CheckDamageAmount( currDmg, DamageType.LavaPuddle );\r\n\r\n\t\t\t\tif ( !player.IsInvulnerable && !player.IsTimePausedForChoosing )\r\n\t\t\t\t{\r\n\t\t\t\t\tplayer.Damage( dmg );\r\n\r\n\t\t\t\t\t_timeSinceDamagePlayer = 0f;\r\n\t\t\t\t\tplayer.TimeSinceHurtLava = 0f;\r\n\r\n\t\t\t\t\tManager.Instance.PlaySfxNearby( \"lava_puddle_03\", player.Position2D, pitch: Game.Random.Float( 0.9f, 1.1f ), volume: 1f, maxDist: 4f );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n"
},
{
"Ident": "facepunch.ss1",
"Path": "status/BulletLifetimeDamageStatus.cs",
"FileName": "BulletLifetimeDamageStatus.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 421898,
"IsPrivate": false,
"Code": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing Sandbox;\r\n\r\n[Status( 5, 0, 1f, 0, false )]\r\npublic class BulletLifetimeDamageStatus : Status\r\n{\r\n\tpublic BulletLifetimeDamageStatus()\r\n\t{\r\n\t\tTitle = \"Growing Bullets\";\r\n\t\tIconPath = \"textures/icons/bullet_lifetime_damage.png\";\r\n\t}\r\n\r\n\tpublic override void Init( Player player )\r\n\t{\r\n\t\tbase.Init( player );\r\n\t}\r\n\r\n\tpublic override void Refresh()\r\n\t{\r\n\t\tDescription = GetDescription( Level );\r\n\r\n\t\tPlayer.Modify( this, PlayerStat.BulletDamageGrow, GetAddForLevel( Level ), ModifierType.Add );\r\n\t}\r\n\r\n\tpublic override string GetDescription( int newLevel )\r\n\t{\r\n\t\treturn string.Format( \"Bullets grow their damage by {0} per second\", GetPrintAmountForLevel( Level ) );\r\n\t}\r\n\r\n\tpublic override string GetUpgradeDescription( int newLevel )\r\n\t{\r\n\t\treturn newLevel > 1 ? string.Format( \"Bullets grow their damage by {0}\u2192{1} per second\", GetPrintAmountForLevel( newLevel - 1 ), GetPrintAmountForLevel( newLevel ) ) : GetDescription( newLevel );\r\n\t}\r\n\r\n\tpublic float GetAddForLevel( int level )\r\n\t{\r\n\t\treturn 0.2f + 1.5f * level + (level == 7 ? 1.5f : 0f);\r\n\t}\r\n\r\n\tpublic string GetPrintAmountForLevel( int level )\r\n\t{\r\n\t\treturn string.Format( \"{0:0.0}\", GetAddForLevel( level ) );\r\n\t}\r\n}\r\n"
},
{
"Ident": "facepunch.ss1",
"Path": "status/CritChanceStatus.cs",
"FileName": "CritChanceStatus.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 421898,
"IsPrivate": false,
"Code": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing Sandbox;\r\n\r\n[Status( 7, 0, 1f, 0, false )]\r\npublic class CritChanceStatus : Status\r\n{\r\n\tpublic CritChanceStatus()\r\n\t{\r\n\t\tTitle = \"Sharp Bullets\";\r\n\t\tIconPath = \"textures/icons/crit_chance.png\";\r\n\t}\r\n\r\n\tpublic override void Init( Player player )\r\n\t{\r\n\t\tbase.Init( player );\r\n\t}\r\n\r\n\tpublic override void Refresh()\r\n\t{\r\n\t\tDescription = GetDescription( Level );\r\n\r\n\t\tPlayer.Modify( this, PlayerStat.CritChance, GetAddForLevel( Level ), ModifierType.Add );\r\n\t}\r\n\r\n\tpublic override string GetDescription( int newLevel )\r\n\t{\r\n\t\treturn string.Format( \"Increase critical chance from 5%\u2192{0}%\", GetPercentForLevel( Level ) );\r\n\t}\r\n\r\n\tpublic override string GetUpgradeDescription( int newLevel )\r\n\t{\r\n\t\treturn newLevel > 1 ? string.Format( \"Increase critical chance from {0}%\u2192{1}%\", GetPercentForLevel( newLevel - 1 ), GetPercentForLevel( newLevel ) ) : GetDescription( newLevel );\r\n\t}\r\n\r\n\tpublic float GetAddForLevel( int level )\r\n\t{\r\n\t\treturn 0.05f + 0.08f * level + (level == 7 ? 0.04f : 0f);\r\n\t}\r\n\r\n\tpublic float GetPercentForLevel( int level )\r\n\t{\r\n\t\treturn 5 + 8 * level + (level == 7 ? 4 : 0);\r\n\t}\r\n}\r\n"
},
{
"Ident": "facepunch.ss1",
"Path": "status/CurseAimDir.cs",
"FileName": "CurseAimDir.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 421898,
"IsPrivate": false,
"Code": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing Sandbox;\r\n\r\n[Status(4, 0, 0.5f, 0, true)]\r\npublic class CurseAimDir : Status\r\n{\r\n\tpublic CurseAimDir()\r\n {\r\n\t\tTitle = \"Random Aim\";\r\n\t\tIconPath = \"textures/icons/curse_shoot_random_dir.png\";\r\n\t}\r\n\r\n\tpublic override void Init(Player player)\r\n\t{\r\n\t\tbase.Init(player);\r\n\t}\r\n\r\n\tpublic override void Refresh()\r\n {\r\n\t\tDescription = GetDescription(Level);\r\n\r\n\t\tPlayer.Modify(this, PlayerStat.ShootRandomDirChance, GetChanceForLevel(Level), ModifierType.Add);\r\n\t}\r\n\r\n\tpublic override string GetDescription( int newLevel )\r\n\t{\r\n\t\treturn string.Format( \"{0}% chance to shoot in a random direction\", GetPercentForLevel( Level ) );\r\n\t}\r\n\r\n\tpublic override string GetUpgradeDescription( int newLevel )\r\n\t{\r\n\t\treturn newLevel > 1 ? string.Format( \"{0}%\u2192{1}% chance to shoot in a random direction\", GetPercentForLevel( newLevel - 1 ), GetPercentForLevel( newLevel ) ) : GetDescription( newLevel );\r\n\t}\r\n\r\n\tpublic float GetChanceForLevel( int level )\r\n\t{\r\n\t\treturn 0.25f * level;\r\n\t}\r\n\r\n\tpublic float GetPercentForLevel( int level )\r\n\t{\r\n\t\treturn 25 * level;\r\n\t}\r\n}\r\n"
},
{
"Ident": "facepunch.ss1",
"Path": "status/CurseAttackSpeedStatus.cs",
"FileName": "CurseAttackSpeedStatus.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 421898,
"IsPrivate": false,
"Code": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing Sandbox;\r\n\r\n[Status( 7, 0, 1f, 0, true )]\r\npublic class CurseAttackSpeedStatus : Status\r\n{\r\n\tpublic CurseAttackSpeedStatus()\r\n\t{\r\n\t\tTitle = \"Lazy Shooting\";\r\n\t\tIconPath = \"textures/icons/curse_attack_speed.png\";\r\n\t}\r\n\r\n\tpublic override void Init( Player player )\r\n\t{\r\n\t\tbase.Init( player );\r\n\t}\r\n\r\n\tpublic override void Refresh()\r\n\t{\r\n\t\tDescription = GetDescription( Level );\r\n\r\n\t\tPlayer.Modify( this, PlayerStat.AttackSpeed, GetMultForLevel( Level ), ModifierType.Mult );\r\n\t}\r\n\r\n\tpublic override string GetDescription( int newLevel )\r\n\t{\r\n\t\treturn string.Format( \"Decrease attack speed by {0}%\", GetPercentForLevel( Level ) );\r\n\t}\r\n\r\n\tpublic override string GetUpgradeDescription( int newLevel )\r\n\t{\r\n\t\treturn newLevel > 1 ? string.Format( \"Decrease attack speed by {0}%\u2192{1}%\", GetPercentForLevel( newLevel - 1 ), GetPercentForLevel( newLevel ) ) : GetDescription( newLevel );\r\n\t}\r\n\r\n\tpublic float GetMultForLevel( int level )\r\n\t{\r\n\t\treturn 1f - 0.20f * level;\r\n\t}\r\n\r\n\tpublic float GetPercentForLevel( int level )\r\n\t{\r\n\t\treturn 20 * level;\r\n\t}\r\n}\r\n"
},
{
"Ident": "facepunch.ss1",
"Path": "status/DashStrengthStatus.cs",
"FileName": "DashStrengthStatus.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 421898,
"IsPrivate": false,
"Code": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing Sandbox;\r\n\r\n[Status( 4, 0, 1f, 0, false )]\r\npublic class DashStrengthStatus : Status\r\n{\r\n\tpublic DashStrengthStatus()\r\n\t{\r\n\t\tTitle = \"Leg Day\";\r\n\t\tIconPath = \"textures/icons/dash_strength.png\";\r\n\t}\r\n\r\n\tpublic override void Init( Player player )\r\n\t{\r\n\t\tbase.Init( player );\r\n\t}\r\n\r\n\tpublic override void Refresh()\r\n\t{\r\n\t\tDescription = GetDescription( Level );\r\n\r\n\t\tPlayer.Modify( this, PlayerStat.DashStrength, GetMultForLevel( Level ), ModifierType.Mult );\r\n\t\tPlayer.Modify( this, PlayerStat.DashInvulnTime, GetMultForLevel( Level ), ModifierType.Mult );\r\n\t}\r\n\r\n\tpublic override string GetDescription( int newLevel )\r\n\t{\r\n\t\treturn string.Format( \"You dash {0}% longer\", GetPercentForLevel( Level ) );\r\n\t}\r\n\r\n\tpublic override string GetUpgradeDescription( int newLevel )\r\n\t{\r\n\t\treturn newLevel > 1 ? string.Format( \"You dash {0}%\u2192{1}% longer\", GetPercentForLevel( newLevel - 1 ), GetPercentForLevel( newLevel ) ) : GetDescription( newLevel );\r\n\t}\r\n\r\n\tpublic float GetMultForLevel( int level )\r\n\t{\r\n\t\treturn 1f + 0.18f * level + (level == 4 ? 0.03f : 0f);\r\n\t}\r\n\r\n\tpublic float GetPercentForLevel( int level )\r\n\t{\r\n\t\treturn 18 * level + (level == 4 ? 3 : 0);\r\n\t}\r\n}\r\n"
},
{
"Ident": "facepunch.ss1",
"Path": "status/FearDropGrenadeStatus.cs",
"FileName": "FearDropGrenadeStatus.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 421898,
"IsPrivate": false,
"Code": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing Sandbox;\r\n\r\n[Status( 7, 0, 1f, 0, false, typeof( DashFearStatus ), typeof( GrenadeFearStatus ) )]\r\npublic class FearDropGrenadeStatus : Status\r\n{\r\n\tpublic FearDropGrenadeStatus()\r\n\t{\r\n\t\tTitle = \"Bomb Curse\";\r\n\t\tIconPath = \"textures/icons/fear_drop_grenade.png\";\r\n\t}\r\n\r\n\tpublic override void Init( Player player )\r\n\t{\r\n\t\tbase.Init( player );\r\n\t}\r\n\r\n\tpublic override void Refresh()\r\n\t{\r\n\t\tDescription = GetDescription( Level );\r\n\r\n\t\tPlayer.Modify( this, PlayerStat.FearDropGrenadeChance, GetAddForLevel( Level ), ModifierType.Add ); ;\r\n\t}\r\n\r\n\tpublic override string GetDescription( int newLevel )\r\n\t{\r\n\t\treturn string.Format( \"Enemies you scare have a {0}% chance to drop a grenade on death\", GetPercentForLevel( Level ) );\r\n\t}\r\n\r\n\tpublic override string GetUpgradeDescription( int newLevel )\r\n\t{\r\n\t\treturn newLevel > 1 ? string.Format( \"Enemies you scare have a {0}%\u2192{1}% chance to drop a grenade on death\", GetPercentForLevel( newLevel - 1 ), GetPercentForLevel( newLevel ) ) : GetDescription( newLevel );\r\n\t}\r\n\r\n\tpublic float GetAddForLevel( int level )\r\n\t{\r\n\t\treturn level * 0.07f;\r\n\t}\r\n\r\n\tpublic float GetPercentForLevel( int level )\r\n\t{\r\n\t\treturn level * 7;\r\n\t}\r\n}\r\n"
},
{
"Ident": "facepunch.ss1",
"Path": "status/FearPainStatus.cs",
"FileName": "FearPainStatus.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 421898,
"IsPrivate": false,
"Code": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing Sandbox;\r\n\r\n[Status( 5, 0, 1f, 0, false, typeof( DashFearStatus ), typeof( GrenadeFearStatus ) )]\r\npublic class FearPainStatus : Status\r\n{\r\n\tpublic FearPainStatus()\r\n\t{\r\n\t\tTitle = \"Killer Stress\";\r\n\t\tIconPath = \"textures/icons/fear_pain.png\";\r\n\t}\r\n\r\n\tpublic override void Init( Player player )\r\n\t{\r\n\t\tbase.Init( player );\r\n\t}\r\n\r\n\tpublic override void Refresh()\r\n\t{\r\n\t\tDescription = GetDescription( Level );\r\n\r\n\t\tPlayer.Modify( this, PlayerStat.FearPainPercent, GetAddForLevel( Level ), ModifierType.Add );\r\n\t}\r\n\r\n\tpublic override string GetDescription( int newLevel )\r\n\t{\r\n\t\treturn string.Format( \"Enemies you scare lose {0}% of their remaining HP each second\", GetPercentForLevel( Level ) );\r\n\t}\r\n\r\n\tpublic override string GetUpgradeDescription( int newLevel )\r\n\t{\r\n\t\treturn newLevel > 1 ? string.Format( \"Enemies you scare lose {0}%\u2192{1}% of their remaining HP each second\", GetPercentForLevel( newLevel - 1 ), GetPercentForLevel( newLevel ) ) : GetDescription( newLevel );\r\n\t}\r\n\r\n\tpublic float GetAddForLevel( int level )\r\n\t{\r\n\t\treturn 0.02f + level * 0.07f + (level == 5 ? 0.01f : 0f);\r\n\t}\r\n\r\n\tpublic float GetPercentForLevel( int level )\r\n\t{\r\n\t\treturn 2 + level * 7 + (level == 5 ? 1 : 0);\r\n\t}\r\n}\r\n"
},
{
"Ident": "facepunch.ss1",
"Path": "status/GrenadeFearStatus.cs",
"FileName": "GrenadeFearStatus.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 421898,
"IsPrivate": false,
"Code": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing Sandbox;\r\n\r\n[Status(4, 0, 1f, 0, false, typeof(GrenadeShootReloadStatus), typeof(FearDropGrenadeStatus))]\r\npublic class GrenadeFearStatus : Status\r\n{\r\n public GrenadeFearStatus()\r\n {\r\n Title = \"Terrorism\";\r\n IconPath = \"textures/icons/grenade_fear.png\";\r\n }\r\n\r\n public override void Init(Player player)\r\n {\r\n base.Init(player);\r\n }\r\n\r\n public override void Refresh()\r\n {\r\n Description = GetDescription(Level);\r\n\r\n Player.Modify(this, PlayerStat.GrenadeFearChance, GetAddForLevel(Level), ModifierType.Add); ;\r\n }\r\n\r\n public override string GetDescription(int newLevel)\r\n {\r\n return string.Format(\"Your grenades have a {0}% chance to scare enemies they hurt\", GetPercentForLevel(Level));\r\n }\r\n\r\n public override string GetUpgradeDescription(int newLevel)\r\n {\r\n return newLevel > 1 ? string.Format(\"Your grenades have a {0}%\u2192{1}% chance to scare enemies they hurt\", GetPercentForLevel(newLevel - 1), GetPercentForLevel(newLevel)) : GetDescription(newLevel);\r\n }\r\n\r\n public float GetAddForLevel(int level)\r\n {\r\n return level == 4 ? 1f : 0.3f * level;\r\n }\r\n\r\n public float GetPercentForLevel(int level)\r\n {\r\n return level == 4 ? 100 : 30 * level;\r\n }\r\n}\r\n"
},
{
"Ident": "facepunch.ss1",
"Path": "status/KillHealStatus.cs",
"FileName": "KillHealStatus.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 421898,
"IsPrivate": false,
"Code": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing Sandbox;\r\n\r\n[Status(5, 3, 1f, 0, false )]\r\npublic class KillHealStatus : Status\r\n{\r\n\tpublic KillHealStatus()\r\n {\r\n\t\tTitle = \"Vampire\";\r\n\t\tIconPath = \"textures/icons/kill_heal.png\";\r\n\t}\r\n\r\n\tpublic override void Init(Player player)\r\n\t{\r\n\t\tbase.Init(player);\r\n\t}\r\n\r\n\tpublic override void Refresh()\r\n {\r\n\t\tDescription = GetDescription(Level);\r\n\r\n Player.Modify(this, PlayerStat.HealthRegen, -GetHpDrainAmountForLevel(Level), ModifierType.Add);\r\n }\r\n\r\n\tpublic override string GetDescription(int newLevel)\r\n\t{\r\n\t\treturn string.Format(\"Heal for {0} whenever you kill an enemy within 2 meters but lose {1} HP/s\", GetPrintAmountForLevel(Level), GetHpDrainPrintAmountForLevel(Level));\r\n\t}\r\n\r\n\tpublic override string GetUpgradeDescription(int newLevel)\r\n\t{\r\n\t\treturn newLevel > 1 ? string.Format( \"Heal for {0}\u2192{1} whenever you kill an enemy within 2 meters but lose {2}\u2192{3} HP/s\", GetPrintAmountForLevel(newLevel - 1), GetPrintAmountForLevel(newLevel), GetHpDrainPrintAmountForLevel(newLevel - 1), GetHpDrainPrintAmountForLevel(newLevel)) : GetDescription(newLevel);\r\n\t}\r\n\r\n public override void OnKill(Enemy enemy)\r\n {\r\n\t\tvar distSqr = (enemy.Position2D - Player.Position2D).LengthSquared;\r\n\t\tif ( distSqr > 2f * 2f )\r\n\t\t\treturn;\r\n\r\n\t\tvar amount = GetAmountForLevel( Level );\r\n\r\n\t\tif ( Player.Health < Player.Stats[PlayerStat.MaxHp] )\r\n\t\t\tPlayer.TimeSinceChangeHP = 0f;\r\n\r\n\t\tPlayer.RegenHealth( amount );\r\n\r\n\t\t//DamageNumbersLegacy.Create( amount, Player.Position2D + new Vector2( 0.2f + Game.Random.Float( -0.1f, 0.1f ), Player.Radius * 3f + Game.Random.Float( -0.2f, 0.3f ) ), color: Color.Green, sizeMultiplier: 0.7f );\r\n\t}\r\n\r\n\tpublic float GetAmountForLevel(int level)\r\n\t{\r\n\t\treturn 0.1f + level * 0.5f + (level == 5 ? 0.05f : 0f);\r\n\t}\r\n\r\n public string GetPrintAmountForLevel(int level)\r\n {\r\n return string.Format(\"{0:0.00}\", GetAmountForLevel(level));\r\n }\r\n\r\n public float GetHpDrainAmountForLevel(int level)\r\n {\r\n return level * 0.4f;\r\n }\r\n\r\n public string GetHpDrainPrintAmountForLevel(int level)\r\n {\r\n return string.Format(\"{0:0.00}\", GetHpDrainAmountForLevel(level));\r\n }\r\n}\r\n"
},
{
"Ident": "facepunch.ss1",
"Path": "status/MoreRerollsStatus.cs",
"FileName": "MoreRerollsStatus.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 421898,
"IsPrivate": false,
"Code": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing Sandbox;\r\n\r\n[Status(3, 0, 1f, maxDifficulty: 2, isCurse: false )]\r\npublic class MoreRerollsStatus : Status\r\n{\r\n\tpublic MoreRerollsStatus()\r\n {\r\n\t\tTitle = \"More Rerolls\";\r\n\t\tIconPath = \"textures/icons/more_rerolls.png\";\r\n\t}\r\n\r\n\tpublic override void Init(Player player)\r\n\t{\r\n\t\tbase.Init(player);\r\n\t}\r\n\r\n\tpublic override void Refresh()\r\n {\r\n\t\tDescription = GetDescription(Level);\r\n\r\n\t\tPlayer.Modify(this, PlayerStat.NumRerollsPerLevel, GetAddForLevel(Level), ModifierType.Add);\r\n\t}\r\n\r\n\tpublic override string GetDescription(int newLevel)\r\n\t{\r\n\t\treturn string.Format(\"Gain {0} addition reroll each level\", GetAddForLevel(Level));\r\n\t}\r\n\r\n\tpublic override string GetUpgradeDescription(int newLevel)\r\n {\r\n\t\treturn newLevel > 1 ? string.Format(\"Gain {0}\u2192{1} addition rerolls each level\", GetAddForLevel(newLevel - 1), GetAddForLevel(newLevel)) : GetDescription(newLevel);\r\n\t}\r\n\r\n\tpublic float GetAddForLevel(int level)\r\n {\r\n\t\treturn 1f * level;\r\n }\r\n}\r\n"
},
{
"Ident": "facepunch.ss1",
"Path": "status/MovespeedStatus.cs",
"FileName": "MovespeedStatus.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 421898,
"IsPrivate": false,
"Code": "using Sandbox;\r\n\r\n[Status( 7, 0, 1f, 0, false )]\r\npublic class MovespeedStatus : Status\r\n{\r\n\tpublic MovespeedStatus()\r\n\t{\r\n\t\tTitle = \"Fast Shoes\";\r\n\t\tIconPath = \"textures/icons/shoe.png\";\r\n\t}\r\n\r\n\tpublic override void Init( Player player )\r\n\t{\r\n\t\tbase.Init( player );\r\n\t}\r\n\r\n\tpublic override void Refresh()\r\n\t{\r\n\t\tDescription = GetDescription( Level );\r\n\r\n\t\tPlayer.Modify( this, PlayerStat.MoveSpeed, GetMultForLevel( Level ), ModifierType.Mult );\r\n\t}\r\n\r\n\tpublic override string GetDescription( int newLevel )\r\n\t{\r\n\t\treturn string.Format( \"Increase movespeed by {0}%\", GetPercentForLevel( Level ) );\r\n\t}\r\n\r\n\tpublic override string GetUpgradeDescription( int newLevel )\r\n\t{\r\n\t\treturn newLevel > 1 ? string.Format( \"Increase movespeed by {0}%\u2192{1}%\", GetPercentForLevel( newLevel - 1 ), GetPercentForLevel( newLevel ) ) : GetDescription( newLevel );\r\n\t}\r\n\r\n\tpublic float GetMultForLevel( int level )\r\n\t{\r\n\t\treturn 1f + 0.2f * level + (level == 7 ? 0.2f : 0f);\r\n\t}\r\n\r\n\tpublic float GetPercentForLevel( int level )\r\n\t{\r\n\t\treturn 20 * level + (level == 7 ? 20 : 0);\r\n\t}\r\n}\r\n"
},
{
"Ident": "facepunch.ss1",
"Path": "status/Status.cs",
"FileName": "Status.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 421898,
"IsPrivate": false,
"Code": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Text;\r\nusing System.Threading.Tasks;\r\n\r\npublic class Status\r\n{\r\n\tpublic bool ShouldUpdate { get; protected set; }\r\n\tpublic Player Player { get; protected set; }\r\n\tpublic int Level { get; set; }\r\n\tpublic int MaxLevel { get; set; }\r\n\tpublic TimeSince ElapsedTime { get; protected set; }\r\n\tpublic string Title { get; protected set; }\r\n\tpublic string Description { get; protected set; }\r\n\tpublic string IconPath { get; protected set; }\r\n\r\n\tpublic string DataString { get; protected set; }\r\n\r\n\tpublic Status()\r\n\t{\r\n\t\tLevel = 1;\r\n\t}\r\n\r\n\tpublic virtual void Init( Player player )\r\n\t{\r\n\t\tPlayer = player;\r\n\t\tElapsedTime = 0f;\r\n\t\tShouldUpdate = false;\r\n\t}\r\n\r\n\t// when gaining or leveling up\r\n\tpublic virtual void Refresh()\r\n\t{\r\n\r\n\t}\r\n\r\n\tpublic virtual void Update( float dt )\r\n\t{\r\n\t\t//if (ElapsedTime > 10f)\r\n\t\t// Player.RemoveStatus(this);\r\n\t}\r\n\r\n\tpublic virtual void Remove()\r\n\t{\r\n\r\n\t}\r\n\r\n\tpublic virtual string GetDescription( int newLevel )\r\n\t{\r\n\t\treturn \"...\";\r\n\t}\r\n\r\n\tpublic virtual string GetUpgradeDescription( int newLevel )\r\n\t{\r\n\t\treturn \"...\";\r\n\t}\r\n\r\n\tpublic virtual void Colliding( Thing other, float percent, float dt ) { }\r\n\tpublic virtual void OnDashStarted() { }\r\n\tpublic virtual void OnDashFinished() { }\r\n\tpublic virtual void OnDashRecharged() { }\r\n\tpublic virtual void OnReload() { }\r\n\t//public virtual void OnBurn( Enemy enemy ) { }\r\n\t//public virtual void OnFreeze( Enemy enemy ) { }\r\n\t//public virtual void OnFear( Enemy enemy ) { }\r\n\tpublic virtual void OnKill( Enemy enemy ) { }\r\n\tpublic virtual void OnHurt( float amount ) { }\r\n\tpublic virtual void OnGainExperience( int xp ) { }\r\n\tpublic virtual void OnLevelUp() { }\r\n\tpublic virtual void OnReroll() { }\r\n\tpublic virtual void OnGainShield() { }\r\n\tpublic virtual void OnLoseShield() { }\r\n\tpublic virtual void OnAddStatus( int typeIdentity ) { }\r\n}\r\n"
},
{
"Ident": "facepunch.ss1",
"Path": "things/enemies/Charger.cs",
"FileName": "Charger.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 421898,
"IsPrivate": false,
"Code": "using Sandbox;\r\nusing Sandbox.ModelEditor.Nodes;\r\nusing System.Net.NetworkInformation;\r\n\r\npublic class Charger : Enemy\r\n{\r\n\tprivate TimeSince _damageTime;\r\n\tprivate const float DAMAGE_TIME = 1f;\r\n\r\n\tprotected float _chargeDelayTimer;\r\n\tprivate const float CHARGE_DELAY_MIN = 2f;\r\n\tprivate const float CHARGE_DELAY_MAX = 6f;\r\n\r\n\tpublic bool IsPreparingToCharge { get; private set; }\r\n\tpublic bool IsCharging { get; private set; }\r\n\tprivate float _prepareTimer;\r\n\tprivate const float PREPARE_TIME = 1f;\r\n\tprotected float _chargeTimer;\r\n\tprotected float CHARGE_TIME_MIN = 1.8f;\r\n\tprotected float CHARGE_TIME_MAX = 2.5f;\r\n\tprivate float _chargeTime;\r\n\tprivate float _nextRedirectTime;\r\n\tprivate TimeSince _timeSinceRedirect;\r\n\r\n\tprotected Vector2 _chargeDir;\r\n\tprotected Vector2 _chargeVel;\r\n\tprivate TimeSince _chargeCloudTimer;\r\n\r\n\tpublic override float HeightVariance => 0.03f;\r\n\tpublic override float WidthVariance => 0.015f;\r\n\r\n\tpublic float ChargeRange { get; set; }\r\n\r\n\tprotected float REDIRECT_DELAY_MIN = 0.3f;\r\n\tprotected float REDIRECT_DELAY_MAX = 2.7f;\r\n\r\n\tprotected override void OnAwake()\r\n\t{\r\n\t\t//OffsetY = -0.57f;\r\n\t\tShadowScale = 1.25f;\r\n\t\tShadowFullOpacity = 0.8f;\r\n\t\tShadowOpacity = 0f;\r\n\r\n\t\tScale = 1.25f;\r\n\r\n\t\tbase.OnAwake();\r\n\r\n\t\t//Sprite.Texture = Texture.Load(\"textures/sprites/charger.vtex\");\r\n\t\t//Sprite.Size = new Vector2( 1f, 1f ) * Scale;\r\n\r\n\t\tPushStrength = 25f;\r\n\r\n\t\tRadius = 0.275f;\r\n\r\n\t\tHealth = 75f;\r\n\r\n\t\tif ( Manager.Instance.Difficulty < 0 )\r\n\t\t\tHealth = 55f;\r\n\r\n\t\tMaxHealth = Health;\r\n\t\tDamageToPlayer = 10f;\r\n\r\n\t\tCoinValueMin = 2;\r\n\t\tCoinValueMax = 5;\r\n\t\tCoinChance = 0.7f;\r\n\r\n\t\tSprite.PlayAnimation( AnimSpawnPath );\r\n\r\n\t\tif ( IsProxy )\r\n\t\t\treturn;\r\n\r\n\t\tCollideWith.Add( typeof( Enemy ) );\r\n\t\tCollideWith.Add( typeof( Player ) );\r\n\r\n\t\t_damageTime = DAMAGE_TIME;\r\n\t\t_chargeDelayTimer = Game.Random.Float( CHARGE_DELAY_MIN, CHARGE_DELAY_MAX );\r\n\r\n\t\tChargeRange = 4.2f;\r\n\t}\r\n\r\n\tprotected override void UpdatePosition( float dt )\r\n\t{\r\n\t\t//Gizmo.Draw.Color = Color.White.WithAlpha(0.5f);\r\n\t\t//Gizmo.Draw.Text( $\"IsCharging: {IsCharging}\", new global::Transform( (Vector3)Position2D + new Vector3( 0f, -0.7f, 0f ) ) );\r\n\r\n\t\tbase.UpdatePosition( dt );\r\n\r\n\t\tvar targetPos = Target.IsValid() ? Target.Position2D : (IsCharmed ? Manager.Instance.Player.Position2D : Position2D);\r\n\r\n\t\tif ( IsPreparingToCharge )\r\n\t\t{\r\n\t\t\t_prepareTimer -= dt;\r\n\t\t\tif ( _prepareTimer < 0f )\r\n\t\t\t{\r\n\t\t\t\tCharge();\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if ( IsCharging )\r\n\t\t{\r\n\t\t\t_chargeTimer -= dt;\r\n\t\t\tif ( _chargeTimer < 0f )\r\n\t\t\t{\r\n\t\t\t\tIsCharging = false;\r\n\t\t\t\tSprite.PlayAnimation( AnimIdlePath );\r\n\t\t\t\tCanTurn = true;\r\n\t\t\t\tDontChangeAnimSpeed = false;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tHandleCharging( dt );\r\n\t\t\t}\r\n\r\n\t\t\tWorldPosition += (Vector3)(_chargeVel + Velocity) * dt;\r\n\r\n\t\t\tif ( Manager.Instance.Difficulty >= 1 && _timeSinceRedirect > _nextRedirectTime )\r\n\t\t\t{\r\n\t\t\t\t_chargeDir = (targetPos - Position2D).Normal;\r\n\r\n\t\t\t\tif(Math.Abs( targetPos.x - Position2D.x ) > 0.15f )\r\n\t\t\t\t\tFlipX = targetPos.x > Position2D.x;\r\n\r\n\t\t\t\t_nextRedirectTime = Game.Random.Float( REDIRECT_DELAY_MIN, REDIRECT_DELAY_MAX );\r\n\t\t\t\t_timeSinceRedirect = 0f;\r\n\t\t\t}\r\n\r\n\t\t\tif ( _chargeCloudTimer > 0.25f )\r\n\t\t\t{\r\n\t\t\t\tSpawnCloudClient( Position2D + new Vector2( 0f, 0.25f ), -_chargeDir * Game.Random.Float( 0.2f, 0.8f ) );\r\n\t\t\t\t_chargeCloudTimer = Game.Random.Float( 0f, 0.075f );\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tVelocity += (targetPos - Position2D).Normal * dt * (IsFeared ? -1f : 1f);\r\n\r\n\t\t\tfloat speed = 0.75f * (IsAttacking ? 1.3f : 0.7f) + Utils.FastSin( MoveTimeOffset + Time.Now * (IsAttacking ? 15f : 7.5f) ) * (IsAttacking ? 0.66f : 0.35f);\r\n\r\n\t\t\tif ( Manager.Instance.Difficulty < 0 )\r\n\t\t\t\tspeed *= 0.85f;\r\n\r\n\t\t\tWorldPosition += (Vector3)Velocity * speed * dt;\r\n\t\t}\r\n\r\n\t\tvar player_dist_sqr = (targetPos - Position2D).LengthSquared;\r\n\t\tif ( !IsPreparingToCharge && !IsCharging && !IsAttacking && player_dist_sqr < ChargeRange * ChargeRange && Target.IsValid() )\r\n\t\t{\r\n\t\t\t_chargeDelayTimer -= dt;\r\n\t\t\tif ( _chargeDelayTimer < 0f )\r\n\t\t\t{\r\n\t\t\t\tPrepareToCharge();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprotected virtual void HandleCharging(float dt)\r\n\t{\r\n\t\tfloat chargeSpeed = Manager.Instance.Difficulty >= 1 ? 12f : 4f; ;\r\n\t\t_chargeVel += _chargeDir * chargeSpeed * Utils.MapReturn( _chargeTimer, _chargeTime, 0f, 0f, 1f, EasingType.Linear ) * dt;\r\n\t\tTempWeight += Utils.MapReturn( _chargeTimer, _chargeTime, 0f, 1f, 6f, EasingType.Linear ) * dt;\r\n\r\n\t\tfloat BUFFER = 0.01f;\r\n\t\tvar x_min = Manager.Instance.BOUNDS_MIN.x + Radius + BUFFER;\r\n\t\tvar x_max = Manager.Instance.BOUNDS_MAX.x - Radius - BUFFER;\r\n\t\tvar y_min = Manager.Instance.BOUNDS_MIN.y + BUFFER;\r\n\t\tvar y_max = Manager.Instance.BOUNDS_MAX.y - Radius - BUFFER;\r\n\r\n\t\tif ( Position2D.x < x_min && _chargeDir.x < 0f )\r\n\t\t{\r\n\t\t\t_chargeDir = _chargeDir.WithX( Math.Abs( _chargeDir.x ) );\r\n\t\t\t_chargeVel = _chargeVel.WithX( Math.Abs( _chargeVel.x ) * 0.1f );\r\n\t\t\tFlipX = true;\r\n\t\t\tSprite.SpriteFlags = SpriteFlags.HorizontalFlip;\r\n\t\t}\r\n\t\telse if ( Position2D.x > x_max && _chargeDir.x > 0f )\r\n\t\t{\r\n\t\t\t_chargeDir = _chargeDir.WithX( -Math.Abs( _chargeDir.x ) );\r\n\t\t\t_chargeVel = _chargeVel.WithX( -Math.Abs( _chargeVel.x ) * 0.1f );\r\n\t\t\tFlipX = false;\r\n\t\t\tSprite.SpriteFlags = SpriteFlags.None;\r\n\t\t}\r\n\r\n\t\tif ( Position2D.y < y_min && _chargeDir.y < 0f )\r\n\t\t{\r\n\t\t\t_chargeDir = _chargeDir.WithY( Math.Abs( _chargeDir.y ) );\r\n\t\t\t_chargeVel = _chargeDir.WithY( Math.Abs( _chargeVel.y ) * 0.1f );\r\n\t\t}\r\n\t\telse if ( Position2D.y > y_max && _chargeDir.y > 0f )\r\n\t\t{\r\n\t\t\t_chargeDir = _chargeDir.WithY( -Math.Abs( _chargeDir.y ) );\r\n\t\t\t_chargeVel = _chargeDir.WithY( -Math.Abs( _chargeVel.y ) * 0.1f );\r\n\t\t}\r\n\t}\r\n\r\n\tprotected override void HandleDeceleration( float dt )\r\n\t{\r\n\t\tif ( IsCharging )\r\n\t\t{\r\n\t\t\tVelocity *= (1f - dt * 1.75f);\r\n\r\n\t\t\tfloat decel = Manager.Instance.Difficulty >= 1 ? 3f : 0.5f;\r\n\t\t\t_chargeVel *= (1f - dt * decel);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tbase.HandleDeceleration( dt );\r\n\t\t}\r\n\t}\r\n\r\n\tprotected override void UpdateSprite( Thing target )\r\n\t{\r\n\t\tif ( !IsCharging )\r\n\t\t\tbase.UpdateSprite( target );\r\n\t}\r\n\r\n\tprotected override void HandleAttacking( Thing target, float dt )\r\n\t{\r\n\t\tif ( !IsPreparingToCharge && !IsCharging )\r\n\t\t\tbase.HandleAttacking( target, dt );\r\n\t}\r\n\r\n\tpublic void PrepareToCharge()\r\n\t{\r\n\t\t_prepareTimer = PREPARE_TIME;\r\n\t\tIsPreparingToCharge = true;\r\n\t\tManager.Instance.PlaySfxNearby( \"enemy.roar.prepare\", Position2D, pitch: Game.Random.Float( 0.95f, 1.05f ), volume: 1f, maxDist: 5f );\r\n\t\tSprite.PlayAnimation( \"charge_start\" );\r\n\t\tCanTurn = false;\r\n\t\tCanAttack = false;\r\n\t\tCanAttackAnim = false;\r\n\t\tDontChangeAnimSpeed = true;\r\n\r\n\t\tif ( Manager.Instance.Difficulty >= 1 )\r\n\t\t{\r\n\t\t\t_nextRedirectTime = Game.Random.Float( REDIRECT_DELAY_MIN, REDIRECT_DELAY_MAX );\r\n\t\t\t_timeSinceRedirect = 0f;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void Charge()\r\n\t{\r\n\t\tvar target_pos = Target.IsValid() && !IsFeared\r\n\t\t\t? Target.Position2D + Target.Velocity * Game.Random.Float( 0.5f, 1.75f )\r\n\t\t\t: Position2D + Utils.GetRandomVector() * 0.5f;\r\n\r\n\t\t_chargeDir = Utils.RotateVector( (target_pos - Position2D).Normal, Game.Random.Float( -10f, 10f ) );\r\n\r\n\t\tIsPreparingToCharge = false;\r\n\t\tIsCharging = true;\r\n\t\t_chargeTime = Game.Random.Float( CHARGE_TIME_MIN, CHARGE_TIME_MAX ) * (Manager.Instance.Difficulty >= 1 ? Game.Random.Float(1.25f, Utils.Map(Manager.Instance.Difficulty, 1, 10, 1.75f, 3f, EasingType.SineIn)) : 1f);\r\n\r\n\t\t_chargeTimer = _chargeTime;\r\n\t\tCanAttack = true;\r\n\t\tCanAttackAnim = true;\r\n\r\n\t\t_chargeDelayTimer = Game.Random.Float( CHARGE_DELAY_MIN, CHARGE_DELAY_MAX ) * (Manager.Instance.Difficulty < 0 ? 1.4f : 1f);\r\n\t\t_chargeVel = Vector2.Zero;\r\n\t\tSprite.PlayAnimation( \"charge_loop\" );\r\n\t\tAnimSpeed = 3f;\r\n\t\tFlipX = _chargeDir.x > 0f;\r\n\t\tSprite.SpriteFlags = FlipX ? SpriteFlags.HorizontalFlip : SpriteFlags.None;\r\n\t\t//Sprite.SpriteFlags = target_pos.x > Position2D.x ? SpriteFlags.HorizontalFlip : SpriteFlags.None;\r\n\r\n\t\tManager.Instance.PlaySfxNearby( \"enemy.roar\", Position2D, pitch: Game.Random.Float( 0.925f, 1.075f ), volume: 1f, maxDist: 8f );\r\n\t}\r\n\r\n\tpublic override void Colliding( Thing other, float percent, float dt )\r\n\t{\r\n\t\tbase.Colliding( other, percent, dt );\r\n\r\n\t\tif ( other is Enemy enemy && !enemy.IsDying )\r\n\t\t{\r\n\t\t\tvar spawnFactor = Utils.Map( enemy.TimeSinceSpawn, 0f, enemy.SpawnTime, 0f, 1f, EasingType.QuadIn );\r\n\t\t\tVelocity += (Position2D - enemy.Position2D).Normal * Utils.Map( percent, 0f, 1f, 0f, 1f ) * enemy.PushStrength * (1f + enemy.TempWeight) * spawnFactor * dt;\r\n\r\n\t\t\tif ( (IsAttacking || IsCharging) && IsCharmed != enemy.IsCharmed && _damageTime > (DAMAGE_TIME / TimeScale) )\r\n\t\t\t{\r\n\t\t\t\tvar dmg = DamageToPlayer;\r\n\t\t\t\tif ( IsCharmed )\r\n\t\t\t\t\tdmg *= CharmDamageDealtMultiplier;\r\n\r\n\t\t\t\tenemy.Damage( dmg, null, addVel: Vector2.Zero, addTempWeight: 0f, isCrit: false, DamageType.Melee );\r\n\t\t\t\tenemy.Target = this;\r\n\t\t\t\t_damageTime = 0f;\r\n\t\t\t\tManager.Instance.PlaySfxNearby( \"zombie.attack.player\", Position2D, pitch: Utils.Map( enemy.Health, enemy.MaxHealth, 0f, 0.95f, 1.15f, EasingType.QuadIn ), volume: 0.6f, maxDist: 4.5f );\r\n\t\t\t}\r\n\t\t}\r\n\t\t// todo: move collision check to player instead to prevent laggy hits?\r\n\t\telse if ( other is Player player )\r\n\t\t{\r\n\t\t\tif ( !player.IsDead )\r\n\t\t\t{\r\n\t\t\t\tVelocity += (Position2D - player.Position2D).Normal * Utils.Map( percent, 0f, 1f, 0f, 1f ) * player.Stats[PlayerStat.PushStrength] * (1f + player.TempWeight) * dt;\r\n\r\n\t\t\t\tif ( (IsAttacking || IsCharging) && _damageTime > (DAMAGE_TIME / TimeScale) && !IsCharmed )\r\n\t\t\t\t{\r\n\t\t\t\t\tfloat dmg = player.CheckDamageAmount( DamageToPlayer, DamageType.Melee );\r\n\r\n\t\t\t\t\tif ( !player.IsInvulnerable && !player.IsTimePausedForChoosing )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tManager.Instance.PlaySfxNearby( \"zombie.attack.player\", Position2D, pitch: Utils.Map( player.Health, player.Stats[PlayerStat.MaxHp], 0f, 0.95f, 1.15f, EasingType.QuadIn ), volume: 1f, maxDist: 5.5f );\r\n\r\n\t\t\t\t\t\tplayer.Damage( dmg );\r\n\r\n\t\t\t\t\t\tif ( dmg > 0f )\r\n\t\t\t\t\t\t\tOnDamagePlayer( player, dmg );\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t_damageTime = 0f;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tpublic override void Celebrate()\r\n\t{\r\n\t\tbase.Celebrate();\r\n\r\n\t\tCelebrateAsync();\r\n\t}\r\n\r\n\tasync void CelebrateAsync()\r\n\t{\r\n\t\tawait Task.Delay( Game.Random.Int( 0, 500 ) );\r\n\r\n\t\tSprite.PlaybackSpeed = Game.Random.Float( 1f, 2.5f );\r\n\r\n\t\tSprite.PlayAnimation( \"cheer_start\" );\r\n\r\n\t\tawait Task.Delay( Game.Random.Int( 200, 400 ) );\r\n\r\n\t\tSprite.PlayAnimation( \"cheer\" );\r\n\t}\r\n}\r\n"
},
{
"Ident": "facepunch.ss1",
"Path": "things/enemies/RunnerEliteSpecial.cs",
"FileName": "RunnerEliteSpecial.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 421898,
"IsPrivate": false,
"Code": "using Sandbox;\r\n\r\npublic class RunnerEliteSpecial : RunnerElite\r\n{\r\n\tpublic override float FullOpacity => 0f;\r\n\r\n\tprotected override void OnAwake()\r\n\t{\r\n\t\tbase.OnAwake();\r\n\r\n\t\tRadius = 0.5075f;\r\n\t\tScale = 2.275f;\r\n\t\tSprite.LocalScale = new Vector3( Scale * Game.Random.Float( 1f - HeightVariance, 1f + HeightVariance ), Scale * Game.Random.Float( 1f - WidthVariance, 1f + WidthVariance ), 1f ) * Globals.SPRITE_SCALE;\r\n\r\n\t\tHealth = 300f;\r\n\t\tMaxHealth = Health;\r\n\r\n\t\tShadowScale = 2.275f;\r\n\t\tShadowFullOpacity = 0f;\r\n\t\tShadowSpriteDirty = true;\r\n\r\n\t\tPushStrength = 55f;\r\n\t\tAggroRange = 2.25f;\r\n\t\t\r\n\t\tDamageToPlayer = 5f;\r\n\r\n\t\tCoinChance = 1f;\r\n\t\tCoinValueMin = 4;\r\n\t\tCoinValueMax = 8;\r\n\r\n\t\tSprite.Tint = Color.White.WithAlpha( FullOpacity );\r\n\r\n\t\tMoveSpeed = 0.35f;\r\n\r\n\t\tJUMP_DELAY_MIN = 4f;\r\n\t\tJUMP_DELAY_MAX = 10f;\r\n\t}\r\n\r\n\tprotected override void SpawnLandingClouds()\r\n\t{\r\n\t\tfor ( int i = 0; i < Game.Random.Int( 2, 3 ); i++ )\r\n\t\t{\r\n\t\t\tvar dir = Utils.GetRandomVector();\r\n\t\t\tSpawnCloudClient( Position2D + dir * Game.Random.Float(0.4f, 0.6f), dir * Game.Random.Float( 0.3f, 1.5f ) );\r\n\t\t}\r\n\t}\r\n\r\n\tpublic override void DealDamage()\r\n\t{\r\n\t\tFlash( 0.1f, Color.Red.WithAlpha( 0.25f ) );\r\n\t}\r\n\r\n\tpublic override void Celebrate()\r\n\t{\r\n\t\tif ( IsSpawning )\r\n\t\t\tFinishSpawning();\r\n\r\n\t\tif ( _isJumping )\r\n\t\t\treturn;\r\n\r\n\t\tCelebrateAsync();\r\n\t}\r\n\r\n\tasync void CelebrateAsync()\r\n\t{\r\n\t\tawait Task.Delay( Game.Random.Int( 0, 500 ) );\r\n\r\n\t\tSprite.PlaybackSpeed = Game.Random.Float( 2f, 4f );\r\n\r\n\t\tSprite.Tint = Color.Red.WithAlpha( 0.25f );\r\n\r\n\t\tSprite.PlayAnimation( \"cheer\" );\r\n\t}\r\n}\r\n"
},
{
"Ident": "facepunch.ss1",
"Path": "things/enemies/Spiker.cs",
"FileName": "Spiker.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 421898,
"IsPrivate": false,
"Code": "using Sandbox;\r\nusing static Sandbox.VertexLayout;\r\n\r\npublic class Spiker : Enemy\r\n{\r\n\tprivate TimeSince _damageTime;\r\n\tprivate const float DAMAGE_TIME = 0.75f;\r\n\r\n\tprivate float _shootDelayTimer;\r\n\tprivate const float SHOOT_DELAY_MIN = 2f;\r\n\tprivate const float SHOOT_DELAY_MAX = 4f;\r\n\r\n\tpublic bool IsShooting { get; private set; }\r\n\tprivate float _shotTimer;\r\n\tprivate const float SHOOT_TIME = 4f;\r\n\tprivate bool _hasShot;\r\n\tprivate TimeSince _prepareStartTime;\r\n\tprivate bool _hasReversed;\r\n\r\n\tpublic override float HeightVariance => 0.02f;\r\n\tpublic override float WidthVariance => 0.01f;\r\n\r\n\tprivate bool _moveClockwise;\r\n\tpublic static int SpikerNum { get; set; }\r\n\tprivate float _perpendicularMaxDist;\r\n\r\n\tprivate float _digDelayTimer;\r\n\tprivate const float DIG_DELAY_MIN = 4f;\r\n\tprivate const float DIG_DELAY_MAX = 13f;\r\n\tpublic bool IsDigging { get; private set; }\r\n\tprivate TimeSince _timeSinceStartDigging;\r\n\tprivate const float DIG_TIME = 1.2f;\r\n\r\n\tpublic float ShootRange { get; set; }\r\n\r\n\tpublic float MoveSpeed { get; set; }\r\n\r\n\tprotected override void OnAwake()\r\n\t{\r\n\t\t//OffsetY = -0.58f;\r\n\t\tShadowScale = 1.2f;\r\n\t\tShadowFullOpacity = 0.8f;\r\n\t\tShadowOpacity = 0f;\r\n\r\n\t\tScale = 1.4f;\r\n\r\n\t\tbase.OnAwake();\r\n\r\n\t\t//AnimSpeed = 4f;\r\n\t\t//Sprite.Texture = Texture.Load(\"textures/sprites/spiker.vtex\");\r\n\r\n\t\tPushStrength = 8f;\r\n\t\tDeceleration = 2.57f;\r\n\t\tDecelerationAttacking = 2.35f;\r\n\t\tAggroRange = 0.75f;\r\n\r\n\t\tRadius = 0.27f;\r\n\r\n\t\tHealth = 80f;\r\n\r\n\t\tif ( Manager.Instance.Difficulty < 0 )\r\n\t\t\tHealth = 70f;\r\n\r\n\t\tMaxHealth = Health;\r\n\t\tDamageToPlayer = 14f;\r\n\r\n\t\tCoinValueMin = 2;\r\n\t\tCoinValueMax = 4;\r\n\t\tCoinChance = 0.75f;\r\n\r\n\t\tSprite.PlayAnimation( AnimSpawnPath );\r\n\r\n\t\tif ( IsProxy )\r\n\t\t\treturn;\r\n\t\t\r\n\t\tCollideWith.Add( typeof( Enemy ) );\r\n\t\tCollideWith.Add( typeof( Player ) );\r\n\r\n\t\t_damageTime = DAMAGE_TIME;\r\n\t\t_shootDelayTimer = Game.Random.Float( SHOOT_DELAY_MIN, SHOOT_DELAY_MAX );\r\n\t\t_digDelayTimer = Game.Random.Float( DIG_DELAY_MIN, DIG_DELAY_MAX );\r\n\r\n\t\t_moveClockwise = SpikerNum % 2 == 0;\r\n\t\tSpikerNum++;\r\n\t\t_perpendicularMaxDist = Game.Random.Float( 3.5f, 6.5f );\r\n\r\n\t\tShootRange = 4.5f;\r\n\r\n\t\tMoveSpeed = 0.9f;\r\n\t}\r\n\r\n\tprotected override void UpdatePosition( float dt )\r\n\t{\r\n\t\tbase.UpdatePosition( dt );\r\n\r\n\t\t//if(!IsDigging)\r\n\t\t//{\r\n\t\t//\tGizmo.Draw.Color = Color.White;\r\n\t\t//\tGizmo.Draw.Text( $\"{_digDelayTimer}\", new global::Transform( (Vector3)Position2D + new Vector3( 0f, -0.4f, 0f ) ) );\r\n\t\t//}\r\n\r\n\t\tvar targetPos = GetTargetPos();\r\n\r\n\t\tif ( IsShooting )\r\n\t\t{\r\n\t\t\tVelocity *= (1f - dt * (IsAttacking ? DecelerationAttacking : Deceleration));\r\n\t\t\tif ( !_hasShot && _prepareStartTime > 1f )\r\n\t\t\t{\r\n\t\t\t\tCreateSpike();\r\n\t\t\t\t_hasShot = true;\r\n\t\t\t}\r\n\r\n\t\t\tif ( !_hasReversed && _prepareStartTime > 3f )\r\n\t\t\t{\r\n\t\t\t\t_hasReversed = true;\r\n\t\t\t\tSprite.PlayAnimation( \"shoot_reverse\" );\r\n\t\t\t}\r\n\r\n\t\t\tVelocity *= (1f - dt * 4f);\r\n\r\n\t\t\t_shotTimer -= dt;\r\n\t\t\tif ( _shotTimer < 0f )\r\n\t\t\t{\r\n\t\t\t\tFinishShooting();\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if ( IsDigging )\r\n\t\t{\r\n\t\t\tVelocity *= (1f - dt * 6f);\r\n\r\n\t\t\tif ( _timeSinceStartDigging > DIG_TIME )\r\n\t\t\t{\r\n\t\t\t\tVector2 pos;\r\n\t\t\t\tif ( Target.IsValid() )\r\n\t\t\t\t{\r\n\t\t\t\t\tpos = targetPos + Target.Velocity * Game.Random.Float( 0f, 2f ) + Utils.GetRandomVector() * Game.Random.Float( 1.5f, 5.5f);\r\n\t\t\t\t\tif ( (Position2D - pos).LengthSquared < MathF.Pow( 0.5f, 2f ) )\r\n\t\t\t\t\t\tpos = Position2D + Utils.GetRandomVector() * Game.Random.Float( 3f, 5f );\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tpos = Position2D + Game.Random.Float( 4f, 5f );\r\n\t\t\t\t}\r\n\r\n\t\t\t\tFinishDigging( Manager.Instance.ClampToBounds( pos ) );\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tfloat progress = Utils.Map( _timeSinceStartDigging, 0f, DIG_TIME, 0f, 1f );\r\n\t\t\t\t//ZPos = Utils.Map( progress, 0f, 1f, 0f, DIG_DEPTH );\r\n\t\t\t\t//PlaybackRate = Utils.Map( progress, 0f, 1f, 0f, 1f ) * _personalSpeedScale;\r\n\r\n\t\t\t\tfloat shadowOpacity = Utils.Map( progress, 0f, 1f, ShadowOpacity, 0f, EasingType.QuadIn );\r\n\t\t\t\tShadowSprite.Tint = Color.Black.WithAlpha( shadowOpacity );\r\n\r\n\t\t\t\tVfxOpacity = Utils.Map( progress, 0f, 1f, 1f, 0f, EasingType.QuadIn );\r\n\r\n\t\t\t\t//Gizmo.Draw.Color = Color.White;\r\n\t\t\t\t//Gizmo.Draw.Text( $\"{progress}\", new global::Transform( (Vector3)Position2D + new Vector3( 0f, -0.4f, 0f ) ) );\r\n\r\n\t\t\t\tIgnoreCollision = progress > 0.3f;\r\n\r\n\t\t\t\tif ( _spawnCloudTime > (0.3f / TimeScale) )\r\n\t\t\t\t{\r\n\t\t\t\t\tvar cloud = Manager.Instance.SpawnCloud( Position2D + new Vector2( 0f, 0.05f ) );\r\n\t\t\t\t\tcloud.Velocity = new Vector2( Game.Random.Float( -1f, 1f ), Game.Random.Float( -1f, 1f ) ).Normal * Game.Random.Float( 0.2f, 0.6f );\r\n\t\t\t\t\t_spawnCloudTime = Game.Random.Float( 0f, 0.15f );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\treturn;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tVector2 toTarget = (targetPos - Position2D).Normal;\r\n\r\n\t\t\tif(Manager.Instance.Difficulty >= 1)\r\n\t\t\t{\r\n\t\t\t\tif ( (targetPos - Position2D).LengthSquared < MathF.Pow( _perpendicularMaxDist, 2f ) )\r\n\t\t\t\t\ttoTarget = Vector2.Lerp( toTarget, new Vector2( toTarget.y, -toTarget.x ) * (_moveClockwise ? -1f : 1f), Utils.Map( (targetPos - Position2D).LengthSquared, MathF.Pow( _perpendicularMaxDist, 2f ), MathF.Pow( 1.5f, 2f ), 0f, 1f ) );\r\n\t\t\t}\r\n\r\n\t\t\tVelocity += toTarget * 1.0f * dt * (IsFeared ? -1f : 1f);\r\n\t\t}\r\n\r\n\t\tfloat speed = MoveSpeed * (IsAttacking ? 1.3f : 0.7f) + Utils.FastSin( MoveTimeOffset + Time.Now * (IsAttacking ? 15f : 7.5f) ) * (IsAttacking ? 0.66f : 0.35f);\r\n\r\n\t\tif ( Manager.Instance.Difficulty < 0 )\r\n\t\t\tspeed *= 0.85f;\r\n\r\n\t\tWorldPosition += (Vector3)Velocity * speed * dt;\r\n\r\n\t\tif ( !IsShooting && !IsDigging && (!IsAttacking || IsCharmed) && !Manager.Instance.IsGameOver )\r\n\t\t{\r\n\t\t\tvar target_dist_sqr = ((Target.IsValid() ? Target.Position2D : targetPos) - Position2D).LengthSquared;\r\n\t\t\tvar rangeSqr = ShootRange * ShootRange;\r\n\r\n\t\t\tif ( Manager.Instance.Difficulty < 0 )\r\n\t\t\t\trangeSqr *= 0.7f;\r\n\r\n\t\t\tif ( target_dist_sqr < rangeSqr )\r\n\t\t\t{\r\n\t\t\t\t_shootDelayTimer -= dt;\r\n\t\t\t\tif ( _shootDelayTimer < 0f )\r\n\t\t\t\t\tStartShooting();\r\n\t\t\t}\r\n\r\n\t\t\tif ( target_dist_sqr < MathF.Pow( 12f, 2f ) && Manager.Instance.Difficulty > 0 )\r\n\t\t\t{\r\n\t\t\t\t_digDelayTimer -= dt;\r\n\t\t\t\tif ( _digDelayTimer < 0f )\r\n\t\t\t\t\tStartDigging();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprotected virtual Vector2 GetTargetPos()\r\n\t{\r\n\t\treturn Target.IsValid() ? Target.Position2D : (IsCharmed ? Manager.Instance.Player.Position2D : Position2D);\r\n\t}\r\n\r\n\tprotected override void UpdateSprite( Thing target )\r\n\t{\r\n\t\tif ( Sprite.CurrentAnimation.Name.Contains( \"shoot\" ) || IsDigging ) \r\n\t\t\treturn;\r\n\r\n\t\tbase.UpdateSprite( target );\r\n\t}\r\n\r\n\tpublic void StartShooting()\r\n\t{\r\n\t\t_shotTimer = SHOOT_TIME;\r\n\t\tIsShooting = true;\r\n\t\tCanAttack = false;\r\n\t\tCanTurn = false;\r\n\t\tCanAttackAnim = false;\r\n\t\t_hasShot = false;\r\n\t\t_hasReversed = false;\r\n\t\t_prepareStartTime = 0f;\r\n\t\tVelocity *= 0.25f;\r\n\t\tDontChangeAnimSpeed = true;\r\n\t\tAnimSpeed = 1f;\r\n\t\tSprite.PlayAnimation( \"shoot\" );\r\n\r\n\t\tShouldUpdateAfterGameOver = true;\r\n\t}\r\n\r\n\tpublic virtual void CreateSpike()\r\n\t{\r\n\t\tvar target_pos = Target.IsValid()\r\n\t\t\t? Target.Position2D + Target.Velocity * Game.Random.Float( 0.2f, 2f ) + new Vector2( Game.Random.Float( -1f, 1f ), Game.Random.Float( -1f, 1f ) ) * 0.8f\r\n\t\t\t: Position2D + Utils.GetRandomVector() * Game.Random.Float( 3f, 6f );\r\n\r\n\t\tvar BUFFER = 0.1f;\r\n\r\n\t\tvar spike = Manager.Instance.SpawnEnemySpike( new Vector2( Math.Clamp( target_pos.x, Manager.Instance.BOUNDS_MIN.x + BUFFER, Manager.Instance.BOUNDS_MAX.x - BUFFER ), Math.Clamp( target_pos.y, Manager.Instance.BOUNDS_MIN.y + BUFFER, Manager.Instance.BOUNDS_MAX.y - BUFFER ) ) );\r\n\r\n\t\tif(IsCharmed)\r\n\t\t{\r\n\t\t\tspike.BecomeCharmed();\r\n\t\t}\r\n\r\n\t\tManager.Instance.PlaySfxNearby( \"spike.prepare\", target_pos, pitch: Game.Random.Float( 0.95f, 1.05f ), volume: 1.5f, maxDist: 5f );\r\n\t}\r\n\r\n\tpublic void FinishShooting()\r\n\t{\r\n\t\t_shootDelayTimer = Game.Random.Float( SHOOT_DELAY_MIN, SHOOT_DELAY_MAX ) * (Manager.Instance.Difficulty < 0 ? 2.5f : 1f);\r\n\t\tIsShooting = false;\r\n\t\tCanAttack = true;\r\n\t\tCanAttackAnim = true;\r\n\t\tCanTurn = true;\r\n\t\tDontChangeAnimSpeed = false;\r\n\t\tSprite.PlayAnimation( AnimIdlePath );\r\n\r\n\t\tShouldUpdateAfterGameOver = false;\r\n\t\tif ( Manager.Instance.IsGameOver && !Manager.Instance.ShouldUpdateThings )\r\n\t\t\tCelebrate();\r\n\t}\r\n\r\n\tpublic override void Colliding( Thing other, float percent, float dt )\r\n\t{\r\n\t\tbase.Colliding( other, percent, dt );\r\n\r\n\t\tif ( other is Enemy enemy && !enemy.IsDying )\r\n\t\t{\r\n\t\t\tvar spawnFactor = Utils.Map( enemy.TimeSinceSpawn, 0f, enemy.SpawnTime, 0f, 1f, EasingType.QuadIn );\r\n\t\t\tVelocity += (Position2D - enemy.Position2D).Normal * Utils.Map( percent, 0f, 1f, 0f, 1f ) * enemy.PushStrength * (1f + enemy.TempWeight) * spawnFactor * dt;\r\n\r\n\t\t\tif ( IsAttacking && IsCharmed != enemy.IsCharmed && _damageTime > (DAMAGE_TIME / TimeScale) )\r\n\t\t\t{\r\n\t\t\t\tvar dmg = DamageToPlayer;\r\n\t\t\t\tif ( IsCharmed )\r\n\t\t\t\t\tdmg *= CharmDamageDealtMultiplier;\r\n\r\n\t\t\t\tenemy.Damage( dmg, null, addVel: Vector2.Zero, addTempWeight: 0f, isCrit: false, DamageType.Melee );\r\n\t\t\t\tenemy.Target = this;\r\n\t\t\t\t_damageTime = 0f;\r\n\t\t\t\tManager.Instance.PlaySfxNearby( \"zombie.attack.player\", Position2D, pitch: Utils.Map( enemy.Health, enemy.MaxHealth, 0f, 0.95f, 1.15f, EasingType.QuadIn ), volume: 0.6f, maxDist: 4.5f );\r\n\t\t\t}\r\n\t\t}\r\n\t\t// todo: move collision check to player instead to prevent laggy hits?\r\n\t\telse if ( other is Player player )\r\n\t\t{\r\n\t\t\tif ( !player.IsDead )\r\n\t\t\t{\r\n\t\t\t\tVelocity += (Position2D - player.Position2D).Normal * Utils.Map( percent, 0f, 1f, 0f, 1f ) * player.Stats[PlayerStat.PushStrength] * (1f + player.TempWeight) * dt;\r\n\r\n\t\t\t\tif ( IsAttacking && _damageTime > (DAMAGE_TIME / TimeScale) && !IsCharmed )\r\n\t\t\t\t{\r\n\t\t\t\t\tfloat dmg = player.CheckDamageAmount( DamageToPlayer, DamageType.Melee );\r\n\r\n\t\t\t\t\tif ( !player.IsInvulnerable && !player.IsTimePausedForChoosing )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tManager.Instance.PlaySfxNearby( \"zombie.attack.player\", Position2D, pitch: Utils.Map( player.Health, player.Stats[PlayerStat.MaxHp], 0f, 0.95f, 1.15f, EasingType.QuadIn ), volume: 1f, maxDist: 5.5f );\r\n\r\n\t\t\t\t\t\tplayer.Damage( dmg );\r\n\r\n\t\t\t\t\t\tif( dmg > 0f )\r\n\t\t\t\t\t\t\tOnDamagePlayer( player, dmg );\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t_damageTime = 0f;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tpublic override void Damage( float damage, Player player, Vector2 addVel, float addTempWeight, bool isCrit = false, DamageType damageType = DamageType.PlayerBullet )\r\n\t{\r\n\t\tbase.Damage( damage, player, addVel, addTempWeight, isCrit, damageType );\r\n\r\n\t\tif ( Game.Random.Float( 0f, 1f ) < Utils.Map( damage, 1f, 20f, 0.1f, 0.7f ) )\r\n\t\t\t_digDelayTimer *= Game.Random.Float( 0.6f, 0.95f );\r\n\t}\r\n\r\n\tpublic void StartDigging()\r\n\t{\r\n\t\tIsDigging = true;\r\n\t\t_timeSinceStartDigging = 0f;\r\n\t\tCanAttack = false;\r\n\t\tCanAttackAnim = false;\r\n\t\tCanTurn = false;\r\n\t\tVelocity *= 0.5f;\r\n\t\tSprite.PlayAnimation( \"dig\" );\r\n\t\tManager.Instance.PlaySfxNearby( \"zombie.dirt\", Position2D, pitch: Game.Random.Float( 0.5f, 0.55f ), volume: 0.6f, maxDist: 7.5f );\r\n\t\t//SS2Game.PlaySfx( \"zombie.dirt\", Position, pitch: Game.Random.Float( 0.6f, 0.8f ), volume: 0.7f );\r\n\t\t//SS2Game.Current.DustCloudClient( Position2D );\r\n\r\n\t\tShouldUpdateAfterGameOver = true;\r\n\t}\r\n\r\n\tvoid FinishDigging( Vector2 pos )\r\n\t{\r\n\t\tPosition2D = pos;\r\n\t\t//WorldPosition = ((Vector3)Position2D).WithZ( ZPos );\r\n\r\n\t\tTransform.ClearInterpolation();\r\n\r\n\t\tIsDigging = false;\r\n\t\tCanAttack = true;\r\n\t\tCanAttackAnim = true;\r\n\t\tCanTurn = true;\r\n\t\tIgnoreCollision = false;\r\n\t\t//SetAnim( \"Attack1\" );\r\n\r\n\t\t_moveClockwise = !_moveClockwise;\r\n\r\n\t\t_digDelayTimer = Game.Random.Float( DIG_DELAY_MIN, DIG_DELAY_MAX );\r\n\r\n\t\t//SS2Game.PlaySfx( \"zombie.dirt\", Position, pitch: Game.Random.Float( 0.6f, 0.8f ), volume: 0.7f );\r\n\t\t//SS2Game.Current.DustCloudClient( Position2D );\r\n\r\n\t\tIsSpawning = true;\r\n\t\t//_hasDug = true;\r\n\t\tTimeSinceSpawn = 0f;\r\n\t\t//SpawnProgress = 0f;\r\n\r\n\t\t//ShadowRadiusModifier = 1.5f;\r\n\t\t//ShadowOpacityModifier = 0f;\r\n\r\n\t\tManager.Instance.PlaySfxNearby( \"zombie.dirt\", Position2D, pitch: Game.Random.Float( 0.85f, 0.9f ), volume: 0.6f, maxDist: 7.5f );\r\n\r\n\t\tShadowSprite.Tint = Color.Black.WithAlpha( 0f );\r\n\t\tVfxOpacity = 0f;\r\n\t}\r\n\r\n\tpublic override void Celebrate()\r\n\t{\r\n\t\tbase.Celebrate();\r\n\r\n\t\tif ( IsShooting || IsDigging )\r\n\t\t\treturn;\r\n\r\n\t\tCelebrateAsync();\r\n\t}\r\n\r\n\tasync void CelebrateAsync()\r\n\t{\r\n\t\tawait Task.Delay( Game.Random.Int( 0, 500 ) );\r\n\r\n\t\tSprite.PlaybackSpeed = Game.Random.Float( 1.5f, 3.5f );\r\n\r\n\t\tSprite.PlayAnimation( \"cheer_start\" );\r\n\r\n\t\tawait Task.Delay( Game.Random.Int( 200, 400 ) );\r\n\r\n\t\tSprite.PlayAnimation( \"cheer\" );\r\n\t}\r\n\r\n\tprotected override void FinishSpawning()\r\n\t{\r\n\t\tbase.FinishSpawning();\r\n\r\n\t\tShouldUpdateAfterGameOver = false;\r\n\t\tif ( Manager.Instance.IsGameOver && !Manager.Instance.ShouldUpdateThings )\r\n\t\t\tCelebrate();\r\n\t}\r\n}\r\n"
},
{
"Ident": "facepunch.ss1",
"Path": "things/Player.cs",
"FileName": "Player.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 421898,
"IsPrivate": false,
"Code": "using static Manager;\r\n\r\npublic enum ModifierType { Set, Add, Mult }\r\npublic class ModifierData\r\n{\r\n\tpublic float value;\r\n\tpublic ModifierType type;\r\n\tpublic float priority;\r\n\r\n\tpublic ModifierData( float _value, ModifierType _type, float _priority = 0f )\r\n\t{\r\n\t\tvalue = _value;\r\n\t\ttype = _type;\r\n\t\tpriority = _priority;\r\n\t}\r\n}\r\n\r\npublic enum PlayerStat\r\n{\r\n\tAttackTime, AttackSpeed, ReloadTime, ReloadSpeed, MaxAmmoCount, BulletDamage, BulletForce, Recoil, MoveSpeed, NumProjectiles, BulletSpread, BulletInaccuracy, BulletSpeed, BulletLifetime,\r\n\tBulletNumPiercing, CritChance, CritMultiplier, LowHealthDamageMultiplier, NumUpgradeChoices, HealthRegen, HealthRegenStill, DamageReductionPercent, PushStrength, CoinAttractRange, CoinAttractStrength, Luck, MaxHp,\r\n\tNumDashes, DashInvulnTime, DashCooldown, DashProgress, DashStrength, ThornsPercent, ShootFireIgniteChance, FireDamage, FireLifetime, FireSpreadChance, ShootFreezeChance, FreezeLifetime,\r\n\tFreezeTimeScale, FreezeOnMeleeChance, FreezeFireDamageMultiplier, LastAmmoDamageMultiplier, FearLifetime, FearDamageMultiplier, FearOnMeleeChance, BulletDamageGrow, BulletDamageShrink,\r\n\tBulletDistanceDamage, NumRerollsPerLevel, FullHealthDamageMultiplier, DamagePerEarlierShot, DamageForSpeed, OverallDamageMultiplier, ExplosionSizeMultiplier, GrenadeVelocity, ExplosionDamageMultiplier,\r\n\tBulletDamageMultiplier, ExplosionDamageReductionPercent, NonExplosionDamageIncreasePercent, GrenadeStickyPercent, GrenadeFearChance, FearDrainPercent, FearPainPercent, CrateChanceAdditional,\r\n\tAttackSpeedStill, FearDropGrenadeChance, FrozenShardsNum, NoDashInvuln, BulletFlatDamageAddition, GrenadesCanCrit, BulletHealTeammateAmount, HomingBulletChance, PauseWhileChoosing,\r\n\tBulletNumBouncing, DashCharm, CharmedEnemyDmgTakenMultiplier, CharmedEnemyDmgDealtMultiplier, BossArrivalTime, RadiusMultiplier, SpecialistStatusAmount, ZoomAmount, MaxFireStacks, XpRepel, HealthPackAmount,\r\n\tMaxBulletSpread, ShootRandomDirChance, DashRandomDirChance, MoveSelfDmg, MoveSelfDmgReqDist, MoveSelfDmgAmount, SelfDmgDistanceMoved, IncreasedDmgTaken, ReverseControls, SelfCritChance,\r\n}\r\n\r\npublic enum DamageType { Melee, Ranged, Explosion, Fire, PlayerBullet, Self, Generic, LavaPuddle, FearPain, }\r\npublic enum PlayerDamageType { Enemy, Self, Grenade }\r\n\r\npublic struct CamShakeData\r\n{\r\n\tpublic float strength;\r\n\tpublic float startTime;\r\n\tpublic float time;\r\n\tpublic EasingType easingType;\r\n\tpublic bool useRealTime;\r\n\r\n\tpublic CamShakeData( float _strength, float _startTime, float _time, EasingType _easingType, bool _useRealTime )\r\n\t{\r\n\t\tstrength = _strength;\r\n\t\tstartTime = _startTime;\r\n\t\ttime = _time;\r\n\t\teasingType = _easingType;\r\n\t\tuseRealTime = _useRealTime;\r\n\t}\r\n}\r\n\r\npublic class Player : Thing\r\n{\r\n\t[Property] public GameObject Body { get; set; }\r\n\t[Property] public GameObject ArrowAimerPrefab { get; set; }\r\n\t[Property] public GameObject BulletPrefab { get; set; }\r\n\t[Property] public Sprite EasySprite { get; set; }\r\n\t[Property] public Sprite Difficulty1Sprite { get; set; }\r\n\t[Property] public Sprite Difficulty2Sprite { get; set; }\r\n\t[Property] public Sprite Difficulty3Sprite { get; set; }\r\n\t[Property] public Sprite Difficulty4Sprite { get; set; }\r\n\t[Property] public Sprite Difficulty5Sprite { get; set; }\r\n\t[Property] public Sprite Difficulty6Sprite { get; set; }\r\n\t[Property] public Sprite Difficulty7Sprite { get; set; }\r\n\t[Property] public Sprite Difficulty8Sprite { get; set; }\r\n\t[Property] public Sprite Difficulty9Sprite { get; set; }\r\n\t[Property] public Sprite Difficulty10Sprite { get; set; }\r\n\t[Property] public Sprite Difficulty11Sprite { get; set; }\r\n\t[Property] public Sprite Difficulty12Sprite { get; set; }\r\n\t[Property] public Sprite Difficulty13Sprite { get; set; }\r\n\t[Property] public Sprite Difficulty14Sprite { get; set; }\r\n\t[Property] public Sprite Difficulty15Sprite { get; set; }\r\n\r\n\r\n\t[Sync] public float Health { get; set; }\r\n\tprivate float _regenHpAccumulated;\r\n\tprivate float _drainHpAccumulated;\r\n\r\n\t[Sync] public Vector2 InputVector { get; set; }\r\n\r\n\tpublic GameObject ArrowAimer { get; private set; }\r\n\tpublic Sprite2D ArrowSprite { get; private set; }\r\n\tpublic Vector2 AimDir { get; private set; }\r\n\tprivate Vector2 _controllerAimTarget;\r\n\tprivate const float CONTROLLER_AIM_DEADZONE = 0.25f;\r\n\r\n\t[Sync] public bool IsDead { get; private set; }\r\n\tpublic float Timer { get; protected set; }\r\n\t[Sync] public bool IsReloading { get; protected set; }\r\n\t[Sync] public float ReloadProgress { get; protected set; }\r\n\r\n\tpublic const float BASE_MOVE_SPEED = 15f;\r\n\tprivate int _shotNum;\r\n\r\n\t[Sync] public int Level { get; protected set; }\r\n\tpublic int ExperienceTotal { get; protected set; }\r\n\tpublic int ExperienceCurrent { get; protected set; }\r\n\tpublic int ExperienceRequired { get; protected set; }\r\n\tpublic bool IsChoosingLevelUpReward { get; protected set; }\r\n\tpublic TimeSince TimeSinceLevelUp { get; set; }\r\n\tpublic RealTimeSince RealTimeSinceChoseUpgrade { get; set; }\r\n\tpublic List<Status> LevelUpChoices { get; private set; }\r\n\r\n\t[Sync] public float DashTimer { get; private set; }\r\n\t[Sync] public bool IsDashing { get; private set; }\r\n\t[Sync] public Vector2 DashVelocity { get; private set; }\r\n\t[Sync] public float DashInvulnTimer { get; private set; }\r\n\tprivate TimeSince _dashCloudTime;\r\n\tpublic float DashProgress { get; protected set; }\r\n\t[Sync] public float DashRechargeProgress { get; protected set; }\r\n\t[Sync] public int NumDashesAvailable { get; set; }\r\n\tpublic int AmmoCount { get; protected set; }\r\n\r\n\tpublic bool IsMoving => Velocity.LengthSquared > 0.05f && !IsDashing;\r\n\t//public bool IsMoving => (Position2D - _lastPos2D).LengthSquared > 0.000001f;\r\n\tpublic bool IsInputtingMove => Input.AnalogMove.LengthSquared > 0.01f;\r\n\tpublic bool IsInvulnerable => IsDashing && Stats[PlayerStat.NoDashInvuln] <= 0f;\r\n\tpublic bool IsTimePausedForChoosing => IsChoosingLevelUpReward && Stats[PlayerStat.PauseWhileChoosing] > 0f;\r\n\r\n\r\n\tprivate float _flashTimer;\r\n\tprivate bool _isFlashing;\r\n\tpublic TimeSince TimeSinceHurt { get; private set; }\r\n\tpublic TimeSince TimeSinceChangeHP { get; set; }\r\n\r\n\tprivate GameObject _shieldVfx;\r\n\r\n\t[Sync] public int NumRerollAvailable { get; set; }\r\n\r\n\t[Sync] public NetDictionary<PlayerStat, float> Stats { get; private set; } = new();\r\n\r\n\tpublic Dictionary<int, Status> Statuses { get; private set; }\r\n\r\n\tprivate Dictionary<Status, Dictionary<PlayerStat, ModifierData>> _modifiers_stat = new Dictionary<Status, Dictionary<PlayerStat, ModifierData>>();\r\n\tprivate Dictionary<PlayerStat, float> _original_properties_stat = new Dictionary<PlayerStat, float>();\r\n\r\n\tprivate bool _doneFirstUpdate;\r\n\tprivate TimeSince _timeSinceShoot;\r\n\tprivate TimeSince _timeSinceSpawn;\r\n\r\n\tprivate Vector2 _lastPos2D;\r\n\tprivate TimeSince _timeSinceTouchLeftSide;\r\n\tprivate bool _hasUnlockedSprinterAchievement;\r\n\r\n\tprivate bool _hasUnlockedExperiencedAchievement;\r\n\r\n\tpublic TimeSince TimeSinceInputMove { get; set; }\r\n\tprivate bool _hasUnlockedNoMoveAchievement;\r\n\r\n\tprivate List<CamShakeData> _camShakeDatas = new();\r\n\tpublic float CamShakeAmount { get; set; }\r\n\r\n\tpublic int ChoiceHash { get; set; }\r\n\r\n\tpublic TimeSince TimeSinceHurtLava { get; set; }\r\n\r\n\tpublic RealTimeSince RealTimeSinceDeath { get; set; }\r\n\tprivate float _arrowDeathAlphaStart;\r\n\tprivate bool _hasPlayedDeathSfx;\r\n\r\n\tprotected override void OnAwake()\r\n\t{\r\n\t\tbase.OnAwake();\r\n\r\n\t\tScale = 1f;\r\n\r\n\t\tShadowOpacity = 0.8f;\r\n\t\tShadowScale = 1.12f;\r\n\r\n\t\tStatuses = new Dictionary<int, Status>();\r\n\t\tLevelUpChoices = new List<Status>();\r\n\t\tInitializeStats();\r\n\r\n\t\tif ( IsProxy )\r\n\t\t\treturn;\r\n\r\n\t\tCollideWith.Add( typeof( Enemy ) );\r\n\t\tCollideWith.Add( typeof( Player ) );\r\n\r\n\t\tArrowAimer = ArrowAimerPrefab.Clone( WorldPosition );\r\n\t\tArrowAimer.SetParent( GameObject );\r\n\t\tArrowAimer.NetworkMode = NetworkMode.Never;\r\n\t\tArrowSprite = ArrowAimer.Components.Get<Sprite2D>();\r\n\t\tArrowSprite.Tint = Color.White.WithAlpha( 0f );\r\n\r\n\t\t_timeSinceShoot = 999f;\r\n\t\t_timeSinceSpawn = 0f;\r\n\r\n\t\tif ( Manager.Instance.Difficulty < 0 ) Sprite.Sprite = EasySprite;\r\n\t\telse if ( Manager.Instance.Difficulty == 1 ) Sprite.Sprite = Difficulty1Sprite;\r\n\t\telse if ( Manager.Instance.Difficulty == 2 ) Sprite.Sprite = Difficulty2Sprite;\r\n\t\telse if ( Manager.Instance.Difficulty == 3 ) Sprite.Sprite = Difficulty3Sprite;\r\n\t\telse if ( Manager.Instance.Difficulty == 4 ) Sprite.Sprite = Difficulty4Sprite;\r\n\t\telse if ( Manager.Instance.Difficulty == 5 ) Sprite.Sprite = Difficulty5Sprite;\r\n\t\telse if ( Manager.Instance.Difficulty == 6 ) Sprite.Sprite = Difficulty6Sprite;\r\n\t\telse if ( Manager.Instance.Difficulty == 7 ) Sprite.Sprite = Difficulty7Sprite;\r\n\t\telse if ( Manager.Instance.Difficulty == 8 ) Sprite.Sprite = Difficulty8Sprite;\r\n\t\telse if ( Manager.Instance.Difficulty == 9 ) Sprite.Sprite = Difficulty9Sprite;\r\n\t\telse if ( Manager.Instance.Difficulty == 10 ) Sprite.Sprite = Difficulty10Sprite;\r\n\t\telse if ( Manager.Instance.Difficulty == 11 ) Sprite.Sprite = Difficulty11Sprite;\r\n\t\telse if ( Manager.Instance.Difficulty == 12 ) Sprite.Sprite = Difficulty12Sprite;\r\n\t\telse if ( Manager.Instance.Difficulty == 13 ) Sprite.Sprite = Difficulty13Sprite;\r\n\t\telse if ( Manager.Instance.Difficulty == 14 ) Sprite.Sprite = Difficulty14Sprite;\r\n\t\telse if ( Manager.Instance.Difficulty == 15 ) Sprite.Sprite = Difficulty15Sprite;\r\n\r\n\t\t//Sprite.LocalScale = 0.5f * Globals.SPRITE_SCALE;\r\n\t}\r\n\r\n\tpublic void InitializeStats()\r\n\t{\r\n\t\t_original_properties_stat.Clear();\r\n\r\n\t\tif ( Network.Active )\r\n\t\t{\r\n\t\t\tRemoveShieldVfx();\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tif ( _shieldVfx != null )\r\n\t\t\t{\r\n\t\t\t\t_shieldVfx.Destroy();\r\n\t\t\t\t_shieldVfx = null;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tLevel = 0;\r\n\t\tExperienceRequired = GetExperienceReqForLevel( Level + 1 );\r\n\t\tExperienceTotal = 0;\r\n\t\tExperienceCurrent = 0;\r\n\t\tStats[PlayerStat.AttackTime] = 0.15f;\r\n\t\tAmmoCount = 5;\r\n\t\tStats[PlayerStat.MaxAmmoCount] = AmmoCount;\r\n\t\tStats[PlayerStat.ReloadTime] = 1.5f;\r\n\t\tStats[PlayerStat.ReloadSpeed] = 1f;\r\n\t\tStats[PlayerStat.AttackSpeed] = 1f;\r\n\t\tStats[PlayerStat.BulletDamage] = 5f;\r\n\t\tStats[PlayerStat.BulletForce] = 0.55f;\r\n\t\tStats[PlayerStat.Recoil] = 0f;\r\n\t\tStats[PlayerStat.MoveSpeed] = 1f;\r\n\t\tStats[PlayerStat.NumProjectiles] = 1f;\r\n\t\tStats[PlayerStat.BulletSpread] = 35f;\r\n\t\tStats[PlayerStat.BulletInaccuracy] = 5f;\r\n\t\tStats[PlayerStat.BulletSpeed] = 4.5f;\r\n\t\tStats[PlayerStat.BulletLifetime] = 0.8f;\r\n\t\tStats[PlayerStat.Luck] = 1f;\r\n\t\tStats[PlayerStat.CritChance] = 0.05f;\r\n\t\tStats[PlayerStat.CritMultiplier] = 1.5f;\r\n\t\tStats[PlayerStat.LowHealthDamageMultiplier] = 1f;\r\n\t\tStats[PlayerStat.FullHealthDamageMultiplier] = 1f;\r\n\t\tStats[PlayerStat.ThornsPercent] = 0f;\r\n\r\n\t\tStats[PlayerStat.NumDashes] = 1f;\r\n\t\tNumDashesAvailable = (int)MathF.Round( Stats[PlayerStat.NumDashes] );\r\n\t\tStats[PlayerStat.DashCooldown] = 3f;\r\n\t\tStats[PlayerStat.DashInvulnTime] = 0.25f;\r\n\t\tStats[PlayerStat.DashStrength] = 3f;\r\n\t\tStats[PlayerStat.BulletNumPiercing] = 0f;\r\n\t\tStats[PlayerStat.BulletNumBouncing] = 0f;\r\n\t\tStats[PlayerStat.DashCharm] = 0f;\r\n\t\tStats[PlayerStat.CharmedEnemyDmgTakenMultiplier] = 1f;\r\n\t\tStats[PlayerStat.CharmedEnemyDmgDealtMultiplier] = 1f;\r\n\t\tStats[PlayerStat.BossArrivalTime] = 15 * 60f;\r\n\t\t//Stats[PlayerStat.BossArrivalTime] = (Manager.Instance.Difficulty >= 5 ? 10 : 15) * 60f;\r\n\t\tStats[PlayerStat.RadiusMultiplier] = 1f;\r\n\t\tStats[PlayerStat.SpecialistStatusAmount] = 0f;\r\n\t\tStats[PlayerStat.ZoomAmount] = 0f;\r\n\t\tStats[PlayerStat.MaxFireStacks] = 0f;\r\n\t\tStats[PlayerStat.XpRepel] = 0f;\r\n\t\tStats[PlayerStat.HealthPackAmount] = 20f;\r\n\t\tStats[PlayerStat.MaxBulletSpread] = 0f;\r\n\t\tStats[PlayerStat.ShootRandomDirChance] = 0f;\r\n\t\tStats[PlayerStat.DashRandomDirChance] = 0f;\r\n\t\tStats[PlayerStat.MoveSelfDmg] = 0f;\r\n\t\tStats[PlayerStat.MoveSelfDmgReqDist] = 0f;\r\n\t\tStats[PlayerStat.MoveSelfDmgAmount] = 0f;\r\n\t\tStats[PlayerStat.SelfDmgDistanceMoved] = 0f;\r\n\t\tStats[PlayerStat.IncreasedDmgTaken] = 0f;\r\n\t\tStats[PlayerStat.ReverseControls] = 0f;\r\n\t\tStats[PlayerStat.SelfCritChance] = 0f;\r\n\r\n\t\tHealth = 100f;\r\n\t\tStats[PlayerStat.MaxHp] = 100f;\r\n\t\t//Health = 1f;\r\n\t\t_regenHpAccumulated = 0f;\r\n\t\t_drainHpAccumulated = 0f;\r\n\r\n\t\tIsDead = false;\r\n\t\tRadius = 0.10f;\r\n\t\tGridPos = Manager.Instance.GetGridSquareForPos( Position2D );\r\n\t\tAimDir = new Vector2( 0f, 1f );\r\n\t\t_controllerAimTarget = Vector2.Zero;\r\n\t\tNumRerollAvailable = Manager.Instance.Difficulty < 0 ? 4 : (Manager.Instance.Difficulty >= 3 ? 3 : 2);\r\n\r\n\t\tStats[PlayerStat.FireDamage] = 1f;\r\n\t\tStats[PlayerStat.FireLifetime] = 3f;\r\n\t\tStats[PlayerStat.ShootFireIgniteChance] = 0f;\r\n\t\tStats[PlayerStat.FireSpreadChance] = 0f;\r\n\t\tStats[PlayerStat.ShootFreezeChance] = 0f;\r\n\t\tStats[PlayerStat.FreezeLifetime] = 4f;\r\n\t\tStats[PlayerStat.FreezeTimeScale] = 0.55f;\r\n\t\tStats[PlayerStat.FreezeOnMeleeChance] = 0f;\r\n\t\tStats[PlayerStat.FreezeFireDamageMultiplier] = 1f;\r\n\t\tStats[PlayerStat.FearLifetime] = 4.5f;\r\n\t\tStats[PlayerStat.FearDamageMultiplier] = 1f;\r\n\t\tStats[PlayerStat.FearOnMeleeChance] = 0f;\r\n\r\n\t\tStats[PlayerStat.CoinAttractRange] = 1.7f;\r\n\t\tStats[PlayerStat.CoinAttractStrength] = 3.5f;\r\n\r\n\t\tStats[PlayerStat.NumUpgradeChoices] = 3f;\r\n\t\tStats[PlayerStat.HealthRegen] = Manager.Instance.Difficulty == -1 ? 0.4f : 0f;\r\n\t\tStats[PlayerStat.HealthRegenStill] = 0f;\r\n\t\t//Stats[PlayerStat.HealthDrain] = 0f;\r\n\t\tStats[PlayerStat.DamageReductionPercent] = 0f;\r\n\t\tStats[PlayerStat.PushStrength] = 30f;\r\n\t\tStats[PlayerStat.LastAmmoDamageMultiplier] = 1f;\r\n\t\tStats[PlayerStat.BulletDamageGrow] = 0f;\r\n\t\tStats[PlayerStat.BulletDamageShrink] = 0f;\r\n\t\tStats[PlayerStat.BulletDistanceDamage] = 0f;\r\n\t\tStats[PlayerStat.NumRerollsPerLevel] = 1f;\r\n\t\tStats[PlayerStat.DamagePerEarlierShot] = 0f;\r\n\t\tStats[PlayerStat.DamageForSpeed] = 0f;\r\n\t\tStats[PlayerStat.OverallDamageMultiplier] = 1f;\r\n\t\tStats[PlayerStat.ExplosionSizeMultiplier] = 1f;\r\n\t\tStats[PlayerStat.GrenadeVelocity] = 8f;\r\n\t\tStats[PlayerStat.ExplosionDamageMultiplier] = 1f;\r\n\t\tStats[PlayerStat.BulletDamageMultiplier] = 1f;\r\n\t\tStats[PlayerStat.ExplosionDamageReductionPercent] = 0f;\r\n\t\tStats[PlayerStat.NonExplosionDamageIncreasePercent] = 0f;\r\n\t\tStats[PlayerStat.GrenadeStickyPercent] = 0f;\r\n\t\tStats[PlayerStat.GrenadeFearChance] = 0f;\r\n\t\tStats[PlayerStat.FearDrainPercent] = 0f;\r\n\t\tStats[PlayerStat.FearPainPercent] = 0f;\r\n\t\tStats[PlayerStat.CrateChanceAdditional] = 0f;\r\n\t\tStats[PlayerStat.AttackSpeedStill] = 1f;\r\n\t\tStats[PlayerStat.FearDropGrenadeChance] = 0f;\r\n\t\tStats[PlayerStat.FrozenShardsNum] = 0f;\r\n\t\tStats[PlayerStat.NoDashInvuln] = 0f;\r\n\t\tStats[PlayerStat.BulletFlatDamageAddition] = 0f;\r\n\t\tStats[PlayerStat.GrenadesCanCrit] = 0f;\r\n\t\tStats[PlayerStat.BulletHealTeammateAmount] = 0f;\r\n\t\tStats[PlayerStat.HomingBulletChance] = 0f;\r\n\t\tStats[PlayerStat.PauseWhileChoosing] = 0f;\r\n\r\n\t\tStatuses.Clear();\r\n\t\t_modifiers_stat.Clear();\r\n\r\n\t\t_isFlashing = false;\r\n\t\tSprite.FlashTint = Color.White.WithAlpha( 0f );\r\n\t\tSprite.Tint = Color.White;\r\n\t\tIsChoosingLevelUpReward = false;\r\n\t\tIsDashing = false;\r\n\t\tIsReloading = true;\r\n\t\tTimer = Stats[PlayerStat.ReloadTime];\r\n\t\tReloadProgress = 0f;\r\n\t\tDashProgress = 0f;\r\n\t\tDashRechargeProgress = 1f;\r\n\t\tTempWeight = 0f;\r\n\t\t_shotNum = 0;\r\n\t\tTimeSinceHurt = 999f;\r\n\t\tTimeSinceChangeHP = 999f;\r\n\t\tTimeSinceHurtLava = 999f;\r\n\t\tShadowOpacity = 0.8f;\r\n\t\tShadowSpriteDirty = true;\r\n\r\n\t\t_timeSinceTouchLeftSide = 999f;\r\n\t\tTimeSinceInputMove = 0f;\r\n\r\n\t\tTimeSinceLevelUp = 999f;\r\n\t\tRealTimeSinceChoseUpgrade = 999f;\r\n\r\n\t\t_camShakeDatas.Clear();\r\n\r\n\t\tChoiceHash = 0;\r\n\t}\r\n\r\n\tpublic Vector2 AverageVelocity { get; private set; }\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tbase.OnUpdate();\r\n\r\n\t\tAverageVelocity += Velocity * Time.Delta * 8f;\r\n\t\t//AverageVelocity = Utils.DynamicEaseTo( AverageVelocity, Vector2.Zero, 0.02f, Time.Delta );\r\n\t\tAverageVelocity *= (1f - Time.Delta * 2.7f);\r\n\r\n\t\t//Gizmo.Draw.Color = Color.Red.WithAlpha( 0.5f );\r\n\t\t//Gizmo.Draw.Line( Position2D, Position2D + AverageVelocity );\r\n\r\n\t\t//Gizmo.Draw.Color = Color.Red.WithAlpha( 0.5f );\r\n\t\t//Gizmo.Draw.Line( Position2D, Position2D + AverageVelocity );\r\n\r\n\t\tVector2 anchor = Position2D + AverageVelocity * 0.1f;\r\n\t\tVector2 perp = Utils.GetPerpendicularVector( AverageVelocity ).Normal;\r\n\t\tvar perpA = anchor - perp * 10f;\r\n\t\tvar perpB = anchor + perp * 10f;\r\n\r\n\t\t//Gizmo.Draw.Color = Color.Green.WithAlpha( 0.3f );\r\n\t\t//Gizmo.Draw.Line( perpA, perpB );\r\n\r\n\t\t//Gizmo.Draw.Color = Color.White.WithAlpha( 0.03f );\r\n\t\t//Gizmo.Draw.LineSphere( (Vector3)Position2D, 2, 16 );\r\n\r\n\t\t//Gizmo.Draw.Color = Color.White.WithAlpha( 0.5f );\r\n\t\t//Gizmo.Draw.Text( $\"{Stats[PlayerStat.FearPainPercent]}\", new global::Transform( (Vector3)Position2D + new Vector3( 0f, -0.4f, 0f ) ) );\r\n\r\n\t\tif ( !_doneFirstUpdate )\r\n\t\t{\r\n\t\t\tSpawnShadow( ShadowScale, ShadowOpacity );\r\n\t\t\tManager.Instance.Camera2D.SetPos( Position2D );\r\n\r\n\t\t\t_doneFirstUpdate = true;\r\n\t\t}\r\n\r\n\t\tInputVector = new Vector2( -Input.AnalogMove.y, Input.AnalogMove.x );\r\n\r\n\t\tif ( Stats[PlayerStat.ReverseControls] > 0f )\r\n\t\t\tInputVector *= -1f;\r\n\r\n\t\t//if ( Input.Pressed( \"Menu\" ) )\r\n\t\t//{\r\n\t\t//\tManager.Instance.Restart();\r\n\t\t//\treturn;\r\n\t\t//}\r\n\r\n\t\tHandleCamShaking();\r\n\r\n\t\tif ( IsDead )\r\n\t\t{\r\n\t\t\tSprite.FlashTint = RealTimeSinceDeath < 0.1f\r\n\t\t\t\t? Color.Red\r\n\t\t\t\t: Color.White.WithAlpha( 0f );\r\n\r\n\t\t\tShadowOpacity = Utils.Map( RealTimeSinceDeath, 0f, Manager.FINAL_PANEL_WAIT_TIME, 0.8f, 0f, EasingType.Linear );\r\n\t\t\tShadowSprite.Tint = Color.Black.WithAlpha( ShadowOpacity );\r\n\r\n\t\t\tArrowSprite.Tint = Color.White.WithAlpha( Utils.Map( RealTimeSinceDeath, 0f, 0.4f, _arrowDeathAlphaStart, 0f, EasingType.SineOut ) );\r\n\r\n\t\t\tif ( !_hasPlayedDeathSfx && RealTimeSinceDeath > 0.7f )\r\n\t\t\t{\r\n\t\t\t\tManager.Instance.PlaySfxNearby( \"zombie.attack.player\", Position2D, pitch: Game.Random.Float( 0.55f, 0.65f ), volume: 1f, maxDist: 5.5f );\r\n\t\t\t\t_hasPlayedDeathSfx = true;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( !Manager.Instance.ShouldUpdatePlayer )\r\n\t\t\treturn;\r\n\r\n\t\tfloat dt = Time.Delta;\r\n\r\n\t\tif ( MathF.Abs( Velocity.x ) > 0.01f )\r\n\t\t\tSprite.SpriteFlags = Velocity.x > 0f ? SpriteFlags.HorizontalFlip : SpriteFlags.None;\r\n\r\n\t\tbool hurting = TimeSinceHurt < 0.25f;\r\n\t\tbool attacking = !IsReloading;\r\n\t\tbool moving = Velocity.LengthSquared > 0.01f && InputVector.LengthSquared > 0.1f;\r\n\r\n\t\tstring stateStr = \"\";\r\n\t\tif ( IsDead )\r\n\t\t\tstateStr = \"ghost_\";\r\n\t\telse if ( hurting && attacking )\r\n\t\t\tstateStr = \"hurt_attack_\";\r\n\t\telse if ( hurting )\r\n\t\t\tstateStr = \"hurt_\";\r\n\t\telse if ( attacking )\r\n\t\t\tstateStr = \"attack_\";\r\n\r\n\t\tSprite.PlayAnimation( $\"{stateStr}{(moving ? \"walk\" : \"idle\")}\" );\r\n\t\tSprite.PlaybackSpeed = moving ? Utils.Map( Velocity.Length, 0f, 2f, 1.5f, 2f ) : 0.66f;\r\n\r\n\t\tSprite.LocalRotation = new Angles( 0f, -90f + (Velocity.Length * Utils.FastSin( Time.Now * MathF.PI * 6f ) * 1.6f) * (Sprite.SpriteFlags.HasFlag( SpriteFlags.HorizontalFlip ) ? -1f : 1f), 0f );\r\n\r\n\t\tif ( !IsDead )\r\n\t\t{\r\n\t\t\tHandleFlashing( dt );\r\n\t\t}\r\n\r\n\t\tif ( IsProxy )\r\n\t\t\treturn;\r\n\r\n\t\t// ACHIEVEMENTS\r\n\t\tif ( Manager.Instance.Difficulty >= 0 )\r\n\t\t{\r\n\t\t\tif ( _lastPos2D.x < -15.7f && WorldPosition.x >= -15.7f )\r\n\t\t\t{\r\n\t\t\t\t_timeSinceTouchLeftSide = 0f;\r\n\t\t\t}\r\n\r\n\t\t\tif ( _lastPos2D.x < 15.7f && WorldPosition.x >= 15.7f )\r\n\t\t\t{\r\n\t\t\t\tLog.Info( $\"_timeSinceTouchLeftSide: {_timeSinceTouchLeftSide}\" );\r\n\r\n\t\t\t\tif ( _timeSinceTouchLeftSide < 5.5f )\r\n\t\t\t\t{\r\n\t\t\t\t\tif ( !_hasUnlockedSprinterAchievement )\r\n\t\t\t\t\t\tSandbox.Services.Achievements.Unlock( \"sprinter\" );\r\n\r\n\t\t\t\t\t_hasUnlockedSprinterAchievement = true;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif ( !_hasUnlockedNoMoveAchievement )\r\n\t\t\t{\r\n\t\t\t\tif ( IsInputtingMove )\r\n\t\t\t\t{\r\n\t\t\t\t\tTimeSinceInputMove = 0f;\r\n\t\t\t\t}\r\n\t\t\t\telse if ( TimeSinceInputMove > 60f * 10f )\r\n\t\t\t\t{\r\n\t\t\t\t\tSandbox.Services.Achievements.Unlock( \"stand_ground\" );\r\n\t\t\t\t\t_hasUnlockedNoMoveAchievement = true;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t//Gizmo.Draw.Color = Color.White;\r\n\t\t//Gizmo.Draw.Text( $\"ArrowAimer: {ArrowAimer}\", new global::Transform( (Vector3)Position2D + new Vector3( 0f, -0.7f, 0f ) ) );\r\n\r\n\t\t//Gizmo.Draw.ScreenText( $\"_timeSinceTouchLeftSide: {_timeSinceTouchLeftSide}\", new Vector2( 50, 50 ) );\r\n\r\n\t\t_lastPos2D = Position2D;\r\n\r\n\t\tvar velocity = Velocity + (IsDashing ? DashVelocity : Vector2.Zero);\r\n\t\tPosition2D += velocity * dt;\r\n\r\n\t\tif ( Stats[PlayerStat.MoveSelfDmg] > 0f )\r\n\t\t{\r\n\t\t\tStats[PlayerStat.SelfDmgDistanceMoved] += velocity.Length * dt;\r\n\r\n\t\t\tif ( !IsInvulnerable && Stats[PlayerStat.SelfDmgDistanceMoved] > Stats[PlayerStat.MoveSelfDmgReqDist] )\r\n\t\t\t{\r\n\t\t\t\tStats[PlayerStat.SelfDmgDistanceMoved] -= Stats[PlayerStat.MoveSelfDmgReqDist];\r\n\t\t\t\tDamage( Stats[PlayerStat.MoveSelfDmgAmount], PlayerDamageType.Self );\r\n\t\t\t\tManager.Instance.PlaySfxNearby( \"zombie.attack.player\", Position2D, pitch: Game.Random.Float( 1.25f, 1.45f ), volume: 0.9f, maxDist: 3f );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tWorldPosition = WorldPosition.WithZ( Globals.GetZPos( Position2D.y ) );\r\n\r\n\t\t//Velocity = Utils.DynamicEaseTo( Velocity, Vector2.Zero, 0.2f, dt );\r\n\t\tVelocity *= Math.Max( 1f - dt * 12.9f, 0f );\r\n\r\n\t\tif ( Velocity.LengthSquared < 0.001f )\r\n\t\t\tVelocity = Vector2.Zero;\r\n\r\n\t\tTempWeight *= (1f - dt * 4.7f);\r\n\r\n\t\tif ( InputVector.LengthSquared > 0f )\r\n\t\t{\r\n\t\t\tVelocity += InputVector.Normal * Stats[PlayerStat.MoveSpeed] * BASE_MOVE_SPEED * dt;\r\n\t\t\t//Log.Info( $\"dt: {dt}\" );\r\n\t\t}\r\n\r\n\t\tHandleBounds();\r\n\r\n\t\tManager.Instance.Camera2D.TargetPos = Position2D;\r\n\r\n\t\tif ( Input.UsingController )\r\n\t\t{\r\n\t\t\t// raw stick rather than AnalogLook, which is scaled by look sensitivity and affected by invert-y (SDL stick y is positive downward)\r\n\t\t\tvar stick = new Vector2( Input.GetAnalog( InputAnalog.RightStickX ), -Input.GetAnalog( InputAnalog.RightStickY ) );\r\n\t\t\tif ( stick.Length > CONTROLLER_AIM_DEADZONE )\r\n\t\t\t\t_controllerAimTarget = stick.Normal;\r\n\r\n\t\t\tif ( _controllerAimTarget.LengthSquared > 0f )\r\n\t\t\t{\r\n\t\t\t\t// reverse here rather than when storing the target, so it still flips when ReverseControls toggles while the stick is released\r\n\t\t\t\tvar target = Stats[PlayerStat.ReverseControls] > 0f ? -_controllerAimTarget : _controllerAimTarget;\r\n\r\n\t\t\t\t// rotate by angle, since a linear lerp toward the exact opposite direction never changes direction\r\n\t\t\t\t// keeps turning after the stick is released, so a quick flick still reaches the target\r\n\t\t\t\tfloat currentDegrees = Utils.VectorToDegrees( AimDir );\r\n\t\t\t\tfloat deltaDegrees = ((Utils.VectorToDegrees( target ) - currentDegrees + 540f) % 360f) - 180f;\r\n\t\t\t\tAimDir = Utils.DegreesToVector( currentDegrees + deltaDegrees * MathF.Min( Time.Delta * 25f, 1f ) );\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tAimDir = (Manager.Instance.MouseWorldPos - (Position2D + new Vector2( 0f, 0.5f ))).Normal;\r\n\r\n\t\t\tif ( Stats[PlayerStat.ReverseControls] > 0f )\r\n\t\t\t\tAimDir *= -1f;\r\n\r\n\t\t\t// forget the last stick direction, so switching back to the controller keeps the mouse aim instead of swinging back to it\r\n\t\t\t_controllerAimTarget = Vector2.Zero;\r\n\t\t}\r\n\r\n\t\tif ( ArrowAimer != null && !Manager.Instance.IsPauseMenuOpen && !IsTimePausedForChoosing )\r\n\t\t{\r\n\t\t\tArrowAimer.LocalRotation = new Angles( 0f, MathF.Atan2( AimDir.y, AimDir.x ) * (180f / MathF.PI) - 180f, 0f );\r\n\t\t\tArrowAimer.LocalPosition = new Vector2( 0f, 0.4f ) + AimDir * Utils.Map( _timeSinceShoot, 0f, 0.25f, 0.6f, 0.55f, EasingType.QuadOut );\r\n\t\t\tArrowAimer.LocalScale = new Vector3( Utils.Map( _timeSinceShoot, 0f, 0.25f, 1.25f, 0.75f, EasingType.QuadOut ), 1f, 1f ) * 0.005f;\r\n\t\t\tArrowSprite.Tint = Color.White.WithAlpha( Utils.Map( _timeSinceShoot, 0f, 0.3f, 1f, 0.3f, EasingType.QuadOut ) * Utils.Map( _timeSinceSpawn, 0f, 1f, 0f, 1f, EasingType.Linear ) );\r\n\t\t}\r\n\r\n\t\tfor ( int dx = -1; dx <= 1; dx++ )\r\n\t\t{\r\n\t\t\tfor ( int dy = -1; dy <= 1; dy++ )\r\n\t\t\t{\r\n\t\t\t\tManager.Instance.HandleThingCollisionForGridSquare( this, new GridSquare( GridPos.x + dx, GridPos.y + dy ), dt );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( !IsDead )\r\n\t\t{\r\n\t\t\tHandleDashing( dt );\r\n\t\t\tHandleStatuses( dt );\r\n\t\t\tHandleShooting( dt );\r\n\t\t\tHandleRegen( dt );\r\n\t\t}\r\n\r\n\t\tif ( IsChoosingLevelUpReward && !Manager.Instance.IsPauseMenuOpen )\r\n\t\t{\r\n\t\t\tif ( Input.Pressed( \"reload\" ) ) UseReroll();\r\n\t\t\telse if ( Input.Pressed( \"Slot1\" ) ) UseChoiceHotkey( 1 );\r\n\t\t\telse if ( Input.Pressed( \"Slot2\" ) ) UseChoiceHotkey( 2 );\r\n\t\t\telse if ( Input.Pressed( \"Slot3\" ) ) UseChoiceHotkey( 3 );\r\n\t\t\telse if ( Input.Pressed( \"Slot4\" ) ) UseChoiceHotkey( 4 );\r\n\t\t\telse if ( Input.Pressed( \"Slot5\" ) ) UseChoiceHotkey( 5 );\r\n\t\t\telse if ( Input.Pressed( \"Slot6\" ) ) UseChoiceHotkey( 6 );\r\n\t\t}\r\n\r\n\t\tif ( Input.Pressed( \"use\" ) )\r\n\t\t{\r\n\t\t\t//AddExperience( 10 );\r\n\t\t}\r\n\t}\r\n\r\n\tvoid HandleRegen( float dt )\r\n\t{\r\n\t\tif ( Math.Abs( Stats[PlayerStat.HealthRegen] ) > 0f )\r\n\t\t\tRegenHealth( Stats[PlayerStat.HealthRegen] * dt );\r\n\r\n\t\t//if ( Math.Abs( Stats[PlayerStat.HealthDrain] ) > 0f )\r\n\t\t//\tRegenHealth( Stats[PlayerStat.HealthDrain] * dt );\r\n\r\n\t\tif ( Stats[PlayerStat.HealthRegenStill] > 0f && !IsMoving )\r\n\t\t{\r\n\t\t\tRegenHealth( Stats[PlayerStat.HealthRegenStill] * dt );\r\n\r\n\t\t\tif ( !IsDashing && Health < Stats[PlayerStat.MaxHp] )\r\n\t\t\t\tTimeSinceChangeHP = 0f;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void RegenHealth( float amount )\r\n\t{\r\n\t\tfloat maxHp = Stats[PlayerStat.MaxHp];\r\n\t\tfloat hpMissing = maxHp - Health;\r\n\r\n\t\tif ( amount > 0f )\r\n\t\t{\r\n\t\t\tif ( hpMissing <= 0f )\r\n\t\t\t\treturn;\r\n\r\n\t\t\t_regenHpAccumulated += amount;\r\n\r\n\t\t\tif ( _regenHpAccumulated < 0.85f && hpMissing > _regenHpAccumulated )\r\n\t\t\t\treturn;\r\n\r\n\t\t\tfloat hpRecovered = Math.Min( _regenHpAccumulated, hpMissing );\r\n\r\n\t\t\tHealth += hpRecovered;\r\n\r\n\t\t\t//var particle = DamageNumbersLegacy.Create( hpRecovered, Position2D + new Vector2( 0.2f + Game.Random.Float( -0.1f, 0.1f ), Radius * 3f + Game.Random.Float( -0.2f, 0.3f ) ), color: Color.Green, sizeMultiplier: 0.8f );\r\n\t\t\t//Vector3 velocity = new Vector3( Game.Random.Float(-0.5f, 0.5f), 0f, 0f );\r\n\t\t\t//Vector3 gravity = new Vector3( 0f, 1.5f, 0f );\r\n\t\t\t//particle.SetVector( 1, velocity );\r\n\t\t\t//particle.SetNamedValue( \"Gravity\", gravity );\r\n\r\n\t\t\tvar pos = Position2D + new Vector2( Game.Random.Float( -0.1f, 0.1f ), Radius * 3f + Game.Random.Float( -0.2f, 0.3f ) );\r\n\t\t\tfloat size = 1.1f;\r\n\t\t\tManager.Instance.SpawnDamageNumber( pos, amount, Color.Green, size, FloaterType.Heal );\r\n\r\n\t\t\t_regenHpAccumulated = 0f;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t_drainHpAccumulated += amount;\r\n\t\t\tif ( _drainHpAccumulated < -1f )\r\n\t\t\t{\r\n\t\t\t\tvar dmgAmount = MathF.Truncate( _drainHpAccumulated );\r\n\t\t\t\t_drainHpAccumulated -= dmgAmount;\r\n\r\n\t\t\t\tDamage( MathF.Abs( dmgAmount ), PlayerDamageType.Self );\r\n\r\n\t\t\t\tManager.Instance.PlaySfxNearby( \"lava_puddle_03\", Position2D, pitch: Game.Random.Float( 1.7f, 1.75f ), volume: 0.15f, maxDist: 4f );\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tvoid HandleDashing( float dt )\r\n\t{\r\n\t\tint numDashes = (int)MathF.Round( Stats[PlayerStat.NumDashes] );\r\n\t\tif ( NumDashesAvailable < numDashes )\r\n\t\t{\r\n\t\t\tDashTimer -= dt;\r\n\t\t\tDashRechargeProgress = Utils.Map( DashTimer, Stats[PlayerStat.DashCooldown], 0f, 0f, 1f );\r\n\t\t\tif ( DashTimer <= 0f )\r\n\t\t\t{\r\n\t\t\t\tDashRecharged();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( DashInvulnTimer > 0f )\r\n\t\t{\r\n\t\t\tDashInvulnTimer -= dt;\r\n\t\t\tDashProgress = Utils.Map( DashInvulnTimer, Stats[PlayerStat.DashInvulnTime], 0f, 0f, 1f );\r\n\t\t\tif ( DashInvulnTimer <= 0f )\r\n\t\t\t{\r\n\t\t\t\tIsDashing = false;\r\n\t\t\t\t//Sprite.Tint = Color.White;\r\n\t\t\t\tSprite.FlashTint = Color.White.WithAlpha( 0f );\r\n\r\n\t\t\t\tDashFinished();\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tif ( Stats[PlayerStat.DashCharm] > 0f )\r\n\t\t\t\t{\r\n\t\t\t\t\tif ( IsInvulnerable )\r\n\t\t\t\t\t\tSprite.FlashTint = new Color( Game.Random.Float( 0.8f, 1f ), Game.Random.Float( 0f, 0.1f ), Game.Random.Float( 0.8f, 1f ), 0.9f );\r\n\t\t\t\t\telse if ( !_isFlashing )\r\n\t\t\t\t\t\tSprite.FlashTint = new Color( Game.Random.Float( 0.9f, 1f ), Game.Random.Float( 0.1f, 0.3f ), Game.Random.Float( 0.9f, 1f ), 0.8f );\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tif ( IsInvulnerable )\r\n\t\t\t\t\t\tSprite.FlashTint = new Color( Game.Random.Float( 0f, 0.25f ), Game.Random.Float( 0f, 0.25f ), Game.Random.Float( 0.8f, 1f ), 0.9f );\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif ( _dashCloudTime > Game.Random.Float( 0.1f, 0.2f ) )\r\n\t\t\t\t{\r\n\t\t\t\t\tSpawnDashCloudClient();\r\n\t\t\t\t\t_dashCloudTime = 0f;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tbool pressedDash = Input.Pressed( \"Dash\" ) && !Gamepad.IsAClickingUi;\r\n\t\tif ( (pressedDash ||Input.Pressed( \"RB\" )) && !Manager.Instance.IsPauseMenuOpen )\r\n\t\t{\r\n\t\t\t//Position2D = Manager.Instance.MouseWorldPos;\r\n\t\t\t////Manager.Instance.Camera2D.SetPos( Position2D );\r\n\t\t\t//Transform.ClearInterpolation();\r\n\t\t\t//return;\r\n\r\n\t\t\tDash();\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void Dash()\r\n\t{\r\n\t\tif ( NumDashesAvailable <= 0 || IsTimePausedForChoosing )\r\n\t\t\treturn;\r\n\r\n\t\tVector2 dashDir = Velocity.LengthSquared > 0f ? Velocity.Normal : AimDir;\r\n\r\n\t\tif ( Game.Random.Float( 0f, 1f ) < Stats[PlayerStat.DashRandomDirChance] )\r\n\t\t\tdashDir = Utils.GetRandomVector();\r\n\r\n\t\tDashVelocity = dashDir * Stats[PlayerStat.DashStrength];\r\n\t\tTempWeight = 2f;\r\n\r\n\t\tif ( NumDashesAvailable == (int)Stats[PlayerStat.NumDashes] )\r\n\t\t\tDashTimer = Stats[PlayerStat.DashCooldown];\r\n\r\n\t\tNumDashesAvailable--;\r\n\t\tIsDashing = true;\r\n\t\tDashInvulnTimer = Stats[PlayerStat.DashInvulnTime];\r\n\t\tDashProgress = 0f;\r\n\t\tDashRechargeProgress = 0f;\r\n\r\n\t\tManager.Instance.PlaySfxNearby( \"player.dash\", Position2D + dashDir * 0.5f, pitch: Utils.Map( NumDashesAvailable, 0, 5, 1f, 0.9f ), volume: 1f, maxDist: 4f );\r\n\t\tSpawnDashCloudClient();\r\n\t\t_dashCloudTime = 0f;\r\n\r\n\t\tForEachStatus( status => status.OnDashStarted() );\r\n\t}\r\n\r\n\tpublic void DashFinished()\r\n\t{\r\n\t\tForEachStatus( status => status.OnDashFinished() );\r\n\t}\r\n\r\n\tpublic void DashRecharged()\r\n\t{\r\n\t\tNumDashesAvailable++;\r\n\t\tvar numDashes = (int)MathF.Round( Stats[PlayerStat.NumDashes] );\r\n\t\tif ( NumDashesAvailable > numDashes )\r\n\t\t\tNumDashesAvailable = numDashes;\r\n\r\n\t\tif ( NumDashesAvailable < numDashes )\r\n\t\t{\r\n\t\t\tDashTimer = Stats[PlayerStat.DashCooldown];\r\n\t\t\tDashRechargeProgress = 0f;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tDashRechargeProgress = 1f;\r\n\t\t}\r\n\r\n\t\tForEachStatus( status => status.OnDashRecharged() );\r\n\r\n\t\tManager.Instance.PlaySfxNearby( \"player.dash.recharge\", Position2D, pitch: Utils.Map( NumDashesAvailable, 1, numDashes, 1f, 1.2f ), volume: 0.2f, maxDist: 5f );\r\n\t}\r\n\r\n\tvoid HandleBounds()\r\n\t{\r\n\t\tvar x_min = Manager.Instance.BOUNDS_MIN.x + Radius;\r\n\t\tvar x_max = Manager.Instance.BOUNDS_MAX.x - Radius;\r\n\t\tvar y_min = Manager.Instance.BOUNDS_MIN.y;\r\n\t\tvar y_max = Manager.Instance.BOUNDS_MAX.y - Radius;\r\n\r\n\t\tif ( Position2D.x < x_min )\r\n\t\t\tPosition2D = new Vector2( x_min, Position2D.y );\r\n\t\telse if ( Position2D.x > x_max )\r\n\t\t\tPosition2D = new Vector2( x_max, Position2D.y );\r\n\r\n\t\tif ( Position2D.y < y_min )\r\n\t\t\tPosition2D = new Vector2( Position2D.x, y_min );\r\n\t\telse if ( Position2D.y > y_max )\r\n\t\t\tPosition2D = new Vector2( Position2D.x, y_max );\r\n\t}\r\n\r\n\tpublic int GetExperienceReqForLevel( int level )\r\n\t{\r\n\t\tswitch ( Manager.Instance.Difficulty )\r\n\t\t{\r\n\t\t\tcase -1:\r\n\t\t\t\treturn (int)MathF.Round( Utils.Map( level, 1, 150, 3f, 240f, EasingType.SineIn ) );\r\n\t\t\tcase 0:\r\n\t\t\tdefault:\r\n\t\t\t\treturn (int)MathF.Round( Utils.Map( level, 1, 150, 3f, 320f, EasingType.SineIn ) );\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void Flash( float time )\r\n\t{\r\n\t\tif ( _isFlashing )\r\n\t\t\treturn;\r\n\r\n\t\t//Sprite.Tint = new Color( 1f, 0f, 0f );\r\n\t\tSprite.FlashTint = new Color( 1f, 0f, 0f, 1f );\r\n\t\t_isFlashing = true;\r\n\t\t_flashTimer = time;\r\n\t}\r\n\r\n\tpublic void Heal( float amount, float flashTime )\r\n\t{\r\n\t\t//Sprite.Tint = new Color( 0f, 1f, 0f );\r\n\t\tSprite.FlashTint = new Color( 0f, 1f, 0f, 1f );\r\n\t\t_isFlashing = true;\r\n\t\t_flashTimer = flashTime;\r\n\r\n\t\tif ( IsProxy )\r\n\t\t\treturn;\r\n\r\n\t\tif ( Health < Stats[PlayerStat.MaxHp] )\r\n\t\t\tTimeSinceChangeHP = 0f;\r\n\r\n\t\tHealth += amount;\r\n\t\tif ( Health > Stats[PlayerStat.MaxHp] )\r\n\t\t\tHealth = Stats[PlayerStat.MaxHp];\r\n\t}\r\n\r\n\tvoid HandleFlashing( float dt )\r\n\t{\r\n\t\tif ( _isFlashing )\r\n\t\t{\r\n\t\t\t_flashTimer -= dt;\r\n\t\t\tif ( _flashTimer < 0f )\r\n\t\t\t{\r\n\t\t\t\t_isFlashing = false;\r\n\t\t\t\t//Sprite.Tint = Color.White;\r\n\t\t\t\tSprite.FlashTint = Color.White.WithAlpha( 0f );\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t[ConCmd( \"give_status\" )]\r\n\tpublic static void GiveStatus( string name )\r\n\t{\r\n\t\t// Cheat only works in the editor\r\n\t\tif ( !Game.IsEditor )\r\n\t\t\treturn;\r\n\r\n\t\tvar type = TypeLibrary.GetType( name );\r\n\t\tif ( type == null )\r\n\t\t{\r\n\t\t\tLog.Info( $\"No status with name '{name}' found!\" );\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tManager.Instance?.GetLocalPlayer()?.AddStatus( type );\r\n\t}\r\n\r\n\tpublic void AddStatus( TypeDescription type )\r\n\t{\r\n\t\tStatus status = null;\r\n\t\tvar typeIdentity = type.Identity;\r\n\r\n\t\tForEachStatus( status => status.OnAddStatus( typeIdentity ) );\r\n\r\n\t\tif ( Statuses.ContainsKey( typeIdentity ) )\r\n\t\t{\r\n\t\t\tstatus = Statuses[typeIdentity];\r\n\t\t\tstatus.Level++;\r\n\t\t}\r\n\r\n\t\tif ( status == null )\r\n\t\t{\r\n\t\t\tstatus = StatusManager.CreateStatus( type );\r\n\t\t\tStatuses.Add( typeIdentity, status );\r\n\t\t\tstatus.Init( this );\r\n\t\t}\r\n\r\n\t\t//Sandbox.Services.Stats.Increment( Client, \"status\", 1, $\"{type.Name.ToLowerInvariant()}\", new { Status = type.Name.ToLowerInvariant(), Level = status.Level } );\r\n\r\n\t\tstatus.Refresh();\r\n\r\n\t\tManager.Instance.PlaySfxNearby( \"click\", Position2D, 0.9f, 0.75f, 5f );\r\n\r\n\t\tLevelUpChoices.Clear();\r\n\t\tIsChoosingLevelUpReward = false;\r\n\t\tRealTimeSinceChoseUpgrade = 0f;\r\n\r\n\t\tCheckForLevelUp();\r\n\t}\r\n\r\n\tpublic bool HasStatus( TypeDescription type )\r\n\t{\r\n\t\treturn Statuses.ContainsKey( type.Identity );\r\n\t}\r\n\r\n\tpublic Status GetStatus( TypeDescription type )\r\n\t{\r\n\t\tif ( Statuses.ContainsKey( type.Identity ) )\r\n\t\t\treturn Statuses[type.Identity];\r\n\r\n\t\treturn null;\r\n\t}\r\n\r\n\tpublic int GetStatusLevel( TypeDescription type )\r\n\t{\r\n\t\tif ( Statuses.ContainsKey( type.Identity ) )\r\n\t\t\treturn Statuses[type.Identity].Level;\r\n\r\n\t\treturn 0;\r\n\t}\r\n\r\n\tpublic void Modify( Status caller, PlayerStat statType, float value, ModifierType type, float priority = 0f, bool update = true )\r\n\t{\r\n\t\tif ( !_modifiers_stat.ContainsKey( caller ) )\r\n\t\t\t_modifiers_stat.Add( caller, new Dictionary<PlayerStat, ModifierData>() );\r\n\r\n\t\t_modifiers_stat[caller][statType] = new ModifierData( value, type, priority );\r\n\r\n\t\tif ( update )\r\n\t\t\tUpdateProperty( statType );\r\n\t}\r\n\r\n\tpublic void AdjustBaseStat( PlayerStat statType, float amount, bool update = true )\r\n\t{\r\n\t\tif ( !_original_properties_stat.ContainsKey( statType ) )\r\n\t\t\t_original_properties_stat.Add( statType, Stats[statType] );\r\n\r\n\t\t_original_properties_stat[statType] += amount;\r\n\r\n\t\tif ( update )\r\n\t\t\tUpdateProperty( statType );\r\n\t}\r\n\r\n\tvoid UpdateProperty( PlayerStat statType )\r\n\t{\r\n\t\tif ( !_original_properties_stat.ContainsKey( statType ) )\r\n\t\t{\r\n\t\t\t_original_properties_stat.Add( statType, Stats[statType] );\r\n\t\t}\r\n\r\n\t\tfloat curr_value = _original_properties_stat[statType];\r\n\t\tfloat curr_set = curr_value;\r\n\t\tbool should_set = false;\r\n\t\tfloat curr_priority = 0f;\r\n\t\tfloat total_add = 0f;\r\n\t\tfloat total_mult = 1f;\r\n\r\n\t\tforeach ( Status caller in _modifiers_stat.Keys )\r\n\t\t{\r\n\t\t\tvar dict = _modifiers_stat[caller];\r\n\t\t\tif ( dict.ContainsKey( statType ) )\r\n\t\t\t{\r\n\t\t\t\tvar mod_data = dict[statType];\r\n\t\t\t\tswitch ( mod_data.type )\r\n\t\t\t\t{\r\n\t\t\t\t\tcase ModifierType.Set:\r\n\t\t\t\t\t\tif ( mod_data.priority >= curr_priority )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tcurr_set = mod_data.value;\r\n\t\t\t\t\t\t\tcurr_priority = mod_data.priority;\r\n\t\t\t\t\t\t\tshould_set = true;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase ModifierType.Add:\r\n\t\t\t\t\t\ttotal_add += mod_data.value;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase ModifierType.Mult:\r\n\t\t\t\t\t\ttotal_mult *= mod_data.value;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( should_set )\r\n\t\t\tcurr_value = curr_set;\r\n\r\n\t\tcurr_value += total_add;\r\n\t\tcurr_value *= total_mult;\r\n\r\n\t\tStats[statType] = curr_value;\r\n\r\n\t\tif ( statType == PlayerStat.MaxHp )\r\n\t\t\tStats[statType] = Math.Max( curr_value, 1f );\r\n\t}\r\n\r\n\tpublic void AddExperience( int xp )\r\n\t{\r\n\t\tExperienceTotal += xp;\r\n\t\tExperienceCurrent += xp;\r\n\r\n\t\t//var particle = DamageNumbersLegacy.Create( xp, Position2D + new Vector2( 0.2f + Game.Random.Float( -0.1f, 0.1f ), Radius * 3f + Game.Random.Float( -0.2f, 0.3f ) ), color: new Color(0.1f, 0.1f, 1f), sizeMultiplier: 0.8f );\r\n\t\t//Vector3 velocity = new Vector3( 0f, 0f, 0f );\r\n\t\t//Vector3 gravity = new Vector3( 0f, 1f, 0f );\r\n\t\t//particle.SetVector( 1, velocity );\r\n\t\t//particle.SetNamedValue( \"Gravity\", gravity );\r\n\r\n\t\tvar pos = Position2D + new Vector2( Game.Random.Float( -0.1f, 0.1f ), Radius * 3f + Game.Random.Float( -0.2f, 0.3f ) );\r\n\t\tfloat size = Utils.Map( xp, 1f, 4f, 0.95f, 1.1f, EasingType.Linear );\r\n\t\tvar color = new Color( 0.4f, 0.4f, 1f );\r\n\t\tManager.Instance.SpawnDamageNumber( pos, xp, color, size, FloaterType.Xp );\r\n\r\n\t\tForEachStatus( status => status.OnGainExperience( xp ) );\r\n\t\tif ( !IsChoosingLevelUpReward )\r\n\t\t\tCheckForLevelUp();\r\n\t}\r\n\r\n\tpublic void LoseExperience( int amount )\r\n\t{\r\n\t\tExperienceCurrent = Math.Max( ExperienceCurrent - amount, 0 );\r\n\t}\r\n\r\n\tpublic void CheckForLevelUp()\r\n\t{\r\n\t\t//Log.Info(\"CheckForLevelUp: \" + ExperienceCurrent + \" / \" + ExperienceRequired + \" IsServer: \" + Sandbox.Game.IsServer + \" Level: \" + Level);\r\n\t\tif ( ExperienceCurrent >= ExperienceRequired && Manager.Instance.ShouldUpdatePlayer )\r\n\t\t\tLevelUp();\r\n\t}\r\n\r\n\tpublic void LevelUp()\r\n\t{\r\n\t\tExperienceCurrent -= ExperienceRequired;\r\n\r\n\t\tLevel++;\r\n\t\tExperienceRequired = GetExperienceReqForLevel( Level + 1 );\r\n\r\n\t\tif ( Manager.Instance.Difficulty < 3 )\r\n\t\t\tNumRerollAvailable += (int)Stats[PlayerStat.NumRerollsPerLevel];\r\n\r\n\t\tManager.Instance.PlaySfxNearby( \"levelup\", Position2D, Game.Random.Float( 0.95f, 1.05f ), 0.5f, 5f );\r\n\r\n\t\tForEachStatus( status => status.OnLevelUp() );\r\n\r\n\t\tGenerateLevelUpChoices();\r\n\t\tIsChoosingLevelUpReward = true;\r\n\t\tTimeSinceLevelUp = 0f;\r\n\r\n\t\tif ( Manager.Instance.Difficulty >= 0 && !_hasUnlockedExperiencedAchievement && Level >= 85 )\r\n\t\t{\r\n\t\t\tSandbox.Services.Achievements.Unlock( \"experienced\" );\r\n\t\t\t_hasUnlockedExperiencedAchievement = true;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void UseReroll()\r\n\t{\r\n\t\tif ( NumRerollAvailable <= 0 )\r\n\t\t{\r\n\t\t\t// todo: sfx\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tManager.Instance.PlaySfxNearby( \"reroll\", Position2D, Utils.Map( NumRerollAvailable, 0, 20, 0.9f, 1.4f, EasingType.QuadIn ), 0.6f, 5f );\r\n\r\n\t\tNumRerollAvailable--;\r\n\r\n\t\tGenerateLevelUpChoices();\r\n\r\n\t\tForEachStatus( status => status.OnReroll() );\r\n\t}\r\n\r\n\t/// <summary>\r\n\t/// Number of level up choices that have a hotkey (Slot1..Slot6 actions).\r\n\t/// </summary>\r\n\tpublic const int NUM_CHOICE_HOTKEYS = 6;\r\n\r\n\tpublic void UseChoiceHotkey( int num )\r\n\t{\r\n\t\tvar index = num - 1;\r\n\r\n\t\tif ( !IsChoosingLevelUpReward || index >= LevelUpChoices.Count )\r\n\t\t\treturn;\r\n\r\n\t\tAddStatus( TypeLibrary.GetType( LevelUpChoices[index].GetType() ) );\r\n\t}\r\n\r\n\tpublic float CheckDamageAmount( float damage, DamageType damageType )\r\n\t{\r\n\t\tif ( IsInvulnerable )\r\n\t\t{\r\n\t\t\treturn 0f;\r\n\t\t}\r\n\r\n\t\tif ( HasStatus( TypeLibrary.GetType( typeof( ShieldStatus ) ) ) && damageType != DamageType.LavaPuddle )\r\n\t\t{\r\n\t\t\tvar shieldStatus = GetStatus( TypeLibrary.GetType( typeof( ShieldStatus ) ) ) as ShieldStatus;\r\n\t\t\tif ( shieldStatus != null && shieldStatus.IsShielded )\r\n\t\t\t{\r\n\t\t\t\tshieldStatus.LoseShield();\r\n\t\t\t\treturn 0f;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( Stats[PlayerStat.DamageReductionPercent] > 0f )\r\n\t\t\tdamage *= (1f - MathX.Clamp( Stats[PlayerStat.DamageReductionPercent], 0f, 1f ));\r\n\r\n\t\tif ( Stats[PlayerStat.IncreasedDmgTaken] > 0f )\r\n\t\t\tdamage *= (1f + MathX.Clamp( Stats[PlayerStat.IncreasedDmgTaken], 0f, 1f ));\r\n\r\n\t\tif ( damageType == DamageType.Explosion && Stats[PlayerStat.ExplosionDamageReductionPercent] > 0f )\r\n\t\t\tdamage *= (1f - MathX.Clamp( Stats[PlayerStat.ExplosionDamageReductionPercent], 0f, 1f ));\r\n\r\n\t\tif ( damageType != DamageType.Explosion && Stats[PlayerStat.NonExplosionDamageIncreasePercent] > 0f )\r\n\t\t\tdamage *= (1f + Stats[PlayerStat.NonExplosionDamageIncreasePercent]);\r\n\r\n\t\tif ( Manager.Instance.Difficulty < 0 )\r\n\t\t\tdamage *= 0.571429f; // so zombie's 7 dmg becomes 4 dmg\r\n\r\n\t\treturn damage;\r\n\t}\r\n\r\n\tpublic void Damage( float damage, PlayerDamageType playerDamageType = PlayerDamageType.Enemy )\r\n\t{\r\n\t\tif ( !Manager.Instance.ShouldUpdatePlayer )\r\n\t\t\treturn;\r\n\r\n\t\tTimeSinceHurt = 0f;\r\n\r\n\t\tbool isCrit = Game.Random.Float( 0f, 1f ) < Stats[PlayerStat.SelfCritChance];\r\n\r\n\t\tif ( isCrit )\r\n\t\t\tdamage *= 2f;\r\n\r\n\t\tif ( damage > 0f )\r\n\t\t{\r\n\t\t\tTimeSinceChangeHP = 0f;\r\n\t\t\tFlash( 0.125f );\r\n\t\t}\r\n\r\n\t\tvar offset = new Vector2(\r\n\t\t\tGame.Random.Float( -0.1f, 0.1f ),\r\n\t\t\tRadius * 3f + Game.Random.Float( -0.2f, 0.3f ) + (Health - damage < 0f ? -0.7f : 0f)\r\n\t\t);\r\n\r\n\t\t//DamageNumbers.Add( (int)damage, Position2D + Vector2.Up * Radius * 3f + new Vector2( Game.Random.Float( -1f, 1f ), Game.Random.Float( -1f, 1f ) ) * 0.2f, color: Color.Red );\r\n\t\t//DamageNumbersLegacy.Create( damage, Position2D + offset, color: isCrit ? Color.Orange : Color.Red );\r\n\r\n\t\tvar pos = Position2D + offset;\r\n\r\n\t\tfloat size;\r\n\t\tif ( damage < 5f ) size = Utils.Map( damage, 1f, 5f, 1.1f, 1.25f, EasingType.QuadOut );\r\n\t\telse if ( damage < 20f ) size = Utils.Map( damage, 5f, 20f, 1.25f, 1.6f, EasingType.Linear );\r\n\t\telse size = Utils.Map( damage, 20f, 100f, 1.6f, 1.8f, EasingType.Linear );\r\n\r\n\t\tvar color = isCrit ? Color.Orange : Color.Red;\r\n\r\n\t\tManager.Instance.SpawnDamageNumber( pos, damage, color, size );\r\n\r\n\t\tForEachStatus( status => status.OnHurt( damage ) );\r\n\r\n\t\tHealth -= damage;\r\n\r\n\t\t//if ( damage > 3f )\r\n\t\t//\tShakeCam( Utils.Map( damage, 3f, 25f, 0f, 0.1f ), Utils.Map( damage, 3f, 20f, 0.1f, 0.25f ), EasingType.QuadOut );\r\n\r\n\t\tif ( Health <= 0f )\r\n\t\t{\r\n\t\t\tDie();\r\n\t\t\tSpawnBlood( damage, sizeMultiplier: Game.Random.Float( 2.5f, 3f ), playbackSpeed: Game.Random.Float( 20f, 25f ), shouldUseRealTime: true );\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tSpawnBlood( damage );\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void AddVelocity( Vector2 vel )\r\n\t{\r\n\t\tif ( !Manager.Instance.ShouldUpdatePlayer )\r\n\t\t\treturn;\r\n\r\n\t\tVelocity += vel;\r\n\t}\r\n\r\n\tpublic void SpawnBlood( float damage, float sizeMultiplier = 1f, float playbackSpeed = 0f, bool shouldUseRealTime = false )\r\n\t{\r\n\t\tvar blood = Manager.Instance.SpawnBloodSplatter( Position2D );\r\n\r\n\t\tif ( blood != null )\r\n\t\t{\r\n\t\t\tblood.LocalScale *= Utils.Map( damage, 1f, 20f, 0.5f, 1.2f, EasingType.QuadIn ) * Game.Random.Float( 0.8f, 1.2f ) * sizeMultiplier;\r\n\t\t\tblood.Lifetime *= 0.3f;\r\n\t\t\tblood.ShouldUseRealTime = shouldUseRealTime;\r\n\r\n\t\t\tif ( playbackSpeed > 0f )\r\n\t\t\t\tblood.Sprite.PlaybackSpeed = playbackSpeed;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void Die()\r\n\t{\r\n\t\tif ( IsDead )\r\n\t\t\treturn;\r\n\r\n\t\tIsDead = true;\r\n\t\t_hasPlayedDeathSfx = false;\r\n\t\tSprite.Tint = new Color( 1f, 1f, 1f, 1f );\r\n\t\tSprite.FlashTint = new Color( 1f, 1f, 1f, 0f );\r\n\t\t//ShadowOpacity = 0.2f;\r\n\t\t_isFlashing = false;\r\n\t\tIsReloading = false;\r\n\r\n\t\tRealTimeSinceDeath = 0f;\r\n\t\t_arrowDeathAlphaStart = ArrowSprite.Tint.a;\r\n\r\n\t\tvar pitch = Game.Random.Float( 1.25f, 1.3f ) * (Manager.Instance.Difficulty < 0 ? 2f : 1f);\r\n\t\tManager.Instance.PlaySfxNearby( \"die\", Position2D, pitch, volume: 1.5f, maxDist: 12f );\r\n\r\n\t\tSprite.LocalScale *= 2f;\r\n\r\n\t\tSprite.PlayAnimation( $\"death\" );\r\n\t\t//Sprite.PlayAnimation( \"ghost_idle\" );\r\n\r\n\t\tShakeCam( Game.Random.Float( 0.025f, 0.065f ), Game.Random.Float( 0.3f, 0.5f ), EasingType.SineOut, useRealTime: true );\r\n\r\n\t\tif ( IsProxy )\r\n\t\t\treturn;\r\n\r\n\t\tManager.Instance.PlayerDied( this );\r\n\t}\r\n\r\n\tpublic void Revive()\r\n\t{\r\n\t\tif ( !IsDead )\r\n\t\t\treturn;\r\n\r\n\t\tIsDead = false;\r\n\t\tIsChoosingLevelUpReward = false;\r\n\t\tIsDashing = false;\r\n\t\tIsReloading = true;\r\n\t\tSprite.Tint = Color.White;\r\n\t\tShadowOpacity = 0.8f;\r\n\r\n\t\tif ( IsProxy )\r\n\t\t\treturn;\r\n\r\n\t\tTimer = Stats[PlayerStat.ReloadTime];\r\n\t\tReloadProgress = 0f;\r\n\t\tDashProgress = 0f;\r\n\t\tExperienceCurrent = 0;\r\n\r\n\t\tHealth = Stats[PlayerStat.MaxHp] * 0.33f;\r\n\t}\r\n\r\n\tpublic void ForEachStatus( Action<Status> action )\r\n\t{\r\n\t\tif ( IsProxy )\r\n\t\t\treturn;\r\n\r\n\t\tforeach ( var (_, status) in Statuses )\r\n\t\t{\r\n\t\t\taction( status );\r\n\t\t}\r\n\t}\r\n\r\n\tvoid HandleStatuses( float dt )\r\n\t{\r\n\t\tforeach ( KeyValuePair<int, Status> pair in Statuses )\r\n\t\t{\r\n\t\t\tStatus status = pair.Value;\r\n\t\t\tif ( status.ShouldUpdate )\r\n\t\t\t\tstatus.Update( dt );\r\n\t\t}\r\n\t}\r\n\r\n\tvoid HandleShooting( float dt )\r\n\t{\r\n\t\tif ( IsReloading )\r\n\t\t{\r\n\t\t\tReloadProgress = Utils.Map( Timer, Stats[PlayerStat.ReloadTime], 0f, 0f, 1f );\r\n\t\t\tTimer -= dt * Stats[PlayerStat.ReloadSpeed];\r\n\t\t\tif ( Timer <= 0f )\r\n\t\t\t{\r\n\t\t\t\tReload();\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tTimer -= dt * Stats[PlayerStat.AttackSpeed] * (IsMoving ? 1f : Stats[PlayerStat.AttackSpeedStill]);\r\n\t\t\tif ( Timer <= 0f )\r\n\t\t\t{\r\n\t\t\t\tShoot( isLastAmmo: AmmoCount == 1 );\r\n\t\t\t\tAmmoCount--;\r\n\r\n\t\t\t\tif ( AmmoCount <= 0 )\r\n\t\t\t\t{\r\n\t\t\t\t\tIsReloading = true;\r\n\r\n\t\t\t\t\tTimer += Stats[PlayerStat.ReloadTime];\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tTimer += Stats[PlayerStat.AttackTime];\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void Shoot( bool isLastAmmo = false )\r\n\t{\r\n\t\tint num_bullets_int = (int)Stats[PlayerStat.NumProjectiles];\r\n\r\n\t\tvar aimDir = Game.Random.Float( 0f, 1f ) < Stats[PlayerStat.ShootRandomDirChance]\r\n\t\t\t? Utils.GetRandomVector()\r\n\t\t\t: AimDir;\r\n\r\n\t\tvar pos = Position2D + AimDir * 0.3f;\r\n\r\n\t\tif ( Stats[PlayerStat.MaxBulletSpread] > 0f )\r\n\t\t{\r\n\t\t\tfloat increment = 360f / num_bullets_int;\r\n\r\n\t\t\tfor ( int i = 0; i < num_bullets_int; i++ )\r\n\t\t\t{\r\n\t\t\t\tvar dir = Utils.RotateVector( aimDir, i * increment );\r\n\t\t\t\tSpawnBullet( pos, dir, isLastAmmo );\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tfloat start_angle = MathF.Sin( -_shotNum * 2f ) * Stats[PlayerStat.BulletInaccuracy];\r\n\r\n\t\t\tvar spread = Stats[PlayerStat.BulletSpread] * num_bullets_int;\r\n\r\n\t\t\tfloat currAngleOffset = num_bullets_int == 1 ? 0f : -spread * 0.5f;\r\n\t\t\tfloat increment = num_bullets_int == 1 ? 0f : spread / (float)(num_bullets_int - 1);\r\n\r\n\t\t\tfor ( int i = 0; i < num_bullets_int; i++ )\r\n\t\t\t{\r\n\t\t\t\tvar dir = Utils.RotateVector( aimDir, start_angle + currAngleOffset + increment * i );\r\n\t\t\t\tSpawnBullet( pos, dir, isLastAmmo );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tManager.Instance.PlaySfxNearby( \"shoot\", pos, pitch: Utils.Map( _shotNum, 0f, (float)Stats[PlayerStat.MaxAmmoCount], 1f, 1.25f ), volume: 1f, maxDist: 4f );\r\n\r\n\t\tVelocity -= aimDir * Stats[PlayerStat.Recoil];\r\n\r\n\t\t_shotNum++;\r\n\t\t_timeSinceShoot = 0f;\r\n\t}\r\n\r\n\tvoid SpawnBullet( Vector2 pos, Vector2 dir, bool isLastAmmo = false, float damageMult = 1f )\r\n\t{\r\n\t\tvar damage = (Stats[PlayerStat.BulletDamage] * Stats[PlayerStat.BulletDamageMultiplier] + Stats[PlayerStat.BulletFlatDamageAddition]) * GetDamageMultiplier() * damageMult;\r\n\t\tif ( isLastAmmo )\r\n\t\t\tdamage *= Stats[PlayerStat.LastAmmoDamageMultiplier];\r\n\r\n\t\tif ( Stats[PlayerStat.DamagePerEarlierShot] > 0f )\r\n\t\t\tdamage += _shotNum * Stats[PlayerStat.DamagePerEarlierShot];\r\n\r\n\t\tif ( Stats[PlayerStat.DamageForSpeed] > 0f )\r\n\t\t{\r\n\t\t\tdamage += Stats[PlayerStat.DamageForSpeed] * Velocity.Length;\r\n\r\n\t\t\tif ( IsDashing )\r\n\t\t\t\tdamage += Stats[PlayerStat.DamageForSpeed] * DashVelocity.Length;\r\n\t\t}\r\n\r\n\t\tvar bulletObj = BulletPrefab.Clone( (Vector3)pos );\r\n\t\tvar bullet = bulletObj.Components.Get<Bullet>();\r\n\r\n\t\tbullet.Velocity = dir * Stats[PlayerStat.BulletSpeed];\r\n\t\tbullet.Shooter = this;\r\n\t\tbullet.TempWeight = 3f;\r\n\r\n\t\tbullet.Stats[BulletStat.Damage] = damage;\r\n\t\tbullet.Stats[BulletStat.Force] = Stats[PlayerStat.BulletForce];\r\n\t\tbullet.Stats[BulletStat.Lifetime] = Stats[PlayerStat.BulletLifetime];\r\n\t\tbullet.Stats[BulletStat.NumPiercing] = (int)MathF.Round( Stats[PlayerStat.BulletNumPiercing] );\r\n\t\tbullet.Stats[BulletStat.NumBouncing] = (int)MathF.Round( Stats[PlayerStat.BulletNumBouncing] );\r\n\t\tbullet.Stats[BulletStat.WillIgnite] = Game.Random.Float( 0f, 1f ) < Stats[PlayerStat.ShootFireIgniteChance] ? 1f : 0f;\r\n\t\tbullet.Stats[BulletStat.WillFreeze] = Game.Random.Float( 0f, 1f ) < Stats[PlayerStat.ShootFreezeChance] ? 1f : 0f;\r\n\t\tbullet.Stats[BulletStat.GrowDamageAmount] = Stats[PlayerStat.BulletDamageGrow];\r\n\t\tbullet.Stats[BulletStat.ShrinkDamageAmount] = Stats[PlayerStat.BulletDamageShrink];\r\n\t\tbullet.Stats[BulletStat.DistanceDamageAmount] = Stats[PlayerStat.BulletDistanceDamage];\r\n\t\tbullet.Stats[BulletStat.HealTeammateAmount] = Stats[PlayerStat.BulletHealTeammateAmount];\r\n\t\tbullet.IsHoming = Game.Random.Float( 0f, 1f ) < Stats[PlayerStat.HomingBulletChance];\r\n\r\n\t\tif ( Stats[PlayerStat.GrenadesCanCrit] <= 0f )\r\n\t\t{\r\n\t\t\tbullet.Stats[BulletStat.CriticalChance] = Stats[PlayerStat.CritChance];\r\n\t\t\tbullet.Stats[BulletStat.CriticalMultiplier] = Stats[PlayerStat.CritMultiplier];\r\n\t\t}\r\n\r\n\t\tbullet.Init();\r\n\r\n\t\t//bullet.GameObject.NetworkSpawn( Network.Owner );\r\n\t}\r\n\r\n\tvoid Reload()\r\n\t{\r\n\t\tAmmoCount = (int)Stats[PlayerStat.MaxAmmoCount];\r\n\t\tIsReloading = false;\r\n\t\t_shotNum = 0;\r\n\t\tReloadProgress = 0f;\r\n\r\n\t\tForEachStatus( status => status.OnReload() );\r\n\t}\r\n\r\n\tpublic float GetDamageMultiplier()\r\n\t{\r\n\t\tfloat damageMultiplier = Stats[PlayerStat.OverallDamageMultiplier];\r\n\r\n\t\tif ( Stats[PlayerStat.LowHealthDamageMultiplier] > 1f )\r\n\t\t\tdamageMultiplier *= Utils.Map( Health, Stats[PlayerStat.MaxHp], 0f, 1f, Stats[PlayerStat.LowHealthDamageMultiplier] );\r\n\r\n\t\tif ( Stats[PlayerStat.FullHealthDamageMultiplier] > 1f && !(Health < Stats[PlayerStat.MaxHp]) )\r\n\t\t\tdamageMultiplier *= Stats[PlayerStat.FullHealthDamageMultiplier];\r\n\r\n\t\treturn damageMultiplier;\r\n\t}\r\n\r\n\tpublic override void Colliding( Thing other, float percent, float dt )\r\n\t{\r\n\t\tbase.Colliding( other, percent, dt );\r\n\r\n\t\tif ( IsDead )\r\n\t\t\treturn;\r\n\r\n\t\tForEachStatus( status => status.Colliding( other, percent, dt ) );\r\n\r\n\t\tif ( other is Enemy enemy && !enemy.IsDying )\r\n\t\t{\r\n\t\t\tif ( !Position2D.Equals( other.Position2D ) )\r\n\t\t\t{\r\n\t\t\t\tvar spawnFactor = Utils.Map( enemy.TimeSinceSpawn, 0f, enemy.SpawnTime, 0f, 1f, EasingType.QuadIn );\r\n\t\t\t\tVelocity += (Position2D - other.Position2D).Normal * Utils.Map( percent, 0f, 1f, 0f, 100f ) * (1f + other.TempWeight) * spawnFactor * dt;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if ( other is Player player )\r\n\t\t{\r\n\t\t\tif ( !player.IsDead && !Position2D.Equals( other.Position2D ) )\r\n\t\t\t{\r\n\t\t\t\tVelocity += (Position2D - other.Position2D).Normal * Utils.Map( percent, 0f, 1f, 0f, 100f ) * (1f + other.TempWeight) * dt;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void SpawnDashCloudClient()\r\n\t{\r\n\t\tManager.Instance.SpawnCloud( Position2D + new Vector2( Game.Random.Float( -1f, 1f ), Game.Random.Float( -1f, 1f ) ) * 0.05f );\r\n\t}\r\n\r\n\tpublic void GenerateLevelUpChoices()\r\n\t{\r\n\t\tLevelUpChoices.Clear();\r\n\r\n\t\t//if( Level == 1)\r\n\t\t//{\r\n\t\t//\tLevelUpChoices.Add( CreateStatus( TypeLibrary.GetType( typeof( PauseWhileChoosingStatus ) ) ) );\r\n\t\t//\tLevelUpChoices.Add( CreateStatus( TypeLibrary.GetType( typeof( MysteryBoxStatus ) ) ) );\r\n\t\t//\tLevelUpChoices.Add( CreateStatus( GetRandomStartingPerk() ) );\r\n\r\n\t\t//\tLevelUpChoices.Shuffle();\r\n\r\n\t\t//\treturn;\r\n\t\t//}\r\n\r\n\t\tbool offerCurses = false;\r\n\t\tif ( Manager.Instance.Difficulty >= 6 )\r\n\t\t\tofferCurses = IsLevelCursed( Level );\r\n\r\n\t\tint numChoices = Math.Clamp( (int)MathF.Round( Stats[PlayerStat.NumUpgradeChoices] ), 1, 6 );\r\n\t\tList<TypeDescription> statusTypes = StatusManager.GetRandomStatuses( this, numChoices, offerCurses );\r\n\r\n\t\tfor ( int i = 0; i < statusTypes.Count; i++ )\r\n\t\t\tLevelUpChoices.Add( CreateStatus( statusTypes[i] ) );\r\n\r\n\t\tif ( Level == 1 )\r\n\t\t{\r\n\t\t\tbool alreadyOffered = false;\r\n\r\n\t\t\tforeach ( var status in LevelUpChoices )\r\n\t\t\t{\r\n\t\t\t\tif ( status is PauseWhileChoosingStatus )\r\n\t\t\t\t{\r\n\t\t\t\t\talreadyOffered = true;\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif ( !alreadyOffered )\r\n\t\t\t{\r\n\t\t\t\tLevelUpChoices.RemoveAt( 1 );\r\n\t\t\t\tLevelUpChoices.Add( CreateStatus( TypeLibrary.GetType( typeof( PauseWhileChoosingStatus ) ) ) );\r\n\t\t\t\tLevelUpChoices.Shuffle();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tChoiceHash++;\r\n\t}\r\n\r\n\tpublic bool IsLevelCursed( int level )\r\n\t{\r\n\t\tif ( Manager.Instance.Difficulty < 6 || level <= 1 )\r\n\t\t\treturn false;\r\n\r\n\t\tswitch ( Manager.Instance.Difficulty )\r\n\t\t{\r\n\t\t\tcase 6: return level % 10 == 0;\r\n\t\t\tcase 7: return level % 9 == 0;\r\n\t\t\tcase 8: return level % 8 == 0;\r\n\t\t\tcase 9: return level % 7 == 0;\r\n\t\t\tcase 10: return level % 6 == 0;\r\n\t\t\tcase 11: return level % 5 == 0;\r\n\t\t\tcase 12: return level % 4 == 0;\r\n\t\t\tcase 13: return level % 3 == 0;\r\n\t\t\tcase 14: return level % 2 == 0;\r\n\t\t\tcase 15: return level % 3 != 1;\r\n\t\t}\r\n\r\n\t\treturn false;\r\n\t}\r\n\r\n\tTypeDescription GetRandomStartingPerk()\r\n\t{\r\n\t\tList<(TypeDescription Type, float Weight)> perks = new List<(TypeDescription, float)>\r\n\t\t{\r\n\t\t\t(TypeLibrary.GetType( typeof( DamageStatus ) ), 5f),\r\n\t\t\t(TypeLibrary.GetType( typeof( MovespeedStatus ) ), 3f),\r\n\t\t\t(TypeLibrary.GetType( typeof( AttackSpeedStatus ) ), 2f),\r\n\t\t\t(TypeLibrary.GetType( typeof( NumProjectileStatus ) ), 2f),\r\n\t\t\t(TypeLibrary.GetType( typeof( PiercingStatus ) ), 3f),\r\n\t\t\t(TypeLibrary.GetType( typeof( GrenadeShootReloadStatus ) ), 1f),\r\n\t\t\t(TypeLibrary.GetType( typeof( NumDashesStatus ) ), 2f),\r\n\t\t\t(TypeLibrary.GetType( typeof( FireIgniteStatus ) ), 2f),\r\n\t\t\t(TypeLibrary.GetType( typeof( FreezeShootStatus ) ), 2f),\r\n\t\t\t(TypeLibrary.GetType( typeof( FullHealthDamageStatus ) ), 2f),\r\n\t\t\t(TypeLibrary.GetType( typeof( MoreRerollsStatus ) ), 4f),\r\n\t\t\t(TypeLibrary.GetType( typeof( MoreChoicesStatus ) ), 3f),\r\n\t\t\t(TypeLibrary.GetType( typeof( ReloadSpeedStatus ) ), 1f),\r\n\t\t\t(TypeLibrary.GetType( typeof( XpDamageStatus ) ), 1f),\r\n\t\t\t(TypeLibrary.GetType( typeof( HomingBulletStatus ) ), 4f),\r\n\t\t\t(TypeLibrary.GetType( typeof( ThornsStatus ) ), 1f),\r\n\t\t\t(TypeLibrary.GetType( typeof( BouncingBulletStatus ) ), 3f),\r\n\t\t};\r\n\r\n\t\tTypeDescription chosenPerk = null;\r\n\r\n\t\tfloat totalWeight = perks.Sum( x => x.Weight );\r\n\t\tvar rand = Game.Random.Float( 0f, totalWeight );\r\n\r\n\t\tfor ( int i = perks.Count - 1; i >= 0; i-- )\r\n\t\t{\r\n\t\t\tvar (type, weight) = perks[i];\r\n\t\t\trand -= weight;\r\n\r\n\t\t\tif ( rand < 0f )\r\n\t\t\t{\r\n\t\t\t\tchosenPerk = type;\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn chosenPerk;\r\n\t}\r\n\r\n\tStatus CreateStatus( TypeDescription type )\r\n\t{\r\n\t\tvar status = StatusManager.CreateStatus( type );\r\n\t\tvar currLevel = GetStatusLevel( type );\r\n\t\tstatus.Level = currLevel + 1;\r\n\t\treturn status;\r\n\t}\r\n\r\n\tpublic void Restart()\r\n\t{\r\n\t\tSprite.PlayAnimation( \"idle\" );\r\n\t\tSprite.PlaybackSpeed = 0.66f;\r\n\r\n\t\tSprite.Tint = new Color( 1f, 1f, 1f, 1f );\r\n\r\n\t\tSprite.LocalScale = new Vector3( Globals.SPRITE_SCALE );\r\n\r\n\t\tif ( IsProxy )\r\n\t\t\treturn;\r\n\r\n\t\tPosition2D = new Vector3( Game.Random.Float( -3f, 3f ), Game.Random.Float( -3f, 3f ) );\r\n\t\tManager.Instance.Camera2D.SetPos( Position2D );\r\n\r\n\t\tInitializeStats();\r\n\r\n\t\tManager.Instance.PlaySfxNearby( \"restart\", Position2D, Game.Random.Float( 0.95f, 1.05f ), 0.66f, 4f );\r\n\t}\r\n\r\n\tpublic void SpawnBulletRing( Vector2 pos, int numBullets, Vector2 aimDir, float damageMultMin = 1f, float damageMultMax = 1f )\r\n\t{\r\n\t\tfloat increment = 360f / numBullets;\r\n\r\n\t\tfor ( int i = 0; i < numBullets; i++ )\r\n\t\t{\r\n\t\t\tvar dir = Utils.RotateVector( aimDir, i * increment );\r\n\t\t\tfloat damageMult = Game.Random.Float( damageMultMin, damageMultMax );\r\n\t\t\tSpawnBullet( pos, dir, false, damageMult );\r\n\t\t}\r\n\r\n\t\tManager.Instance.PlaySfxNearby( \"shoot\", pos, pitch: 1f, volume: 1f, maxDist: 3f );\r\n\t}\r\n\r\n\tpublic Grenade SpawnGrenade( Vector2 pos, Vector2 vel )\r\n\t{\r\n\t\tvar grenadeObj = Manager.Instance.GrenadePrefab.Clone();\r\n\r\n\t\tvar grenade = grenadeObj.Components.Get<Grenade>();\r\n\t\tgrenade.Velocity = vel;\r\n\t\tgrenade.ExplosionSizeMultiplier = Stats[PlayerStat.ExplosionSizeMultiplier];\r\n\t\tgrenade.Player = this;\r\n\t\tgrenade.StickyPercent = Stats[PlayerStat.GrenadeStickyPercent];\r\n\t\tgrenade.FearChance = Stats[PlayerStat.GrenadeFearChance];\r\n\r\n\t\tif ( Stats[PlayerStat.GrenadesCanCrit] > 0f )\r\n\t\t{\r\n\t\t\tgrenade.CriticalChance = Stats[PlayerStat.CritChance];\r\n\t\t\tgrenade.CriticalMultiplier = Stats[PlayerStat.CritMultiplier];\r\n\t\t}\r\n\r\n\t\t//grenadeObj.NetworkSpawn( Network.Owner );\r\n\t\tgrenadeObj.WorldPosition = new Vector3( pos.x, pos.y, Globals.GetZPos( pos.y ) );\r\n\r\n\t\tManager.Instance.AddThing( grenade );\r\n\r\n\t\treturn grenade;\r\n\t}\r\n\r\n\tpublic void CreateShieldVfx()\r\n\t{\r\n\t\t_shieldVfx = Manager.Instance.ShieldVfxPrefab.Clone( WorldPosition );\r\n\t\t_shieldVfx.Parent = GameObject;\r\n\t\t_shieldVfx.LocalPosition = new Vector3( 0f, 0f, 0.1f );\r\n\t\t_shieldVfx.LocalScale = new Vector3( 1f ) * 1.8f * Globals.SPRITE_SCALE;\r\n\t\t_shieldVfx.LocalRotation = new Angles( 0f, -90f, 0f );\r\n\t}\r\n\r\n\tpublic void RemoveShieldVfx()\r\n\t{\r\n\t\tif ( _shieldVfx != null )\r\n\t\t{\r\n\t\t\t_shieldVfx.Destroy();\r\n\t\t\t_shieldVfx = null;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void PlaySfx( string name, Vector2 pos, float pitch, float volume )\r\n\t{\r\n\t\tvar sfx = Sound.Play( name, new Vector3( pos.x, pos.y, Globals.SFX_DEPTH ) );\r\n\r\n\t\tif ( sfx != null )\r\n\t\t{\r\n\t\t\tsfx.Volume = volume;\r\n\t\t\tsfx.Pitch = pitch;\r\n\t\t}\r\n\t}\r\n\r\n\tprotected override void OnStart()\r\n\t{\r\n\t\tbase.OnStart();\r\n\r\n\t\tManager.Instance.AddPlayer( this );\r\n\t}\r\n\r\n\tprotected override void OnDestroy()\r\n\t{\r\n\t\tbase.OnDestroy();\r\n\r\n\t\tManager.Instance.RemovePlayer( this );\r\n\t}\r\n\r\n\tvoid HandleCamShaking()\r\n\t{\r\n\t\tCamShakeAmount = 0f;\r\n\r\n\t\tfor ( int i = _camShakeDatas.Count - 1; i >= 0; i-- )\r\n\t\t{\r\n\t\t\tvar data = _camShakeDatas[i];\r\n\t\t\tvar time = data.useRealTime ? RealTime.Now : Time.Now;\r\n\r\n\t\t\tif ( time > data.startTime + data.time )\r\n\t\t\t{\r\n\t\t\t\t_camShakeDatas.RemoveAt( i );\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tfloat amount = Utils.Map( time, data.startTime, data.startTime + data.time, data.strength, 0f, data.easingType );\r\n\t\t\t\tCamShakeAmount = MathF.Max( amount, CamShakeAmount );\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void ShakeCam( float strength, float time, EasingType easingType = EasingType.Linear, bool useRealTime = false )\r\n\t{\r\n\t\tvar timeNow = useRealTime ? RealTime.Now : Time.Now;\r\n\t\t_camShakeDatas.Add( new CamShakeData( strength, timeNow, time, easingType, useRealTime ) );\r\n\t}\r\n}\r\n"
}
]
}