Search the source of every open source package.
561 results
using Sandbox;
using System.Drawing;
public class FloaterLock : Component
{
public int NumTurns { get; set; }
private TimeSince _timeSinceSpawn;
public float Lifetime { get; set; }
public bool ShowLocked { get; set ; }
private float _startScale;
private float _endScale;
private Vector2 _velocity;
private float _deceleration;
public float Scale { get; set; }
public float Opacity { get; set; }
public void Init( int numTurns )
{
NumTurns = numTurns;
Lifetime = 1.25f;
_velocity = new Vector2( 0f, 12f );
_deceleration = 4f;
Scale = _startScale = 1f;
_endScale = 1f;
}
protected override void OnStart()
{
base.OnStart();
_timeSinceSpawn = 0f;
Manager.Instance.PlaySfx( "lock_start", new Vector3( WorldPosition.x, WorldPosition.y, Manager.Instance.Camera.WorldPosition.z - 100f ), volume: 0.9f, pitch: Game.Random.Float( 1.05f, 1.13f ) );
}
protected override void OnUpdate()
{
Opacity = Utils.Map( _timeSinceSpawn, 0f, 0.3f, 0f, 1f, EasingType.QuadOut ) * Utils.Map( _timeSinceSpawn, 0f, Lifetime - 0.1f, 1f, 0.1f, EasingType.ExpoIn ) * Utils.Map( _timeSinceSpawn, Lifetime - 0.3f, Lifetime - 0.05f, 1f, 0f, EasingType.Linear );
Scale = Utils.Map( _timeSinceSpawn, 0f, Lifetime, _startScale, _endScale, EasingType.SineOut );
WorldPosition += new Vector3( _velocity.x, _velocity.y, 0f ) * Time.Delta;
_velocity *= (1f - _deceleration * Time.Delta);
if( !ShowLocked && _timeSinceSpawn > 0.5f )
{
ShowLocked = true;
_velocity = new Vector2( 0f, -20f );
_deceleration = 7f;
Manager.Instance.PlaySfx( "lock", new Vector3( WorldPosition.x, WorldPosition.y, Manager.Instance.Camera.WorldPosition.z - 100f ), volume: 2f, pitch: Game.Random.Float( 0.97f, 1.03f ) );
}
if ( _timeSinceSpawn > Lifetime )
{
Manager.Instance.NumFloaters--;
GameObject.Destroy();
}
}
}
using Microsoft.VisualBasic;
using Sandbox;
using Sandbox.UI;
using System.Runtime;
using System.Threading.Tasks;
public enum CardType { None, Apple, Bomb, Rocket, Telephone, Trumpet, Pawn, Balloon, Spider, Umbrella, UmbrellaClosed, Juggler, Coin, Vampire, Maracas, Microwave, Raccoon, Rock, Bear, Candle,
CrystalBall, Snake, Rose, RoseWilted, Bell, Ant, Twins, Compass, Antenna, Wizard, AncientScroll, JumpingSpider, Bento, Ninja, Flashlight, Bodybuilder, Helicopter, FlyingMoney,
Syringe, Cellphone, Crab, Magnet, Stethoscope, UFO, Teacher, Tractor, FortuneCookie, Dolphin, Detective, Genie, Potion, Mirror, FaxMachine, Dice, Coffee, Tree, Chipmunk,
Dancer, Wine, King, Police, Ogre, Clown, MoneyBag, Bank, Steak, MagicWand, Carrot, Caterpillar, Butterfly, Tornado, Map, Cockroach, Broccoli, Donut, Cheese, CheeseMoldy, GuideDog, Taco, Burrito,
Skunk, CarePackage, Cash, BirthdayCake, Gem, Pickaxe, Mountain, DiamondRing, Rabbit, RedEnvelope,
}
// StockMarket, StockMarketDown, Key, AlarmClock, ATM
public class Card : Component, IEventHandler
{
public GameObject Model { get; set; }
public ModelRenderer ModelRenderer { get; set; }
public GameObject IconObj { get; set; }
public GameObject IconBackObj { get; set; }
public bool IsFlagShown { get; set; }
public float CardIconScale { get; set; } = 1f;
public bool IsHovered { get; set; }
public bool IsRevealed { get; set; }
public bool IsSpawning { get; set; }
public int NumTimesRevealed { get; set; }
public int NumTimesChosen { get; set; }
public CardType CardType { get; private set; }
public CardType OriginalCardType { get; private set; }
public virtual bool IsFoodOrBeverage => false;
public virtual bool IsAlive => false;
public virtual bool CantBeMoved => false;
public IntVector2 GridPos { get; set; }
public bool IsShaking { get; private set; }
private TimeSince _timeSinceShakeStart; // todo: replace with timer? this can become negative somehow
private float _shakeTime;
private EasingType _shakeEasingType;
public Vector2 ShakeOffset { get; set; }
private float _shakeStrength;
public bool IsMovementControlled { get; set; }
private Vector3 _moveStartPos;
private Vector3 _moveTargetPos;
private float _moveTime;
private TimeSince _timeSinceMoveStart;
private EasingType _moveEasingType;
private bool _moveRemoveControlAfter;
public int HP { get; set; }
public bool IsLocked { get; set; }
public int LockTurnsRemaining { get; set; }
public bool WasJustLocked { get; set; }
public int CardTypeID { get; set; }
public float IconOpacity { get; set; }
public bool IsExploding;
private TimeSince _timeSinceStartExploding;
private float _explodeTime;
private EasingType _explodeEasingType;
private Vector3 _explodeStartPos;
private EasingType _explodeShakeEasingType;
public bool IsScaling;
private TimeSince _timeSinceScale;
private float _scaleTime;
private float _scaleAmount;
private EasingType _scaleEasingType;
public bool AreRevealMarksShown { get; set; }
protected override void OnAwake()
{
base.OnAwake();
}
public virtual void Init(CardType cardType)
{
CardType = OriginalCardType = cardType;
Model = GameObject.Children.Find( x => x.Name == "model" );
ModelRenderer = Model.Components.Get<ModelRenderer>();
IconOpacity = 1f;
IconObj = GameObject.Children.Find( x => x.Name == "icon" );
var cardIcon = IconObj.Components.Get<CardIcon>( includeDisabled: true );
cardIcon.Card = this;
IconBackObj = GameObject.Children.Find( x => x.Name == "icon_back" );
var cardIconBack = IconBackObj.Components.Get<CardIconBack>( includeDisabled: true );
cardIconBack.Card = this;
SetMaterial( cardType );
}
public void SetMaterial(CardType cardType)
{
var matFilename = GetMaterialFilename( cardType );
if ( !string.IsNullOrEmpty( matFilename ) )
ModelRenderer.MaterialOverride = Material.Load( matFilename );
}
public void SetCardType(CardType newType)
{
CardType = newType;
SetMaterial( newType );
}
protected override void OnUpdate()
{
//if ( Input.Down( "Duck" ) )
//{
// var emoji = Card.GetIconEmoji( CardType );
// if ( string.IsNullOrEmpty( emoji ) )
// {
// Gizmo.Draw.Color = Color.Black.WithAlpha( 1f );
// Gizmo.Draw.Text( $"{CardType}", new global::Transform( WorldPosition + new Vector3( -0.5f, -0.5f, -1f ) ), size: 24f );
// Gizmo.Draw.Color = Color.White.WithAlpha( 1f );
// Gizmo.Draw.Text( $"{CardType}", new global::Transform( WorldPosition ), size: 24f );
// }
// else
// {
// Gizmo.Draw.Color = Color.White.WithAlpha( 1f );
// Gizmo.Draw.Text( emoji, new global::Transform( WorldPosition ), size: 60f );
// }
// if ( IsMovementControlled )
// {
// Gizmo.Draw.Color = Color.White.WithAlpha( 1f );
// Gizmo.Draw.Text( "➡️", new global::Transform( WorldPosition + new Vector3( -15.5f, -19f, -1f ) ), size: 30f );
// }
// if ( IsLocked )
// {
// Gizmo.Draw.Color = Color.White.WithAlpha( 1f );
// Gizmo.Draw.Text( "🔒", new global::Transform( WorldPosition + new Vector3( 15.5f, -19f, -1f ) ), size: 30f );
// Gizmo.Draw.Color = Color.Red.WithAlpha( 1f );
// Gizmo.Draw.Text( $"{LockTurnsRemaining}", new global::Transform( WorldPosition + new Vector3( 15.5f, -21f, 1f ) ), size: 20f );
// }
// Gizmo.Draw.Color = Color.Black.WithAlpha( 1f );
// Gizmo.Draw.Text( $"ID: {CardTypeID}", new global::Transform( WorldPosition + new Vector3( -13.5f, 19f, -1f ) + new Vector3( -0.5f, -0.5f, -1f ) ), size: 16 );
// Gizmo.Draw.Color = Color.White.WithAlpha( 1f );
// Gizmo.Draw.Text( $"ID: {CardTypeID}", new global::Transform( WorldPosition + new Vector3( -13.5f, 19f, -1f ) ), size: 16 );
//}
HandleMoving();
HandleShaking();
if(IsExploding)
{
HandleExploding();
return;
}
if ( IsScaling )
{
HandleScaling();
}
//if(!IsScaling && !IsExploding)
//{
// Transform.LocalScale = new Vector3( Utils.DynamicEaseTo( Transform.LocalScale.x, IsHovered ? 1.05f : 1f, 0.3f, Time.Delta ) );
//}
if( IsSpawning )
{
// do nothing
}
else if( IsMovementControlled )
{
LocalPosition += new Vector3( ShakeOffset.x, ShakeOffset.y, 0f ) * Time.Delta * 10f;
}
else
{
var targetPos = Manager.GetCardPos( GridPos );
if ( IsShaking )
targetPos += new Vector3( ShakeOffset.x, ShakeOffset.y, 0f );
var height = 10f + ((IsRevealed || IsHovered) ? Globals.CARD_ADD_HEIGHT_REVEALED_OR_HOVERED : 0f) + ((IsRevealed && IsHovered) ? Globals.CARD_ADD_HEIGHT_REVEALED_AND_HOVERED : 0f);
var zPos = MathX.Lerp( LocalPosition.z, height, Time.Delta * 20f );
LocalPosition = Vector3.Lerp( LocalPosition, targetPos, Time.Delta * 10f ).WithZ( zPos );
}
var targetRot = Rotation.FromPitch( IsRevealed ? 0f : 180f );
WorldRotation = Rotation.Lerp( WorldRotation, targetRot, Time.Delta * 15f );
}
public void MoveToPos( Vector3 pos, float time, EasingType easingType = EasingType.Linear, bool removeControlAfter = false)
{
//Log.Info( $"Card - MoveToPos: {pos}" );
IsMovementControlled = true;
_moveStartPos = LocalPosition;
_moveTargetPos = pos;
_moveTime = time;
_timeSinceMoveStart = 0f;
_moveEasingType = easingType;
_moveRemoveControlAfter = removeControlAfter;
}
void HandleMoving()
{
if ( !IsMovementControlled )
return;
if(_timeSinceMoveStart > _moveTime)
{
LocalPosition = _moveTargetPos;
if( _moveRemoveControlAfter )
IsMovementControlled = false;
}
else
{
LocalPosition = Vector3.Lerp( _moveStartPos, _moveTargetPos, Utils.Map( _timeSinceMoveStart, 0f, _moveTime, 0f, 1f, _moveEasingType ) );
}
}
protected void HandleShaking()
{
if ( IsShaking )
{
if ( _timeSinceShakeStart > _shakeTime )
IsShaking = false;
else
ShakeOffset = Utils.GetRandomVector() * _shakeStrength * Utils.Map( _timeSinceShakeStart, 0f, _shakeTime, 1f, 0f, _shakeEasingType );
}
}
void HandleExploding()
{
LocalScale = new Vector3( Utils.Map(_timeSinceStartExploding, 0f, _explodeTime, 1f, Globals.CARD_EXPLODE_SCALE, _explodeEasingType) );
IconOpacity = Utils.Map( _timeSinceStartExploding, 0f, _explodeTime, 1f, 0f, EasingType.SineIn );
LocalPosition = _explodeStartPos + Vector3.Random * Utils.Map( _timeSinceStartExploding, 0f, _explodeTime, 0f, 2f, _explodeShakeEasingType) * Utils.Map( _timeSinceStartExploding, _explodeTime * 0.9f, _explodeTime, 1f, 0f );
}
void HandleScaling()
{
if(_timeSinceScale > 0.25f)
{
IsScaling = false;
LocalScale = new Vector3( 1f );
}
else
{
LocalScale = Utils.MapReturn( _timeSinceScale, 0f, _scaleTime, 1f, _scaleAmount, _scaleEasingType);
}
}
public static string GetName( CardType cardType )
{
switch ( cardType )
{
case CardType.CrystalBall: return "Crystal Ball";
case CardType.AncientScroll: return "Ancient Scroll";
case CardType.JumpingSpider: return "Jumping Spider";
case CardType.Rock: return "Boulder";
case CardType.Syringe: return "Morphine";
case CardType.FlyingMoney: return "Flying Money";
case CardType.FortuneCookie: return "Fortune Cookie";
//case CardType.AlarmClock: return "Alarm Clock";
case CardType.FaxMachine: return "Fax Machine";
case CardType.MoneyBag: return "Money Bag";
case CardType.MagicWand: return "Magic Wand";
//case CardType.StockMarket: return "Stock Market";
case CardType.GuideDog: return "Guide Dog";
case CardType.CarePackage: return "Care Package";
case CardType.BirthdayCake: return "Birthday Cake";
case CardType.DiamondRing: return "Diamond Ring";
case CardType.RedEnvelope: return "Red Envelope";
}
return cardType.ToString();
}
public static string GetFilename( CardType cardType )
{
switch ( cardType )
{
case CardType.Apple: return "apple";
case CardType.Bomb: return "bomb";
case CardType.Rocket: return "rocket";
case CardType.Telephone: return "telephone";
case CardType.Trumpet: return "trumpet";
case CardType.Pawn: return "pawn";
case CardType.Balloon: return "balloon";
case CardType.Spider: return "spider";
case CardType.Umbrella: return "umbrella";
case CardType.UmbrellaClosed: return "umbrella_closed";
case CardType.Juggler: return "juggler";
case CardType.Coin: return "coin";
case CardType.Vampire: return "vampire";
case CardType.Maracas: return "maracas";
case CardType.Microwave: return "microwave";
case CardType.Raccoon: return "raccoon";
case CardType.Rock: return "rock";
case CardType.Bear: return "bear";
case CardType.Candle: return "candle";
case CardType.CrystalBall: return "crystal_ball";
case CardType.Snake: return "snake";
case CardType.Rose: return "rose";
case CardType.RoseWilted: return "rose_wilted";
case CardType.Bell: return "bell";
case CardType.Ant: return "ant";
case CardType.Twins: return "twins";
case CardType.Compass: return "compass";
case CardType.Antenna: return "antenna";
case CardType.Wizard: return "wizard";
case CardType.AncientScroll: return "scroll";
case CardType.JumpingSpider: return "spider_jumping";
case CardType.Bento: return "bento";
//case CardType.StockMarket: return "investment";
//case CardType.StockMarketDown: return "investment_down";
case CardType.Ninja: return "ninja";
case CardType.Flashlight: return "flashlight";
case CardType.Bodybuilder: return "bodybuilder";
case CardType.Helicopter: return "helicopter";
case CardType.FlyingMoney: return "flying_money";
case CardType.Syringe: return "syringe";
case CardType.Cellphone: return "cellphone";
case CardType.Crab: return "crab";
case CardType.Magnet: return "magnet";
case CardType.Stethoscope: return "stethoscope";
case CardType.UFO: return "ufo";
case CardType.Teacher: return "teacher";
case CardType.Tractor: return "tractor";
case CardType.FortuneCookie: return "fortune_cookie";
case CardType.Dolphin: return "dolphin";
case CardType.Detective: return "detective";
case CardType.Genie: return "genie";
case CardType.Potion: return "potion";
case CardType.Mirror: return "mirror";
//case CardType.AlarmClock: return "alarm_clock";
case CardType.FaxMachine: return "fax_machine";
case CardType.Dice: return "dice";
case CardType.Coffee: return "coffee";
case CardType.Tree: return "tree";
case CardType.Chipmunk: return "chipmunk";
case CardType.Dancer: return "dancer";
case CardType.Wine: return "wine";
case CardType.King: return "king";
case CardType.Police: return "police";
case CardType.Ogre: return "ogre";
case CardType.Clown: return "clown";
case CardType.MoneyBag: return "money_bag";
case CardType.Bank: return "bank";
case CardType.Steak: return "steak";
case CardType.MagicWand: return "magic_wand";
case CardType.Carrot: return "carrot";
case CardType.Caterpillar: return "caterpillar";
case CardType.Butterfly: return "butterfly";
case CardType.Tornado: return "tornado";
case CardType.Map: return "map";
case CardType.Cockroach: return "cockroach";
//case CardType.Key: return "key";
case CardType.Broccoli: return "broccoli";
//case CardType.ATM: return "atm";
case CardType.Donut: return "donut";
case CardType.Cheese: return "cheese";
case CardType.CheeseMoldy: return "cheese_moldy";
case CardType.GuideDog: return "guide_dog";
case CardType.Taco: return "taco";
case CardType.Burrito: return "burrito";
case CardType.Skunk: return "skunk";
case CardType.CarePackage: return "care_package";
case CardType.Cash: return "cash";
case CardType.BirthdayCake: return "birthday_cake";
case CardType.Gem: return "gem";
case CardType.Pickaxe: return "pickaxe";
case CardType.Mountain: return "mountain";
case CardType.DiamondRing: return "diamond_ring";
case CardType.Rabbit: return "rabbit";
case CardType.RedEnvelope: return "red_envelope";
}
return "";
}
public static string GetIconFilename( CardType cardType )
{
return $"textures/{GetFilename( cardType )}.png";
}
public static string GetMaterialFilename( CardType cardType )
{
return $"materials/cards/{GetFilename( cardType )}.vmat";
}
public static string GetIconEmoji( CardType cardType )
{
switch ( cardType )
{
case CardType.Apple: return "🍎";
case CardType.Bomb: return "💣";
case CardType.Rocket: return "🚀";
case CardType.Telephone: return "☎️";
case CardType.Trumpet: return "🎺";
case CardType.Pawn: return "♟️";
case CardType.Balloon: return "🎈";
case CardType.Spider: return "🕷️";
case CardType.Umbrella: return "☂️";
case CardType.UmbrellaClosed: return "🌂";
case CardType.Juggler: return "🤹";
case CardType.Coin: return "🟡";
case CardType.Vampire: return "🧛";
case CardType.Raccoon: return "🦝";
case CardType.Rock: return "🗿";
case CardType.Bear: return "🐻";
case CardType.Candle: return "🕯️";
case CardType.CrystalBall: return "🔮";
case CardType.Snake: return "🐍";
case CardType.Rose: return "🌹";
case CardType.RoseWilted: return "🥀";
case CardType.Bell: return "🔔";
case CardType.Ant: return "🐜";
case CardType.Twins: return "👯";
case CardType.Compass: return "🧭";
case CardType.Antenna: return "📡";
case CardType.Wizard: return "🧙";
case CardType.AncientScroll: return "📜";
case CardType.JumpingSpider: return "🕷️";
case CardType.Bento: return "🍱";
//case CardType.StockMarket: return "📈";
//case CardType.StockMarketDown: return "📉";
case CardType.Ninja: return "🐱👤";
case CardType.Flashlight: return "🔦";
case CardType.Bodybuilder: return "🏋️";
case CardType.Helicopter: return "🚁";
case CardType.FlyingMoney: return "💸";
case CardType.Syringe: return "💉";
case CardType.Cellphone: return "📱";
case CardType.Crab: return "🦀";
case CardType.Magnet: return "🧲";
case CardType.Stethoscope: return "🩺";
case CardType.UFO: return "🛸";
case CardType.Teacher: return "👩🏫";
case CardType.Tractor: return "🚜";
case CardType.FortuneCookie: return "🥠";
case CardType.Dolphin: return "🐬";
case CardType.Detective: return "🕵️";
case CardType.Genie: return "🧞";
case CardType.Potion: return "🧪";
case CardType.Mirror: return "🔘";
//case CardType.AlarmClock: return "⏰";
case CardType.FaxMachine: return "📠";
case CardType.Dice: return "🎲";
case CardType.Coffee: return "☕";
case CardType.Tree: return "🌳";
case CardType.Chipmunk: return "🐿️";
case CardType.Dancer: return "💃";
case CardType.Wine: return "🍷";
case CardType.King: return "🤴";
case CardType.Police: return "👮";
case CardType.Ogre: return "👹";
case CardType.Clown: return "🤡";
case CardType.MoneyBag: return "💰";
case CardType.Bank: return "🏦";
case CardType.Steak: return "🥩";
case CardType.MagicWand: return "✨";
case CardType.Carrot: return "🥕";
case CardType.Caterpillar: return "🐛";
case CardType.Butterfly: return "🦋";
case CardType.Tornado: return "🌪️";
case CardType.Map: return "🗺️";
case CardType.Cockroach: return "𓆣";
//case CardType.Key: return "🔑";
case CardType.Broccoli: return "🥦";
//case CardType.ATM: return "🏧";
case CardType.Donut: return "🍩";
case CardType.Cheese: return "🧀";
case CardType.CheeseMoldy: return "🤮";
case CardType.GuideDog: return "🦮";
case CardType.Taco: return "🌮";
case CardType.Burrito: return "🌯";
case CardType.Skunk: return "🦨";
case CardType.CarePackage: return "📦";
case CardType.Cash: return "💵";
case CardType.BirthdayCake: return "🎂";
case CardType.Gem: return "💎";
case CardType.Pickaxe: return "⛏️";
case CardType.Mountain: return "⛰️";
case CardType.DiamondRing: return "💍";
case CardType.Rabbit: return "🐇";
case CardType.RedEnvelope: return "🧧";
}
return "";
}
public static string GetCardDescription( CardType cardType )
{
switch ( cardType )
{
case CardType.Apple: return "✅Match: +1 HP";
case CardType.Bomb: return "✅Match: reveal nearby cards then shuffle them";
case CardType.Rocket: return "❌Mismatch: swap position";
case CardType.Telephone: return "✅Match: shake nearby pairs";
case CardType.Trumpet: return "Match other cards: 30% chance to reveal";
case CardType.Pawn: return "➡️Turn start: 50% to try to move upward";
case CardType.Balloon: return "Floats upward";
case CardType.Spider: return "Starts in a corner";
case CardType.Umbrella: return "👁️Revealed: toggle open/closed";
case CardType.Juggler: return "❌Mismatch: rearrange nearby cards";
case CardType.Coin: return "✅Match: +$2";
case CardType.Vampire: return "While hovered: shake if you lose HP";
case CardType.Maracas: return "Makes a distinctive sound when shook";
case CardType.Microwave: return "➡️Turn start: shake a nearby food/drink";
case CardType.Raccoon: return "➡️Turn start: shake an adjacent card";
case CardType.Rock: return "Falls downward, pushing other cards";
case CardType.Bear: return "Food/drink revealed: 20% chance to shake";
case CardType.Candle: return "✅Match: each reveal a nearby card";
case CardType.CrystalBall: return "❌Mismatch: shake cards, including other card's match";
case CardType.Snake: return "Shake when an adjacent card is matched";
case CardType.Rose: return "When moved: shake and wilt";
case CardType.Bell: return "Rings when moved or shook";
case CardType.Ant: return "➡️Turn start: move to nearby empty space";
case CardType.Twins: return "👁️Revealed: nudge toward match";
case CardType.Compass: return "❌Mismatch: nudge toward other card's match";
case CardType.Antenna: return "👁️Revealed: sound indicates distance to match";
case CardType.Wizard: return "⏩Every 3 turns: shuffle some cards\n💔Damage Wizard: respawn some cards and shuffle them";
case CardType.AncientScroll: return "✅Match: reveal all cards (after next turn, shuffle all cards)";
case CardType.JumpingSpider: return "Starts in a corner\n❌Mismatch: swaps with an unknown card";
case CardType.Bento: return "✅Match: +1 HP for each consecutive match";
//case CardType.StockMarket: return "✅Match: gain half your money if the market is up, lose half if the market is down";
case CardType.Ninja: return "➡️Turn start: 25% chance to swap position";
case CardType.Flashlight: return "👆Chosen: reveal a nearby card";
case CardType.Bodybuilder: return "➡️Turn start: move adjacent card upwards, pushing other cards";
case CardType.Helicopter: return "❌Mismatch: relocate along with an adjacent card";
case CardType.FlyingMoney: return "✅Match: +$3\n❌Mismatch: shuffle with some nearby cards";
case CardType.Syringe: return "✅Match: heal all HP, but time is halved until end of level";
case CardType.Cellphone: return "❌Mismatch: vibrate nearest cellphone";
case CardType.Crab: return "Moves sideways to empty spaces";
case CardType.Magnet: return "➡️Turn start: pull card toward it";
case CardType.Stethoscope: return "✅Match/❌Mismatch: shake nearby cards with a heartbeat";
case CardType.UFO: return "✅Match: abduct and shuffle nearby cards";
case CardType.Teacher: return "✅Match: arrange row alphabetically";
case CardType.Tractor: return "✅Match/❌Mismatch: shift row one space";
case CardType.FortuneCookie: return "✅Match: +1 HP, and reveal 5 cards that you haven't seen before";
case CardType.Dolphin: return "Revealed on 4 consecutive mismatches";
case CardType.Detective: return "➡️Turn start: 50% chance to reveal a nearby card";
case CardType.Genie: return "✅Match: next card chosen shakes its match";
case CardType.Potion: return "✅Match: your HP is set to half";
case CardType.Mirror: return "✅Match: flip the level horizontally";
//case CardType.AlarmClock: return "70% chance to shake when timer goes below 1s";
case CardType.FaxMachine: return "👆Chosen: shake 3 cards, including another fax machine";
case CardType.Dice: return "❌Mismatch: roll a 6 to get +$2 (rolling 1 loses $1)\n✅Match: Roll a 12 to get +$5 (rolling 2 loses $2)";
case CardType.Coffee: return "✅Match: +2 HP, and your next turn lasts 3 seconds";
case CardType.Tree: return "✅Match: reveal an apple";
case CardType.Chipmunk: return "➡️Turn start: move toward a tree";
case CardType.Dancer: return "❌Mismatch: dance with multiple partners";
case CardType.Wine: return "✅Match: +2 HP\nShake if an adjacent card shakes";
case CardType.King: return "💔Damage King: he decides your next choice";
case CardType.Police: return "❌Mismatch: Reveal and lock a nearby card for 2 turns\n💔Damage Police: Lock some nearby cards for 1 turn, then shuffle";
case CardType.Ogre: return "💔Damage Ogre: shuffle with nearby cards";
case CardType.Clown: return "💔Damage Clown: swap multiple times";
case CardType.MoneyBag: return "✅Match: +$5";
case CardType.Bank: return "✅Match: transform all coins into money bags";
case CardType.Steak: return "✅Match: -$1 and +4 HP";
case CardType.MagicWand: return "❌Mismatch: send other card adjacent to its match";
case CardType.Carrot: return "✅Match: +3 HP, and timer is doubled next turn";
case CardType.Caterpillar: return "➡️Turn start: 20% chance to shake and transform into a butterfly";
case CardType.Tornado: return "➡️Turn start: swap 2 adjacent cards";
case CardType.Map: return "✅Match: reveal all cards between";
case CardType.Cockroach: return "Can't be matched until no other cards remain";
//case CardType.Key: return "❌Mismatch: unlock other card\n✅Match: unlock all cards";
case CardType.Broccoli: return "✅Match: +3 HP";
//case CardType.ATM: return "✅Match: +$1 for every 2 matches you've made this level (max: $7)";
case CardType.Donut: return "✅Match: +1 HP for each adjacent empty space";
case CardType.Cheese: return "✅Match: +3 HP if not moldy\nGoes moldy after 7 turns";
case CardType.GuideDog: return "When you mismatch other cards, 50% chance to point toward one of their matches";
case CardType.Taco: return "✅Match: +1 Max HP";
case CardType.Burrito: return "✅Match: +2 Max HP";
case CardType.Skunk: return "Can't be matched while adjacent to any non-skunks";
case CardType.CarePackage: return "✅Match: +$1 for every 2 HP missing";
case CardType.Cash: return "✅Match: +$3 if both cash are nearby eachother, otherwise +$1";
case CardType.BirthdayCake: return "✅Match: +1 HP and +$2";
case CardType.Gem: return "✅Match: +$4";
case CardType.Pickaxe: return "✅Match: transform all boulders into gems";
case CardType.Mountain: return "Can't be moved";
case CardType.DiamondRing: return "✅Match: +$3 if no other cards remain, otherwise +$1";
case CardType.Rabbit: return "➡️Turn start: 50% chance to move towards its match";
case CardType.RedEnvelope: return "✅Match: if you have less than $5, set your money to $5";
}
return "";
}
public static string GetCardExplanation( CardType cardType )
{
switch ( cardType )
{
case CardType.Ant: return "\"nearby\" means 1 space away\n(including diagonal)";
case CardType.Snake: return "\"adjacent\" means 1 space away\n(NOT including diagonal)";
}
return "";
}
public static bool HasHP( CardType cardType )
{
switch ( cardType )
{
case CardType.Ogre: return true;
case CardType.Clown: return true;
case CardType.Police: return true;
case CardType.Wizard: return true;
case CardType.King: return true;
}
return false;
}
public static int GetMaxHP( CardType cardType )
{
switch ( cardType )
{
case CardType.Ogre: return 2;
case CardType.Clown: return 2;
case CardType.Police: return 2;
case CardType.Wizard: return 3;
case CardType.King: return 3;
}
return 0;
}
public void SetRevealedFromAsync(bool revealed)
{
IsRevealed = revealed;
if( Card.HasHP(CardType) || IsLocked )
IconObj.Enabled = revealed;
if ( revealed )
{
NumTimesRevealed++;
if(Manager.Instance.Stats[StatType.MaxCrayonMarks] > 0f && !AreRevealMarksShown)
ShowRevealMarks();
}
}
public void SetRevealedInstant()
{
IsRevealed = true;
if ( Card.HasHP( CardType ) || IsLocked )
IconObj.Enabled = true;
WorldRotation = Rotation.FromPitch( 0f );
NumTimesRevealed++;
if ( Manager.Instance.Stats[StatType.MaxCrayonMarks] > 0f && !AreRevealMarksShown )
ShowRevealMarks();
}
public void SetHiddenInstant()
{
IsRevealed = false;
if ( Card.HasHP( CardType ) )
IconObj.Enabled = false;
WorldRotation = Rotation.FromPitch( 180f );
}
public void Shake( float strength, float time = 0.6f, EasingType easingType = EasingType.QuadOut, bool playSfx = true )
{
IsShaking = true;
_timeSinceShakeStart = 0f;
_shakeTime = time;
_shakeEasingType = easingType;
_shakeStrength = strength;
if ( playSfx || ( OverrideShakeSfx && ( IsRevealed || !OnlyOverrideShakeSfxWhenRevealed ) ) )
PlayShakeSfx();
}
public virtual void PlayShakeSfx()
{
//Manager.Instance.PlayCardSfx( "shake_1", this, volume: 1.1f, pitch: Game.Random.Float( 1.4f, 1.6f ) );
Manager.Instance.PlayCardSfx( "shake_3", this, volume: 1.35f, pitch: Game.Random.Float( 0.94f, 1.06f ) );
}
public virtual bool OverrideShakeSfx => false;
public virtual bool OnlyOverrideShakeSfxWhenRevealed => false;
public virtual void PlayHurtSfx(Card card0, Card card1)
{
Manager.Instance.PlayCardSfxBetween( "shake_1", card0, card1, volume: 1.3f, pitch: Game.Random.Float( 1.4f, 1.6f ) );
}
// return false if needed to change start pos
public virtual bool ValidateStartingGridPos()
{
return true;
}
public virtual bool ShouldHandleEvent( EventType eventType )
{
return false;
}
public virtual async Task HandleEventAsync(EventType eventType)
{
await Task.Frame();
}
public virtual string GetEventText( EventType eventType )
{
return GetCardDescription( CardType );
}
public async Task Hurt()
{
if(!Card.HasHP(CardType))
{
Log.Error( $"Trying to hurt card {CardType} with no HP!" );
return;
}
HP--;
await Manager.Instance.ShakeCard(this, playSfx: false);
}
public void Lock(int numTurns)
{
IsLocked = true;
LockTurnsRemaining = numTurns;
WasJustLocked = true;
}
public void Unlock()
{
IsLocked = false;
}
public virtual bool CanBeMatched()
{
return true;
}
public void StartExploding(float explodeTime)
{
IsExploding = true;
_timeSinceStartExploding = 0f;
_explodeTime = explodeTime;
_explodeStartPos = LocalPosition;
int rand = Game.Random.Int( 0, 5 );
switch(rand)
{
case 0: _explodeEasingType = EasingType.SineIn; break;
case 1: _explodeEasingType = EasingType.SineIn; break;
case 2: _explodeEasingType = EasingType.QuadIn; break;
case 3: _explodeEasingType = EasingType.QuadIn; break;
case 4: _explodeEasingType = EasingType.QuartIn; break;
case 5: _explodeEasingType = EasingType.Linear; break;
}
rand = Game.Random.Int( 0, 7 );
switch ( rand )
{
case 0: _explodeShakeEasingType = EasingType.SineIn; break;
case 1: _explodeShakeEasingType = EasingType.QuadIn; break;
case 2: _explodeShakeEasingType = EasingType.QuartIn; break;
case 3: _explodeShakeEasingType = EasingType.ExpoIn; break;
case 4: _explodeShakeEasingType = EasingType.ExpoIn; break;
case 5: _explodeShakeEasingType = EasingType.ExpoIn; break;
case 6: _explodeShakeEasingType = EasingType.ExpoIn; break;
case 7: _explodeShakeEasingType = EasingType.Linear; break;
}
}
public void Explode( int breakNum )
{
var scale = LocalScale.x * Model.LocalScale.x;
Manager.Instance.SpawnCardBreak( WorldPosition, LocalRotation, scale, ModelRenderer.MaterialOverride, breakNum );
Manager.Instance.PlayCardSfx( "explosion", this, volume: Game.Random.Float( 0.4f, 0.55f ), pitch: Game.Random.Float( 0.4f, 2f ) );
Manager.Instance.CardBreakParticlesPrefab.Clone( WorldPosition.WithZ( 25f ) );
Manager.Instance.CardBreakShockwavePrefab.Clone( WorldPosition.WithZ( 55f ) );
GameObject.Destroy();
}
public void StartScaling(float time, float amount, EasingType easingType)
{
IsScaling = true;
_timeSinceScale = 0f;
_scaleTime = time;
_scaleAmount = amount;
_scaleEasingType = easingType;
}
public void ShowFlag()
{
IsFlagShown = true;
IconBackObj.Enabled = true;
}
public void HideFlag()
{
IsFlagShown = false;
if ( !AreRevealMarksShown )
IconBackObj.Enabled = false;
}
public void ShowRevealMarks()
{
AreRevealMarksShown = true;
IconBackObj.Enabled = true;
}
}
using Sandbox;
using System.Net;
using System.Threading.Tasks;
public class CardBank : Card
{
public override bool ShouldHandleEvent( EventType eventType )
{
switch ( eventType )
{
case EventType.Match:
return Manager.Instance.ChosenCards[1] == this;
}
return false;
}
public override async Task HandleEventAsync( EventType eventType )
{
Manager.Instance.PushEventMessage( this, eventType );
await Task.DelayRealtime( 150 );
var coins = Manager.Instance.Cards.Where( x => x.CardType == CardType.Coin ).ToList();
if(coins.Count > 0)
{
Manager.Instance.PlayCardSfxBetween( "bank", Manager.Instance.ChosenCards[0], Manager.Instance.ChosenCards[1], volume: 1f, pitch: Game.Random.Float( 0.9f, 1.1f ) );
await Task.DelayRealtime( 50 );
foreach ( var coin in coins )
coin.SetCardType( CardType.MoneyBag );
await Task.DelayRealtime( 300 );
}
await Task.DelayRealtime( 800 );
Manager.Instance.PopEventMessage();
}
}
using Sandbox;
using System.Threading.Tasks;
public class CardCockroach : Card
{
public override bool IsAlive => true;
public override bool ShouldHandleEvent( EventType eventType )
{
return eventType == EventType.Mismatch && Manager.Instance.ChosenCards[0].CardType == CardType.Cockroach && Manager.Instance.ChosenCards[1].CardType == CardType.Cockroach && !IsLocked;
}
public override async Task HandleEventAsync( EventType eventType )
{
Manager.Instance.PushEventMessage( this, eventType );
await Task.DelayRealtime( 850 );
Manager.Instance.PopEventMessage();
}
public override bool CanBeMatched()
{
foreach(var card in Manager.Instance.Cards)
{
if ( card.CardType != CardType.Cockroach )
return false;
}
return true;
}
}
using Sandbox;
using System.Threading.Tasks;
public class CardDancer : Card
{
public override bool IsAlive => true;
public override bool ShouldHandleEvent( EventType eventType )
{
return eventType == EventType.Mismatch && Manager.Instance.ChosenCards.Contains( this ) && !Manager.Instance.IsMismatchALockedMatch;
}
public override async Task HandleEventAsync( EventType eventType )
{
Manager.Instance.PushEventMessage( this, eventType );
List<Card> neighbourCards = new();
List<IntVector2> neighboursEmpty = new();
Card lastPartnerCard = null;
await Task.DelayRealtime( 300 );
Manager.Instance.PlayCardSfx( "dancer", this, volume: 1.1f, pitch: Game.Random.Float( 0.98f, 1.02f ) );
for (int i = 0; i < 4; i++)
{
neighbourCards.Clear();
neighboursEmpty.Clear();
EvaluateNeighbour( GridPos + new IntVector2( -1, 0 ), neighbourCards, neighboursEmpty );
EvaluateNeighbour( GridPos + new IntVector2( 1, 0 ), neighbourCards, neighboursEmpty );
EvaluateNeighbour( GridPos + new IntVector2( 0, -1 ), neighbourCards, neighboursEmpty );
EvaluateNeighbour( GridPos + new IntVector2( 0, 1 ), neighbourCards, neighboursEmpty );
IntVector2 targetGridPos;
Card targetCard = null;
var validNeighbourCards = neighbourCards.Where( x => x != lastPartnerCard ).ToList();
if ( validNeighbourCards.Count > 0 )
{
validNeighbourCards.Shuffle();
targetCard = validNeighbourCards.First();
targetGridPos = targetCard.GridPos;
}
else
{
neighboursEmpty.Shuffle();
targetGridPos = neighboursEmpty.First();
}
if ( targetCard != null )
{
await Manager.Instance.ShakeCard( this );
await Manager.Instance.ShakeCard( targetCard );
lastPartnerCard = targetCard;
await Task.DelayRealtime( 650 );
if(i < 3)
{
await Manager.Instance.SwapCardPositions( this, targetCard );
await Task.DelayRealtime( 250 );
}
}
else
{
lastPartnerCard = null;
if ( i < 3 )
{
Manager.Instance.RemoveCardGridPos( this );
await Manager.Instance.SetCardGridPos( this, targetGridPos );
await Task.DelayRealtime( 400 );
}
}
}
await Task.DelayRealtime( 100 );
Manager.Instance.PopEventMessage();
await Manager.Instance.EventHappened( EventType.AfterCardsMoved );
}
void EvaluateNeighbour(IntVector2 gridPos, List<Card> neighbourCards, List<IntVector2> empty)
{
if ( !Manager.Instance.IsGridPosInBounds( gridPos ) )
return;
var card = Manager.Instance.GetCardAtGridPos( gridPos );
if ( card != null )
neighbourCards.Add( card );
else
empty.Add( gridPos );
}
}
using Sandbox;
using System.Threading.Tasks;
using System.Xml.Linq;
public class CardDolphin : Card
{
public override bool IsAlive => true;
public override bool ShouldHandleEvent( EventType eventType )
{
return eventType == EventType.Mismatch && !Manager.Instance.ChosenCards.Contains(this) && Manager.Instance.Stats[StatType.ConsecutiveMismatches] == 3;
}
public override async Task HandleEventAsync( EventType eventType )
{
Manager.Instance.PushEventMessage( this, eventType );
await Task.DelayRealtime( 250 );
Manager.Instance.PlayCardSfx( "card_flip", this, volume: 0.8f, pitch: Game.Random.Float( 1.15f, 1.2f ) );
await Task.DelayRealtime( 100 );
Manager.Instance.PlayCardSfx( "dolphin", this, volume: 0.8f, pitch: Game.Random.Float( 1f, 1.15f ) );
await Manager.Instance.RevealCard( this );
await Task.DelayRealtime( Game.Random.Int(400, 700) );
Manager.Instance.HideCard( this );
Manager.Instance.PlayCardSfx( "card_flip", this, volume: 0.7f, pitch: Game.Random.Float( 0.65f, 0.75f ) );
Manager.Instance.PopEventMessage();
}
}
using Sandbox;
using System.Threading.Tasks;
public class CardGenie : Card
{
public override bool IsAlive => true;
public override bool ShouldHandleEvent( EventType eventType )
{
return eventType == EventType.Match && Manager.Instance.ChosenCards[0] == this;
}
public override async Task HandleEventAsync( EventType eventType )
{
Manager.Instance.PushEventMessage( this, eventType );
await Task.DelayRealtime( 250 );
Manager.Instance.PlayCardSfxBetween( "genie", Manager.Instance.ChosenCards[0], Manager.Instance.ChosenCards[1], volume: 0.6f, pitch: Game.Random.Float( 1.25f, 1.26f ) );
Manager.Instance.AddStatus( "StatusGenie" );
await Task.DelayRealtime( 1750 );
Manager.Instance.PopEventMessage();
}
}
public class StatusGenie : StatusEffect
{
public float TimerReductionAmount { get; set; }
public override bool ShouldHandleEvent( EventType eventType )
{
return eventType == EventType.Choose;
}
public override async Task HandleEventAsync( EventType eventType )
{
var validCards = Manager.Instance.Cards.Where(x => x.CardType == Manager.Instance.ChosenCard.CardType && x != Manager.Instance.ChosenCard).ToList();
if ( validCards.Count == 0 )
return;
Manager.Instance.PushEventMessage( this, eventType );
await Task.DelayRealtime( 250 );
Manager.Instance.PlayCardSfx( "genie_choose", Manager.Instance.ChosenCard, volume: 0.9f, pitch: Game.Random.Float( 0.95f, 1.05f ) );
await Task.DelayRealtime( 750 );
validCards.Shuffle();
await Manager.Instance.ShakeCard( validCards.FirstOrDefault() );
await Task.DelayRealtime( 750 );
Manager.Instance.PopEventMessage();
Manager.Instance.RemoveStatus( this );
}
public override string GetEventIcon( EventType eventType )
{
return "textures/genie.png";
}
public override string GetEventText( EventType eventType )
{
return "Shake matching card";
}
}
using Sandbox;
using System.Threading.Tasks;
public class CardKey : Card
{
//public override bool ShouldHandleEvent( EventType eventType )
//{
// return eventType == EventType.Mismatch && Manager.Instance.ChosenCards.Contains(this) || eventType == EventType.Match && Manager.Instance.ChosenCards[1] == this;
//}
//public override async Task HandleEventAsync( EventType eventType )
//{
// if ( eventType == EventType.Mismatch )
// {
// var key = Manager.Instance.ChosenCards[0].CardType == CardType.Key ? Manager.Instance.ChosenCards[0] : Manager.Instance.ChosenCards[1];
// var otherCard = Manager.Instance.ChosenCards[0].CardType == CardType.Key ? Manager.Instance.ChosenCards[1] : Manager.Instance.ChosenCards[0];
// if ( key.IsLocked || !otherCard.IsLocked )
// return;
// Manager.Instance.PushEventMessage( this, eventType );
// await Task.DelayRealtime( 50 );
// Manager.Instance.PlayCardSfxBetween( "key", Manager.Instance.ChosenCards[0], Manager.Instance.ChosenCards[1], volume: 1.5f, pitch: Game.Random.Float( 0.9f, 1.1f ) );
// await Task.DelayRealtime( 50 );
// await Manager.Instance.UnlockCard( otherCard );
// await Task.DelayRealtime( 900 );
// Manager.Instance.PopEventMessage();
// }
// else if (eventType == EventType.Match)
// {
// Manager.Instance.PushEventMessage( this, eventType );
// await Task.DelayRealtime( 50 );
// var lockedCards = Manager.Instance.Cards.Where( x => x.IsLocked ).ToList();
// if ( lockedCards.Count == 0 )
// {
// await Task.DelayRealtime( 800 );
// Manager.Instance.PopEventMessage();
// return;
// }
// Manager.Instance.PlayCardSfxBetween( "key", Manager.Instance.ChosenCards[0], Manager.Instance.ChosenCards[1], volume: 1.5f, pitch: Game.Random.Float( 0.9f, 1.1f ) );
// await Task.DelayRealtime( 50 );
// foreach ( var card in lockedCards )
// {
// await Manager.Instance.UnlockCard( card );
// }
// await Task.DelayRealtime( 1000 );
// Manager.Instance.PopEventMessage();
// }
//}
//public override string GetEventText( EventType eventType )
//{
// if ( eventType == EventType.Mismatch )
// {
// return "❌Mismatch: unlock other card";
// }
// else
// {
// return "✅Match: unlock all cards";
// }
//}
}
using Sandbox;
using System.Reflection.PortableExecutable;
using System.Runtime.Versioning;
using System.Threading.Tasks;
public class RelicGuideDog : Relic
{
public override void Init()
{
base.Init();
MaxLevel = 1;
}
}
using Sandbox;
using System.Reflection.PortableExecutable;
using System.Runtime.Versioning;
using System.Threading.Tasks;
public class RelicMammothMeat : Relic
{
public override void LevelUp()
{
base.LevelUp();
var mouse = Mouse.Position;
var camera = Scene.Camera;
var ray = camera.ScreenPixelToRay( mouse );
var tr = Scene.Trace.Ray( ray, 10000f ).Run();
Manager.Instance.PlaySfxCenter( "mammoth_meat", volume: 0.75f, pitch: Game.Random.Float( 0.85f, 0.9f ) );
int maxHpAmount = 8;
Manager.Instance.SpawnMaxHPFloater( maxHpAmount, tr.EndPosition );// + new Vector3(0f, -30f, 0f) );
Manager.Instance.MaxHP += maxHpAmount;
//int hpAmount = 5;
//Manager.Instance.HP = Math.Min( Manager.Instance.HP + hpAmount, Manager.Instance.MaxHP );
Manager.Instance.TimeSinceHPChanged = 0f;
//Manager.Instance.SpawnHealHPFloater( hpAmount, tr.EndPosition );
}
}
using Sandbox;
using System.Reflection.PortableExecutable;
using System.Runtime.Versioning;
using System.Threading.Tasks;
public class RelicMantlepieceClock : Relic
{
public override void LevelUp()
{
base.LevelUp();
Manager.Instance.Stats[StatType.NumExtraBountyTurns] += (Level == 1 ? 2f : 1f);
}
}
using Sandbox;
using System.Reflection.PortableExecutable;
using System.Runtime.Versioning;
using System.Threading.Tasks;
public class RelicMedicalLicense : Relic
{
public override void Init()
{
base.Init();
MaxLevel = 1;
}
public override bool ShouldHandleEvent( EventType eventType )
{
return eventType == EventType.OverhealHP;
}
public override async Task HandleEventAsync( EventType eventType )
{
await Task.DelayRealtime( 1000 );
Manager.Instance.PushEventMessage( this, eventType );
var camera = Scene.Camera;
var ray = camera.ScreenPixelToRay( new Vector3( Screen.Width / 2, Screen.Height / 2, 0f ) );
var tr = Scene.Trace.Ray( ray, 10000f ).Run();
Manager.Instance.PlaySfx( "medical_license", tr.EndPosition.WithZ( Scene.Camera.WorldPosition.z - Globals.CARD_SFX_DEPTH_DIFF ), volume: 1.2f, pitch: Game.Random.Float( 0.95f, 1.05f ) );
await Task.DelayRealtime( 350 );
int moneyAmount = (int)Manager.Instance.Stats[StatType.LatestOverhealAmount] + (int)Manager.Instance.Stats[StatType.EarnExtraMoney];
Manager.Instance.SpawnGainMoneyFloater( moneyAmount, tr.EndPosition );
await Manager.Instance.GainMoney( moneyAmount );
await Task.DelayRealtime( 650 );
Manager.Instance.PopEventMessage();
}
}
using Sandbox;
using System.Reflection.PortableExecutable;
using System.Runtime.Versioning;
using System.Threading.Tasks;
public class RelicMouseTrap : Relic
{
public override bool ShouldHandleEvent( EventType eventType )
{
return eventType == EventType.ClaimBounty;
}
public override async Task HandleEventAsync(EventType eventType)
{
Manager.Instance.PushEventMessage( this, eventType );
await Task.DelayRealtime( 100 );
Manager.Instance.PlaySfxCenter( "mouse_trap", volume: 0.8f, pitch: Game.Random.Float( 0.95f, 1.05f ) );
await Task.DelayRealtime( 400 );
var camera = Scene.Camera;
var ray = camera.ScreenPixelToRay( new Vector3( Screen.Width / 2, Screen.Height / 2, 0f ) );
var tr = Scene.Trace.Ray( ray, 10000f ).Run();
int maxHP = Level;
Manager.Instance.SpawnMaxHPFloater( maxHP, tr.EndPosition );
Manager.Instance.MaxHP += maxHP;
Manager.Instance.TimeSinceHPChanged = 0f;
await Task.DelayRealtime( 500 );
Manager.Instance.PopEventMessage();
}
}
using Sandbox;
using System.Reflection.PortableExecutable;
using System.Runtime.Versioning;
using System.Threading.Tasks;
public class RelicSacrificialBlade : Relic
{
public override bool ShouldHandleEvent( EventType eventType )
{
return eventType == EventType.HurtCards;
}
public override async Task HandleEventAsync(EventType eventType)
{
Manager.Instance.PushEventMessage( this, eventType );
var camera = Scene.Camera;
var ray = camera.ScreenPixelToRay( new Vector3( Screen.Width / 2, Screen.Height / 2, 0f ) );
var tr = Scene.Trace.Ray( ray, 10000f ).Run();
Manager.Instance.PlaySfx( "sacrificial_blade", tr.EndPosition.WithZ( Scene.Camera.WorldPosition.z - Globals.CARD_SFX_DEPTH_DIFF ), volume: 0.75f, pitch: Game.Random.Float( 1.6f, 1.65f ) );
await Task.DelayRealtime( 450 );
int healAmount = Level;
Manager.Instance.SpawnHealHPFloater( healAmount, tr.EndPosition );
await Task.DelayRealtime( 350 );
await Manager.Instance.GainHP( healAmount );
await Task.DelayRealtime( 250 );
Manager.Instance.PopEventMessage();
}
}
using Sandbox;
using System.Reflection.PortableExecutable;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
public class RelicSign : Relic
{
public override bool ShouldHandleEvent( EventType eventType )
{
return eventType == EventType.Mismatch && Game.Random.Float(0f, 1f) < Level * 0.25f;
}
public override async Task HandleEventAsync( EventType eventType )
{
List<Card> validCards = new();
foreach ( var card in Manager.Instance.Cards )
{
if ( card.NumTimesRevealed > 0 && !Manager.Instance.ChosenCards.Contains(card) && !card.IsRevealed )
validCards.Add( card );
}
if ( validCards.Count == 0 )
return;
Manager.Instance.PushEventMessage( this, eventType );
// todo: sfx
await Task.DelayRealtime( 250 );
validCards.Shuffle();
var cardToReveal = validCards.First();
Manager.Instance.PlayCardSfx( "card_flip", cardToReveal, volume: 0.8f, pitch: Game.Random.Float( 1.15f, 1.2f ) );
await Manager.Instance.RevealCard( cardToReveal );
await Task.DelayRealtime( 750 );
Manager.Instance.HideCard( cardToReveal );
Manager.Instance.PlayCardSfx( "card_flip", cardToReveal, volume: 0.7f, pitch: Game.Random.Float( 0.65f, 0.75f ) );
await Task.DelayRealtime( 300 );
Manager.Instance.PopEventMessage();
}
}
using Sandbox.Diagnostics;
public sealed class PlayerInventory : Component, IPlayerEvent, ILocalPlayerEvent
{
[RequireComponent] public Player Player { get; set; }
public List<BaseWeapon> Weapons => Scene.Components.GetAll<BaseWeapon>( FindMode.EverythingInSelfAndDescendants ).Where( x => x.Network.OwnerId == Network.OwnerId ).OrderBy( x => x.InventorySlot ).ThenBy( x => x.InventoryOrder ).ToList();
public BaseWeapon ActiveWeapon { get; private set; }
public void GiveDefaultWeapons()
{
Pickup( "weapons/hands.prefab" );
Pickup( "weapons/camera.prefab" );
}
void Pickup( string prefabName )
{
var prefab = GameObject.Clone( prefabName, new CloneConfig { Parent = GameObject, StartEnabled = false } );
prefab.NetworkSpawn( false, Network.Owner );
var weapon = prefab.Components.Get<BaseWeapon>( true );
Assert.NotNull( weapon );
IPlayerEvent.PostToGameObject( Player.GameObject, e => e.OnWeaponAdded( weapon ) );
ILocalPlayerEvent.Post( e => e.OnWeaponAdded( weapon ) );
}
protected override void OnUpdate()
{
if ( ActiveWeapon.IsValid() )
{
ActiveWeapon.OnPlayerUpdate( Player );
}
}
public void SwitchWeapon( BaseWeapon weapon )
{
if ( ActiveWeapon.IsValid() )
{
ActiveWeapon.GameObject.Enabled = false;
}
ActiveWeapon = weapon;
if ( ActiveWeapon.IsValid() )
{
ActiveWeapon.GameObject.Enabled = true;
}
}
void IPlayerEvent.OnSpawned()
{
GiveDefaultWeapons();
}
void ILocalPlayerEvent.OnCameraMove( ref Angles angles )
{
if ( ActiveWeapon.IsValid() )
{
ActiveWeapon.OnCameraMove( Player, ref angles );
}
}
void ILocalPlayerEvent.OnCameraPostSetup( Sandbox.CameraComponent camera )
{
if ( ActiveWeapon.IsValid() )
{
ActiveWeapon.OnCameraSetup( Player, camera );
}
}
}
@using Sandbox;
@using Sandbox.UI;
@inherits PanelComponent
<root>
<div class="table">
<div class="header row">
<div class="name">Name</div>
<div class="stat">⏲️</div>
</div>
@foreach ( var entry in Connection.All )
{
string specialClass = "";
if (entry == Connection.Local) specialClass = "me";
<div class="row @specialClass">
<div class="name">
@if ( entry.IsHost )
{
<div>👑</div>
}
@entry.DisplayName
</div>
<div class="stat">@GetTime( entry )</div>
</div>
}
</div>
</root>
@code
{
string GetTime( Connection c )
{
TimeSpan time = DateTime.UtcNow - c.ConnectionTime;
if (time.TotalMinutes < 60)
return time.ToString("mm\\m\\ s\\s");
return time.ToString("hh\\h\\ \\m\\m");
}
protected override void OnUpdate()
{
SetClass( "hidden", !Input.Down( "score" ) );
}
/// <summary>
/// update every second
/// </summary>
protected override int BuildHash() => System.HashCode.Combine( RealTime.Now.CeilToInt() );
}
using Sandbox;
public sealed class SoundEmitter : Component
{
[Property] public string SoundString { get; set; }
protected override void OnStart()
{
base.OnStart();
Sound.Play( SoundString, Transform.Position );
}
protected override void OnUpdate()
{
}
}
using Sandbox;
using Sandbox.Citizen;
public sealed class MapOverrider : Component
{
[Property] GameObject FrogPrefab { get; set; }
protected override void OnEnabled()
{
base.OnEnabled();
/*
{
var obj = Scene.GetAllComponents<SkinnedModelRenderer>();
foreach ( var item in obj )
{
if ( item.Model.ResourceName == "frog_test_subject_01a" )
item.Set( "sit", 1 );
item.Set( "sit_pose", Random.Shared.Float( 3 ) );
item.Set( "scale_height", Random.Shared.Float( 0.75f, 1.5f ) );
var ran = Random.Shared.Int( 0, 1 );
item.MaterialGroup = ran == 1 ? "default" : "Orange";
}
}
*/
{
var npc = Scene.GetAllObjects( true ).Where( x => x.Name == "js_npc_text" ).ToList();
foreach ( var item in npc )
{
item.DestroyImmediate();
Log.Info( "Destroyed" );
}
}
}
protected override void OnUpdate()
{
}
}
@using Sandbox;
@using Sandbox.UI;
@inherits PanelComponent
@implements Component.INetworkListener
<root>
<div class="output">
@foreach (var entry in Entries)
{
<div class="chat_entry">
<div class="author">@entry.author</div>
<div class="message">@entry.message</div>
</div>
}
</div>
<div class="input">
<TextEntry @ref="InputBox" onsubmit="@ChatFinished"></TextEntry>
</div>
</root>
@code
{
TextEntry InputBox;
public record Entry( ulong steamid, string author, string message, RealTimeSince timeSinceAdded );
List<Entry> Entries = new();
protected override void OnUpdate()
{
if (InputBox is null)
return;
Panel.AcceptsFocus = false;
if ( Input.Pressed( "chat" ) )
{
InputBox.Focus();
}
if ( Entries.RemoveAll( x => x.timeSinceAdded > 20.0f ) > 0 )
{
StateHasChanged();
}
SetClass( "open", InputBox.HasFocus );
}
void ChatFinished()
{
var text = InputBox.Text;
InputBox.Text = "";
if (string.IsNullOrWhiteSpace(text))
return;
AddText( text );
}
[Broadcast]
public void AddText( string message )
{
message = message.Truncate( 300 );
if (string.IsNullOrWhiteSpace(message))
return;
var author = Rpc.Caller.DisplayName;
var steamid = Rpc.Caller.SteamId;
Log.Info($"{author}: {message}");
Entries.Add(new Entry(steamid, author, message, 0.0f));
StateHasChanged();
}
[Broadcast] // todo: only from host/owner
public void AddSystemText(string message)
{
message = message.Truncate(300);
if (string.IsNullOrWhiteSpace(message))
return;
Entries.Add(new Entry(0, "ℹ️", message, 0.0f));
StateHasChanged();
}
void Component.INetworkListener.OnConnected( Connection channel )
{
if ( IsProxy ) return;
AddSystemText( $"{channel.DisplayName} has joined the game" );
}
void Component.INetworkListener.OnDisconnected( Connection channel )
{
if ( IsProxy ) return;
AddSystemText( $"{channel.DisplayName} has left the game" );
}
}@using Sandbox;
@using Sandbox.UI;
@inherits PanelComponent
@if (Network.OwnerConnection is Connection owner && owner == Connection.Local)
{
<root class=@(IsOpen ? "open" : "closed")>
<div class="bar">
<label class="heightbar"></label>
<label class="flag"></label>
<div class="heightbarpos" style="height:@(CalculateProgress(Height) * 100)%;">
<label class="heighttext" text="< @(Height)" />
</div>
<div class="maxheightbarpos" style="height:@(CalculateProgress(MaxHeight) * 100)%;">
<label class="maxheighttext" text="@(MaxHeight)>" />
</div>
@foreach (var player in Scene.GetAllComponents<JumperPlayerStuff>())
{
if (owner == player.Network.OwnerConnection) continue;
<div class="maxheightotherbarpos" style="border-top: 4px solid @player.rndColor; height:@(CalculateProgress(player.Height) * 100)%;">
<img class="maxheightothertext" style=" box-shadow: 3px 3px @player.rndColor; background-image: url( avatar:@player.Network.OwnerConnection.SteamId )">
</div>
}
</div>
</root>
}
@code
{
float TotalHeight { get; set; }
float Height { get; set; }
float MaxHeight { get; set; }
bool IsOpen { get; set; }
// Find first player stats class that isn't owned by a proxy (is ours)
JumperPlayerStuff PlayerStats => GameObject.Components.Get<JumperPlayerStuff>(FindMode.InParent);
JumperDistanceRuler Ruler { get; set; }
public Color32 rndColor = Color.Random;
public string hexColor;
protected override void OnUpdate()
{
base.OnUpdate();
if (IsProxy) return;
Height = PlayerStats.Height;
MaxHeight = PlayerStats.MaxHeight;
TotalHeight = Scene.GetAllComponents<JumperDistanceRuler>().FirstOrDefault().Distance;
}
private float CalculateProgress(float height)
{
return height.LerpInverse(0, Scene.GetAllComponents<JumperDistanceRuler>().FirstOrDefault().Distance);
}
protected override int BuildHash()
{
IsOpen ^= Input.Pressed("slot1");
return HashCode.Combine(Time.Delta);
}
}@using Sandbox;
@using Sandbox.UI;
@inherits PanelComponent
<root class=@(Visible ? "visible" : "")>
<label class="message">@OutputText</label>
<label class="name">@NPCName</label>
<label class="background"></label>
</root>
@code {
public string Message { get; set; } = "";
private bool Visible => TimeSinceDisplayed < 4;
private RealTimeSince TimeSinceDisplayed = 999;
public string OutputText { get; set; }
public float Delay { get; set; } = .1f;
public string NPCName { get; set; } = "Ben";
public string Voice { get; set; } = "beep1";
protected override void OnEnabled()
{
base.OnEnabled();
TimeSinceDisplayed = 999;
}
public static bool IsTalking()
{
return true;
}
private async Task RevealTextAsync(string message)
{
Random rand = new Random();
foreach (char c in message)
{
IsTalking();
OutputText += c;
TimeSinceDisplayed = 0f;
await Task.DelaySeconds((float)GetRandomNumber(0.05f, 0.2f));
var snd = Sound.Play(Voice);
snd.Pitch = (float)GetRandomNumber(0.9f, 1.1f);
snd.Volume = 0.25f;
}
}
static Random random = new Random();
public double GetRandomNumber(double minimum, double maximum)
{
return random.NextDouble() * (maximum - minimum) + minimum;
}
public void DisplayMessage(string message)
{
Message = message;
OutputText = null;
}
protected override void OnUpdate()
{
base.OnUpdate();
if (OutputText == Message)
{
return;
}
if (OutputText == null)
{
RevealTextAsync(Message);
}
Message = OutputText;
}
protected override int BuildHash()
{
return HashCode.Combine(OutputText, Visible ? 1 : 0);
}
}using System.Numerics;
using Sandbox;
public sealed class ExplosionKiller : Component
{
/// <summary>
/// When a player leaves this area, kill them
/// </summary>
[Property]
public float KillRange { get; set; } = 1000f;
[Property]
public GameObject Explosion;
[Property]
public SoundEvent ExplosionSound;
TimeSince lastCheck;
protected override void OnEnabled()
{
base.OnEnabled();
lastCheck = 0;
}
protected override void OnFixedUpdate()
{
base.OnFixedUpdate();
if (lastCheck > 1)
{
var players = Scene.GetAll<PlayerController>().ToArray();
Game.Random.Shuffle( players );
foreach (var player in players)
{
if (player.WorldPosition.Distance(Vector3.Zero) > KillRange && player.GetComponent<Health>().IsAlive)
{
var dtag = new TagSet();
dtag.Add( "explosion" );
player.GetComponent<IDamageable>().OnDamage( new DamageInfo()
{
Damage = 1000,
Tags = dtag,
} );
Explosion.Clone(player.WorldPosition);
Sound.Play( ExplosionSound, player.WorldPosition );
lastCheck = 0.2f;
return;
}
}
lastCheck = 0;
}
}
protected override void DrawGizmos()
{
base.DrawGizmos();
Gizmo.Draw.Color = Color.Red;
Gizmo.Draw.LineCircle( Vector3.Zero, Vector3.Up, Vector3.Forward, KillRange, 0, 360, 64 );
}
}
using System;
using static System.Net.Mime.MediaTypeNames;
namespace CryptidHunt;
public partial class DiscoverableArea : Component, Component.ITriggerListener
{
[Property]
public string AreaName { get; set; } = "World";
public bool Activated { get; set; } = false;
public DiscoverableArea() { }
public void OnTriggerEnter( Collider other )
{
if ( !Active || Activated ) return;
if ( !other.GameObject.Parent.Components.TryGet<Player>( out var player, FindMode.EnabledInSelf ) ) return;
Activated = true;
GameUI.OpenZoneHint( AreaName );
}
}
namespace CryptidHunt;
public enum PolewikState
{
Idle,
Patrolling,
Stalking,
Following,
Attacking,
Fleeing,
Pain,
Yell,
AttackPersistent,
Jumpscare
}
public partial class Polewik : Component
{
[Property]
public SkinnedModelRenderer ModelRenderer { get; set; }
[Property]
public GameObject Camera { get; set; }
[Property]
public GameObject SpitPrefab { get; set; }
[Property]
public GameObject BloodParticle { get; set; }
public TimeSince LastDamage { get; set; } = 10f;
public bool Alive { get; set; } = true;
public float JumpscareDistance => 120f;
public float DetectDistance => 1400f;
public float StalkingDistance => 700f;
public float AttackDistance => 500f;
public float GiveUpDistance => 3600f;
public float GiveUpAfter => 25f;
public float AttackAfterStalking => 15f;
public float AttackAfterStalling => 90f;
public float WaitUntilNextAttack => 30f;
[Property]
public SoundEvent HeartbeatSound { get; set; }
public SoundHandle Heartbeart { get; set; }
public Vector3 StuckPosition;
public TimeSince LastStuck;
private bool _firstYell = true;
public Vector3? FirstInterceptPoint()
{
var posA = Player.Instance.WorldPosition;
var velA = Player.Instance.Controller.Velocity.WithZ( 0f );
var posB = WorldPosition;
var speedB = CurrentSpeed;
var toTarget = posA - posB;
var a = Vector3.Dot( velA, velA ) - speedB * speedB;
var b = 2f * Vector3.Dot( velA, toTarget );
var c = Vector3.Dot( toTarget, toTarget );
// Handle near-zero 'a' (velA magnitude ≈ speedB) as linear:
if ( MathF.Abs( a ) < 1e-6f )
{
// bt + c = 0 → t = -c/b
if ( MathF.Abs( b ) < 1e-6f ) return null;
var tLin = -c / b;
if ( tLin <= 0f ) return null;
return posA + velA * tLin;
}
var disc = b * b - 4f * a * c;
if ( disc < 0f ) return null;
var sqrtDisc = MathF.Sqrt( disc );
var t1 = (-b + sqrtDisc) / (2f * a);
var t2 = (-b - sqrtDisc) / (2f * a);
float t;
if ( t1 > 0f && t2 > 0f )
t = MathF.Min( t1, t2 );
else if ( t1 > 0f )
t = t1;
else if ( t2 > 0f )
t = t2;
else
return null;
return posA + velA * t;
}
PolewikState currentState { get; set; } = PolewikState.Patrolling;
public PolewikState CurrentState
{
get => currentState;
set
{
currentState = value;
if ( value == PolewikState.Patrolling )
{
NavigateTo( NearestNode.WorldPosition );
CurrentPathId = PatrolPath.IndexOf( NearestNode );
Heartbeart?.Stop();
}
if ( value == PolewikState.Pain )
{
Heartbeart?.Stop();
Agent.Stop();
Agent.Velocity = 0f;
_lastAttack = 0f;
Sound.Play( "pain", WorldPosition );
Task.RunInThreadAsync( async () =>
{
await Task.MainThread();
ModelRenderer.Set( "growl", true );
await Task.DelaySeconds( 0.1f );
ModelRenderer.Set( "growl", false );
await Task.DelaySeconds( 0.9f );
CurrentState = PolewikState.Fleeing;
} );
}
if ( value == PolewikState.Yell )
{
Heartbeart?.Stop();
GameTask.RunInThreadAsync( async () =>
{
await Task.MainThread();
await Task.DelaySeconds( 0.5f );
Player.Instance.AddCameraShake( 4f, 5f );
await Task.DelaySeconds( 1f );
ModelRenderer.Set( "howl", true );
var howl = Sound.Play( "howl_far", WorldPosition );
howl.Volume *= _firstYell ? 1f : MathX.Remap( Player.Instance.WorldPosition.Distance( WorldPosition ), 500f, 3000f, 0.8f, 0.2f );
_firstYell = false;
await Task.DelaySeconds( 4.5f );
CurrentState = PolewikState.AttackPersistent;
} );
}
if ( value == PolewikState.Fleeing )
{
Heartbeart?.Stop();
_lastAttack = 0f;
TargetPosition = FurthestNode.WorldPosition;
NavigateTo( FurthestNode.WorldPosition );
CurrentPathId = PatrolPath.IndexOf( FurthestNode );
GameTask.RunInThreadAsync( async () =>
{
await Task.MainThread();
await GameTask.DelaySeconds( Game.Random.Float( 3f, 6f ) );
CurrentState = PolewikState.Patrolling;
} );
}
if ( value == PolewikState.Stalking )
{
_startedStalking = 0f;
Heartbeart ??= Sound.Play( HeartbeatSound );
}
if ( value == PolewikState.Following || value == PolewikState.AttackPersistent )
{
Heartbeart ??= Sound.Play( HeartbeatSound );
_startedFollowing = 0f;
}
if ( value == PolewikState.Attacking )
{
_lastAttack = 0f;
Heartbeart ??= Sound.Play( HeartbeatSound );
ModelRenderer.Set( "leap", true );
Sound.Play( "jump", WorldPosition );
GameTask.RunInThreadAsync( async () =>
{
await Task.MainThread();
await GameTask.DelaySeconds( 0.05f );
ModelRenderer.Set( "leap", false );
await GameTask.DelaySeconds( 0.9f );
if ( CurrentState == PolewikState.Attacking )
CurrentState = PolewikState.Following;
} );
}
if ( value == PolewikState.Jumpscare )
{
Heartbeart?.Stop();
_lastAttack = 0f;
Agent.Stop();
Agent.Velocity = 0f;
WorldRotation = Rotation.LookAt( Vector3.Direction( WorldPosition, Player.Instance.WorldPosition ) );
Player.Instance.LockInputs = true;
ModelRenderer.Set( "attack", true );
var mouth = ModelRenderer.GetAttachmentObject( "mouth" );
var spit = SpitPrefab.Clone( mouth.WorldPosition, mouth.WorldRotation );
spit.SetParent( mouth );
Sound.Play( "jumpscare", WorldPosition );
Player.Instance.AddCameraShake( 1.6f, 15f );
GameTask.RunInThreadAsync( async () =>
{
await Task.MainThread();
await GameTask.DelaySeconds( 1.2f );
Player.Instance.ChangeHolding(null, true);
Player.Instance.HP -= 1;
await GameTask.DelaySeconds( 1f );
if ( CurrentState == PolewikState.Jumpscare )
CurrentState = PolewikState.Fleeing;
Player.Instance.LockInputs = false;
// TODO: Make player camera follow the camera attachment
} );
}
}
}
[Property]
public List<GameObject> PatrolPath { get; set; } = new List<GameObject>();
public int CurrentPathId { get; set; } = 0;
public Vector3 TargetPosition { get; set; }
[Property]
public NavMeshAgent Agent { get; set; }
public Dictionary<PolewikState, float> Speeds = new()
{
{ PolewikState.Idle, 0f },
{ PolewikState.Patrolling, 350f },
{ PolewikState.Stalking, 200f },
{ PolewikState.Following, 420f },
{ PolewikState.Attacking, 1800f },
{ PolewikState.Fleeing, 700f },
{ PolewikState.Pain, 0f },
{ PolewikState.Yell, 0f },
{ PolewikState.AttackPersistent, 410f },
{ PolewikState.Jumpscare, 0f }
};
public float CurrentSpeed => Speeds[CurrentState];
private float _hp { get; set; } = 100f;
public float HP
{
get => _hp;
set
{
if ( !Alive ) return;
var damage = _hp - value;
_hp = value;
LastDamage = 0f;
if ( HP <= 0 )
{
RagdollModel();
Sound.Play( "pain", WorldPosition );
Alive = false;
}
else
{
if ( damage >= 10f && CurrentState != PolewikState.Pain && CurrentState != PolewikState.Fleeing )
CurrentState = PolewikState.Pain;
if ( damage > 0f )
BloodParticle.Clone( WorldPosition + Vector3.Up * 30f, WorldRotation );
}
}
}
private BBox _towerZone = BBox.FromPoints( new List<Vector3>() { new Vector3( 2868f, 5616f, 612f ), new Vector3( 2436f, 5932f, 132f ) } ); // too lazy...
TimeSince _startedStalking;
TimeSince _startedFollowing;
TimeSince _lastAttack;
public GameObject ClosestNodeTo( Vector3 pos ) => PatrolPath.OrderBy( x => x.WorldPosition.Distance( pos ) ).FirstOrDefault();
public GameObject NearestNode => ClosestNodeTo( WorldPosition );
public GameObject FurthestNode => PatrolPath.OrderBy( x => x.WorldPosition.Distance( WorldPosition ) ).LastOrDefault();
public bool WithinAttackRange => !_towerZone.Contains( Player.Instance.WorldPosition ) && Player.Instance.WorldPosition.Distance( WorldPosition ) <= AttackDistance && Math.Abs( Player.Instance.WorldPosition.z - WorldPosition.z ) <= 200f;
public bool OutsideDistance => _towerZone.Contains( Player.Instance.WorldPosition ) || Player.Instance.WorldPosition.Distance( WorldPosition ) >= GiveUpDistance || Math.Abs( Player.Instance.WorldPosition.z - WorldPosition.z ) > 200f;
public bool OutsidePersistentDistance => Player.Instance.WorldPosition.Distance( WorldPosition ) >= GiveUpDistance * 4f || Math.Abs( Player.Instance.WorldPosition.z - WorldPosition.z ) > 200f;
protected override void OnStart()
{
if ( ModelRenderer.IsValid() )
ModelRenderer.OnFootstepEvent += OnFootstepEvent;
_startedStalking = 0f;
_startedFollowing = 0f;
_lastAttack = 0f;
}
protected override void OnFixedUpdate()
{
if ( HP <= 0f ) return;
ComputeAnimation();
Agent.MaxSpeed = CurrentSpeed;
/*
DebugOverlay.Sphere( Position, JumpscareDistance, Color.Red, 0f, false );
DebugOverlay.Sphere( Position, DetectDistance, Color.Green );
DebugOverlay.Sphere( Position, StalkingDistance, Color.Yellow );
DebugOverlay.Sphere( Position, AttackDistance, Color.Orange );
DebugOverlay.Sphere( Position, GiveUpDistance, Color.Blue );*/
if ( CurrentState != PolewikState.Idle && CurrentState != PolewikState.Pain && CurrentState != PolewikState.Jumpscare )
{
ComputePath();
if ( StuckPosition.Distance( WorldPosition ) > 100f )
{
StuckPosition = WorldPosition;
LastStuck = 0f;
}
if ( LastStuck >= 5f )
{
LastStuck = 0f;
WorldPosition = TargetPosition;
}
}
if ( CurrentState == PolewikState.Patrolling )
{
NavigateTo( TargetPosition );
if ( _lastAttack >= WaitUntilNextAttack && Player.Instance.WorldPosition.Distance( WorldPosition ) <= DetectDistance )
{
_lastAttack = 0f;
CurrentState = PolewikState.Stalking;
}
if ( _lastAttack >= AttackAfterStalling )
{
_lastAttack = 0f;
CurrentState = PolewikState.Yell;
}
Agent.UpdateRotation = true;
}
if ( CurrentState == PolewikState.Stalking )
{
NavigateTo( ClosestNodeTo( Player.Instance.WorldPosition ).WorldPosition );
Agent.UpdateRotation = false;
WorldRotation = Rotation.LookAt( Vector3.Direction( WorldPosition, Player.Instance.WorldPosition ) );
if ( Player.Instance.WorldPosition.Distance( WorldPosition ) <= StalkingDistance || _startedStalking >= AttackAfterStalking )
{
Sound.Play( "scream_scare", WorldPosition );
CurrentState = PolewikState.Following;
}
if ( OutsideDistance )
CurrentState = PolewikState.Fleeing;
var lookingTrace = Scene.Trace.Sphere( 200f, Player.Instance.Camera.WorldPosition, Player.Instance.Camera.WorldPosition + Player.Instance.Camera.WorldRotation.Forward * 2000f )
.WithTag( "Polewik" )
.IgnoreStatic()
.IgnoreGameObjectHierarchy( Player.Instance.GameObject )
.Run();
if ( lookingTrace.Hit && lookingTrace.GameObject == GameObject )
CurrentState = PolewikState.Following;
}
if ( CurrentState == PolewikState.Following )
{
var intercept = FirstInterceptPoint();
if ( intercept != null )
NavigateTo( intercept.Value );
if ( _startedFollowing >= GiveUpAfter )
CurrentState = PolewikState.Patrolling;
if ( WithinAttackRange )
CurrentState = PolewikState.Attacking;
if ( OutsideDistance )
CurrentState = PolewikState.Fleeing;
}
if ( CurrentState == PolewikState.AttackPersistent )
{
var intercept = FirstInterceptPoint();
if ( intercept != null )
NavigateTo( intercept.Value );
if ( _startedFollowing >= GiveUpAfter * 2f )
CurrentState = PolewikState.Patrolling;
if ( WithinAttackRange )
CurrentState = PolewikState.Attacking;
if ( OutsidePersistentDistance )
CurrentState = PolewikState.Fleeing;
}
if ( CurrentState == PolewikState.Attacking )
{
var intercept = FirstInterceptPoint();
if ( intercept != null )
NavigateTo( intercept.Value );
if ( Player.Instance.WorldPosition.Distance( WorldPosition ) <= 100f )
CurrentState = PolewikState.Jumpscare;
if ( OutsideDistance )
CurrentState = PolewikState.Fleeing;
}
if ( CurrentState == PolewikState.Fleeing && PatrolPath != null )
{
NavigateTo( TargetPosition );
}
}
protected override void OnUpdate()
{
if ( CurrentState == PolewikState.Jumpscare )
{
Player.Instance.CameraPosition = WorldTransform.PointToWorld( new Vector3( 90f, 0f, 70f ) );
Player.Instance.CameraRotation = Rotation.LookAt( Vector3.Direction( Player.Instance.Camera.WorldPosition, Camera.WorldPosition ), Vector3.Up );
}
}
public void OnFootstepEvent( SceneModel.FootstepEvent footstepEvent )
{
var footTrace = Scene.Trace.Ray( WorldPosition, WorldPosition + Vector3.Down * 10f )
.Radius( 2f )
.IgnoreDynamic()
.IgnoreGameObjectHierarchy( GameObject )
.Run();
if ( !footTrace.Hit ) return;
var tag = footTrace.Tags
.Where( x => x != "solid" && x != "world" )
.FirstOrDefault();
var sound = tag switch
{
"metal" => "footstep-metal",
"grass" => "footstep-grass",
"dirt" => "footstep-dirt",
_ => "footstep-concrete"
};
Sound.Play( sound, footTrace.EndPosition ).Volume *= Agent.Velocity.WithZ( 0f ).Length / 7f;
}
public virtual void ComputeAnimation()
{
ModelRenderer.Set( "speed", Agent.Velocity.Length / 3 );
if ( CurrentState == PolewikState.Following ||
CurrentState == PolewikState.Stalking ||
CurrentState == PolewikState.Attacking ||
CurrentState == PolewikState.AttackPersistent ||
CurrentState == PolewikState.Jumpscare )
{
var local = WorldTransform.PointToLocal( Player.Instance.WorldPosition );
ModelRenderer.Set( "lookat", local.WithX( Math.Max( local.x, 0 ) ) + Vector3.Forward * 300f );
}
}
public virtual bool NavigateTo( Vector3 pos )
{
Agent.MoveTo( pos );
return true;
}
public virtual void ComputePath()
{
if ( PatrolPath == null ) return;
TargetPosition = PatrolPath[CurrentPathId].WorldPosition;
if ( WorldPosition.Distance( TargetPosition ) < MathF.Max( Agent.Velocity.Length, 100f ) * 20f * Time.Delta )
CurrentPathId = (CurrentPathId + 1) % PatrolPath.Count;
}
public async void RagdollModel()
{
if ( !ModelRenderer.IsValid() ) return;
var ragdoll = ModelRenderer.GameObject.AddComponent<ModelPhysics>();
ragdoll.Renderer = ModelRenderer;
ragdoll.Model = ModelRenderer.Model;
Agent.Velocity = Vector3.Zero;
Agent.Enabled = false;
await Task.DelayRealtimeSeconds( 5f );
GameUI.BlackScreen();
await Task.DelayRealtimeSeconds( 2.5f );
GameManager.Instance.EndGame();
}
}
@using Sandbox;
@using Sandbox.UI;
@namespace CryptidHunt
<root>
<div class="sliderFull" style="width: @((Fraction*100))%"></div>
</root>
@code
{
[Property]
public ComputerScreen Screen { get; set; }
public float Fraction { get; set; } = 0.5f;
public bool IsDragging { get; set; } = false;
public override void Tick()
{
base.Tick();
if (IsDragging)
Fraction = MathX.Clamp( MousePosition.x / (Box.Right - Box.Left), 0f, 1f );
DarknessSlider.Opacity = Fraction;
}
protected override void OnMouseDown(MousePanelEvent e)
{
base.OnMouseDown(e);
IsDragging = true;
}
protected override void OnMouseUp(MousePanelEvent e)
{
base.OnMouseUp(e);
IsDragging = false;
}
/// <summary>
/// the hash determines if the system should be rebuilt. If it changes, it will be rebuilt
/// </summary>
protected override int BuildHash() => System.HashCode.Combine( Time.Now );
}
using System.IO;
using System.Text;
using Braxnet;
using Clover.Items;
using Clover.Persistence;
using Clover.Player;
using Clover.Ui;
using Clover.Utilities;
namespace Clover.Carriable;
[Category( "Clover/Carriable" )]
public class Paintbrush : BaseCarriable
{
[Property] public SoundEvent PaintSound { get; set; }
[Property] public SoundEvent TextureChangeSound { get; set; }
public string CurrentTextureName { get; set; }
public string CurrentTexturePath => $"decals/{CurrentTextureName}.decal";
public override void OnUseDown()
{
var itemColliders = Player.PlayerInteract.InteractCollider.Touching;
foreach ( var itemCollider in itemColliders )
{
if ( itemCollider.GameObject.Components.TryGet<PictureFrame>( out var pictureFrame ) )
{
if ( string.IsNullOrWhiteSpace( CurrentTexturePath ) )
{
Player.Notify( Notifications.NotificationType.Error, "No texture selected" );
return;
}
pictureFrame.TexturePath = CurrentTexturePath;
SoundEx.Play( PaintSound, Player.WorldPosition );
ParticleManager.PoofAt( pictureFrame.WorldPosition );
return;
}
if ( itemCollider.GameObject.Components.TryGet<Pumpkin>( out var pumpkin ) )
{
if ( string.IsNullOrWhiteSpace( CurrentTexturePath ) )
{
Player.Notify( Notifications.NotificationType.Error, "No texture selected" );
return;
}
pumpkin.TexturePath = CurrentTexturePath;
SoundEx.Play( PaintSound, Player.WorldPosition );
ParticleManager.PoofAt( pumpkin.WorldPosition );
return;
}
if ( itemCollider.GameObject.Components.TryGet<SnowmanPiece>( out var snowmanPiece ) )
{
if ( string.IsNullOrWhiteSpace( CurrentTexturePath ) )
{
Player.Notify( Notifications.NotificationType.Error, "No texture selected" );
return;
}
snowmanPiece.TexturePath = CurrentTexturePath;
SoundEx.Play( PaintSound, Player.WorldPosition );
ParticleManager.PoofAt( snowmanPiece.WorldPosition );
return;
}
}
var pos = Player.GetAimingGridPosition();
if ( pos == Vector2Int.Zero )
{
Log.Error( "Invalid position" );
return;
}
var item = Player.World.GetItem<FloorDecal>( pos, 16f );
if ( item != null )
{
if ( string.IsNullOrWhiteSpace( CurrentTexturePath ) || CurrentTexturePath == item.TexturePath )
{
Log.Info( $"Removing decal {item.TexturePath}" );
item.WorldItem.RemoveFromWorld();
}
else
{
Log.Info( $"Updating decal {item.TexturePath} -> {CurrentTexturePath}" );
item.TexturePath = CurrentTexturePath;
item.UpdateDecal();
}
SoundEx.Play( PaintSound, Player.WorldPosition );
ParticleManager.PoofAt( item.WorldPosition );
}
else
{
if ( string.IsNullOrWhiteSpace( CurrentTexturePath ) )
{
Player.Notify( Notifications.NotificationType.Error, "No texture selected" );
return;
}
var playerRotation = World.GetItemRotationFromDirection(
World.Get4Direction( Player.PlayerController.Yaw ) );
Log.Info( $"Spawning decal at {pos} with texture {CurrentTexturePath}" );
/*var newItem = PersistentItem.Create<Persistence.FloorDecal>( Data.ItemData.GetById( "floor_decal" ) );
if ( newItem == null ) throw new System.Exception( "Failed to create floor decal" );
newItem.TexturePath = CurrentTexturePath;
var node = World.SpawnPersistentNode( newItem, pos, playerRotation, World.ItemPlacement.FloorDecal, false );*/
var newPItem = PersistentItem.Create( Data.ItemData.Get( "floor_decal" ) );
newPItem.SetSaveData( "TexturePath", CurrentTexturePath );
WorldItem worldItem;
try
{
worldItem = Player.World.SpawnPlacedItem( newPItem, pos, playerRotation );
}
catch ( System.Exception e )
{
Player.Notify( Notifications.NotificationType.Error, $"Failed to spawn decal: {e.Message}" );
return;
}
SoundEx.Play( PaintSound, Player.WorldPosition );
ParticleManager.PoofAt( worldItem.WorldPosition );
// fade in the decal
/* if ( node is Items.FloorDecal decal2 )
{
decal2.Decal.Modulate = new Godot.Color( 1, 1, 1, 0f );
var tween = GetTree().CreateTween();
tween.TweenProperty( decal2.Decal, "modulate:a", 1f, 0.1f );
} */
}
}
public override string GetUseName()
{
return "Paint";
}
public override IEnumerable<MainUi.InputData> GetInputs()
{
yield return new MainUi.InputData( "WheelUp", "Next texture" );
yield return new MainUi.InputData( "WheelDown", "Previous texture" );
}
protected override void OnStart()
{
CurrentTextureName = Decals.GetAllDecals().FirstOrDefault();
}
protected override void OnFixedUpdate()
{
if ( IsProxy ) return;
if ( Input.MouseWheel.y != 0 )
{
var decals = Decals.GetAllDecals();
if ( !decals.Any() )
{
Player.Notify( Notifications.NotificationType.Error, "No decals found" );
return;
}
var index = decals.IndexOf( CurrentTextureName );
index += Input.MouseWheel.y > 0 ? 1 : -1;
if ( index < 0 ) index = decals.Count - 1;
if ( index >= decals.Count ) index = 0;
CurrentTextureName = decals[index];
if ( string.IsNullOrEmpty( CurrentTextureName ) )
{
Log.Warning( "No texture selected" );
return;
}
Sound.Play( TextureChangeSound );
Log.Info( $"Selected texture: {CurrentTextureName}" );
// Player.Notify( Notifications.NotificationType.Info, $"Selected texture: {CurrentTexture}" );
}
}
}
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Clover.Player;
using Clover.Ui;
namespace Clover;
public class GameManager : Component, Component.INetworkListener, ISceneStartup
{
public static GameManager Instance;
[Property] public GameObject PlayerPrefab { get; set; }
protected override void OnAwake()
{
base.OnAwake();
Instance = this;
}
protected override void OnDestroy()
{
base.OnDestroy();
Instance = null;
}
protected override void OnStart()
{
_ = Bootstrap();
}
private async Task Bootstrap()
{
if ( IsProxy ) return;
Log.Info( "GameManager is booting up" );
await WorldManager.Instance.LoadWorld( WorldManager.Instance.DefaultWorldData );
/*if ( !_spawnQueue.Contains( Connection.Local ) )
{
OnConnected( Connection.Local );
}*/
Log.Info( "GameManager has booted up" );
}
public static JsonSerializerOptions JsonOptions = new()
{
WriteIndented = true,
IncludeFields = true,
Converters = { new JsonStringEnumConverter() },
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
private TimeSince _lastSave;
public void SaveTimer()
{
if ( _lastSave < 60f )
{
return;
}
_lastSave = 0f;
if ( !AutoSave ) return;
if ( Networking.IsHost )
{
foreach ( var world in WorldManager.Instance.Worlds )
{
world.Value.Save();
}
}
PlayerCharacter.Local?.Save();
}
[ConVar( "clover_autosave" )] public static bool AutoSave { get; set; } = true;
protected override void OnFixedUpdate()
{
base.OnFixedUpdate();
SaveTimer();
SpawnPlayers();
}
private void SpawnPlayers()
{
if ( IsProxy ) return;
foreach ( var channel in _spawnQueue.ToList() )
{
SpawnPlayer( channel );
}
}
private readonly List<Connection> _spawnQueue = new();
public void OnConnected( Connection channel )
{
Log.Info( $"Player '{channel.DisplayName}' has joined the game" );
// _spawnQueue.Add( channel );
}
public void OnDisconnected( Connection channel )
{
Log.Info( $"Player '{channel.DisplayName}' has left the game" );
}
public void OnBecameHost( Connection channel )
{
Log.Info( $"Player '{channel.DisplayName}' has become the host" );
}
public static void LoadRealm()
{
Game.ActiveScene.LoadFromFile( "scenes/clover.scene" );
}
public void SpawnPlayer( Connection channel )
{
if ( Scene.GetAllComponents<PlayerCharacter>().Any( x => x.Network.Owner == channel ) )
{
Log.Warning( $"Player '{channel.DisplayName}' already spawned" );
_spawnQueue.Remove( channel );
return;
}
if ( !PlayerPrefab.IsValid() )
return;
// Spawn this object and make the client the owner
var player = PlayerPrefab.Clone( new Transform(), name: $"Player - {channel.DisplayName}" );
player.NetworkSpawn( channel );
// Notify any listeners that a player has spawned
Scene.RunEvent<IPlayerSpawned>( x => x.OnPlayerSpawned( player.GetComponent<PlayerCharacter>() ) );
var island = WorldManager.Island;
if ( island.IsValid() )
{
var spawnPoint = island.GetEntrance( "spawn" );
if ( spawnPoint.IsValid() )
{
player.GetComponent<PlayerCharacter>().SetLayer( island.Layer );
player.GetComponent<PlayerCharacter>().TeleportTo( spawnPoint.EntranceId );
}
else
{
Log.Error( "No spawn point found in the world" );
}
}
else
{
Log.Error( "No active world found" );
}
_spawnQueue.Remove( channel );
}
[Rpc.Owner]
public void RequestSpawn( string playerId )
{
var caller = Rpc.Caller;
Log.Info( $"Player '{caller.DisplayName}' has requested to spawn player '{playerId}'" );
_spawnQueue.Add( caller );
}
public void OnHostPreInitialize( SceneFile scene )
{
Log.Info( "BOOT" );
PlayerCharacter.SpawnPlayerId = null;
}
public void OnHostInitialize()
{
Log.Info( "BOOT" );
PlayerCharacter.SpawnPlayerId = null;
}
public void OnClientInitialize()
{
Log.Info( "BOOT" );
PlayerCharacter.SpawnPlayerId = null;
}
}
using Clover.Player;
namespace Clover.Interactable;
public interface IInteract
{
bool CanInteract( PlayerCharacter player ) { return true; }
/// <summary>
/// Called when the player presses down the interact button. Only called once.
/// </summary>
/// <param name="player"></param>
void StartInteract( PlayerCharacter player );
/// <summary>
/// Called when the player releases the interact button. Only called once.
/// </summary>
/// <param name="player"></param>
void FinishInteract( PlayerCharacter player ) { }
string GetInteractName();
void StartInteractHost( PlayerCharacter player ) { }
void FinishInteractHost( PlayerCharacter player ) { }
}
using Clover.Persistence;
using Clover.Ui;
using Clover.Utilities;
using Sandbox.Diagnostics;
using Sandbox.Utility;
namespace Clover.Items;
public class DecalItem : Component, IPersistent, IPaintEvent
{
private Decals.DecalData _decalData;
protected Texture DecalTexture;
private string _texturePath;
[Sync]
public string TexturePath
{
get => _texturePath;
set
{
_texturePath = value;
UpdateDecal();
}
}
[Sync] public string DecalHash { get; set; }
public void UpdateDecal()
{
if ( Scene.IsEditor ) return;
if ( string.IsNullOrEmpty( TexturePath ) ) return;
if ( IsProxy )
{
FileSystem.Data.CreateDirectory( "decalcache" );
if ( FileSystem.Data.FileExists( $"decalcache/{DecalHash}.decal" ) )
{
_decalData = Decals.ReadDecal( $"decalcache/{DecalHash}.decal" );
DecalTexture = Decals.GetDecalTexture( _decalData.ToRpc() );
var material1 = Material.Create( $"{DecalHash}.vmat", "shaders/floor_decal.shader" );
material1.Set( "Color", DecalTexture );
// ModelRenderer.MaterialOverride = material1;
OnMaterialUpdate( material1 );
Log.Info( $"Updated cached decal '{_decalData.Name}' with texture: {TexturePath}" );
return;
}
Log.Info( $"Decal '{TexturePath}' not found in cache, requesting..." );
RequestDecal();
return;
}
// Update decal
var material = Material.Create( $"{TexturePath}.vmat", "shaders/floor_decal.shader" );
try
{
_decalData = Decals.ReadDecal( TexturePath );
}
catch ( System.Exception e )
{
Log.Error( e.Message );
return;
}
DecalTexture = _decalData.Texture;
material.Set( "Color", _decalData.Texture );
// ModelRenderer.MaterialOverride = material;
OnMaterialUpdate( material );
DecalHash = _decalData.GetHash();
Log.Info( $"Updated decal '{_decalData.Name}' with texture: {TexturePath}" );
}
[Rpc.Owner]
private void RequestDecal()
{
Assert.True( Networking.IsHost );
var caller = Rpc.Caller;
if ( string.IsNullOrEmpty( _texturePath ) )
{
Log.Warning( "Texture path is null or empty" );
return;
}
if ( string.IsNullOrEmpty( _decalData.Name ) )
{
Log.Warning( "Decal name is null or empty" );
return;
}
var rpcDecal = _decalData.ToRpc();
Log.Info( $"Sending decal '{rpcDecal.Name}' by '{rpcDecal.Author}' to {caller}" );
using ( Rpc.FilterInclude( caller ) )
{
RecieveDecal( _texturePath, rpcDecal );
}
}
[Rpc.Broadcast]
public void RecieveDecal( string filename, Decals.DecalDataRpc decal )
{
Log.Info( $"Recieved decal '{filename}':" );
Log.Info( $"Size: {decal.Width}x{decal.Height}" );
Log.Info( $"Name: {decal.Name}" );
Log.Info( $"Author: {decal.Author}" );
Log.Info( $"Palette: {decal.Palette}" );
Log.Info( $"Image: {decal.Image?.Length} bytes" );
if ( string.IsNullOrEmpty( decal.Name ) )
{
Log.Error( "Decal name is null or empty" );
return;
}
_decalData = decal.ToDecalData();
// _decalData = Decals.ToDecalData( decal );
var hash = _decalData.GetHash();
DecalTexture = Decals.GetDecalTexture( decal );
var material = Material.Create( $"{hash}.vmat", "shaders/floor_decal.shader" );
material.Set( "Color", DecalTexture );
// ModelRenderer.MaterialOverride = material;
OnMaterialUpdate( material );
FileSystem.Data.CreateDirectory( "decalcache" );
var file = FileSystem.Data.OpenWrite( $"decalcache/{hash}.decal" );
Decals.WriteDecal( file, decal.ToDecalData() );
file.Close();
}
public virtual void OnMaterialUpdate( Material material )
{
}
public virtual void OnSave( PersistentItem item )
{
item.SetSaveData( "TexturePath", TexturePath );
}
public virtual void OnLoad( PersistentItem item )
{
TexturePath = item.GetSaveData<string>( "TexturePath" );
// UpdateDecal();
}
void IPaintEvent.OnFileSaved( string path )
{
if ( TexturePath == path )
{
Log.Info( "Updating decal" );
UpdateDecal();
RecieveDecal( path, _decalData.ToRpc() );
}
}
[ConCmd( "clover_delete_old_decals" )]
public static void DeleteOldDecals()
{
var decals = Game.ActiveScene.GetAllComponents<DecalItem>();
foreach ( var decal in decals )
{
if ( decal.DecalHash == null )
{
Log.Info( $"Deleting old decal: {decal.TexturePath}" );
// decal.Delete();
}
}
}
}
using Clover.Persistence;
namespace Clover.Items;
public class PictureFrame : DecalItem, IPersistent
{
[RequireComponent] public WorldItem WorldItem { get; private set; }
[Property] public ModelRenderer ModelRenderer { get; set; }
public override void OnMaterialUpdate( Material material )
{
base.OnMaterialUpdate( material );
ModelRenderer.SetMaterialOverride( material, "image" );
}
}
using Clover.Carriable;
using Clover.Components;
using Clover.Data;
using Clover.Interactable;
using Clover.Inventory;
using Clover.Items;
using Clover.Npc;
using Clover.Ui;
namespace Clover.Player;
[Title( "Player Interact" )]
[Icon( "inventory" )]
[Category( "Clover/Player" )]
public class PlayerInteract : Component
{
[RequireComponent] public PlayerCharacter Player { get; set; }
private IInteract _currentInteractable;
[Property] public BoxCollider InteractCollider { get; set; }
[Property] public GameObject Cursor { get; set; }
[Property] public SoundEvent UseFailSound { get; set; }
[Property] public SoundEvent PickUpFailSound { get; set; }
public GameObject InteractionTarget { get; set; }
protected override void OnAwake()
{
if ( IsProxy ) return;
if ( Cursor != null )
{
Cursor.Parent = null;
}
}
protected override void OnDestroy()
{
base.OnDestroy();
if ( Cursor.IsValid() )
{
Cursor.Destroy();
}
}
public bool CanInteract()
{
if ( Player.ItemPlacer.IsPlacing || Player.ItemPlacer.IsMoving ) return false;
if ( Player.InCutscene ) return false;
if ( Player.VehicleRider.Vehicle.IsValid() ) return false;
if ( InteractionTarget.IsValid() )
{
if ( InteractionTarget.GetComponent<BaseNpc>().IsValid() ) return false;
}
if ( Player.Equips.TryGetEquippedItem<BaseCarriable>( Equips.EquipSlot.Tool, out var tool ) &&
tool.ShouldDisableMovement() ) return false;
return true;
}
public bool CanPickUp()
{
if ( Player.Equips.TryGetEquippedItem<BaseCarriable>( Equips.EquipSlot.Tool, out var tool ) &&
tool.ShouldDisableMovement() ) return false;
return true;
}
public bool HasInteractable()
{
return FindInteractable() != null;
}
protected override void OnFixedUpdate()
{
if ( IsProxy ) return;
if ( !CanInteract() )
{
return;
}
var interactable = FindInteractable();
var moveable = FindMoveable();
var pickupableNode = GetPickupableNode();
if ( Input.Pressed( "use" ) )
{
if ( interactable != null )
{
_currentInteractable = interactable;
_currentInteractable.StartInteract( Player );
if ( !Networking.IsHost )
{
using ( Rpc.FilterInclude( Connection.Host ) )
{
_currentInteractable.StartInteractHost( Player );
}
}
Input.Clear( "use" );
}
else
{
Log.Warning( "No interactable found" );
// Notifications.Instance.AddNotification( Notifications.NotificationType.Warning, "No interactable found" );
Sound.Play( UseFailSound, WorldPosition );
}
return;
}
else if ( Input.Released( "use" ) )
{
if ( _currentInteractable != null )
{
_currentInteractable.FinishInteract( Player );
if ( !Networking.IsHost )
{
using ( Rpc.FilterInclude( Connection.Host ) )
{
_currentInteractable.FinishInteractHost( Player );
}
}
_currentInteractable = null;
Input.Clear( "use" );
}
return;
}
if ( Input.Pressed( "pickup" ) && CanPickUp() )
{
if ( pickupableNode != null )
{
if ( pickupableNode.CanPickup( Player ) )
{
pickupableNode.OnPickup( Player );
return;
}
}
Log.Warning( "No pickupable node found" );
Sound.Play( PickUpFailSound, WorldPosition );
return;
}
/*if ( Input.Pressed( "move" ) )
{
if ( !Player.World.Data.DisableItemPlacement )
{
if ( moveable.IsValid() )
{
Log.Info( "Moving..." );
Mouse.Visible = true;
Player.ItemPlacer.StartMovingPlacedItem( moveable.GetComponent<WorldItem>() );
Input.Clear( "move" );
}
else
{
Mouse.Visible = false;
Log.Warning( "No interactable found" );
// Notifications.Instance.AddNotification( Notifications.NotificationType.Warning, "No interactable found" );
Sound.Play( UseFailSound, WorldPosition );
}
}
return;
}*/
GameObject target = null;
if ( interactable is Component interactableComponent )
{
target = interactableComponent.GameObject;
}
else if ( moveable.IsValid() )
{
target = moveable;
}
else if ( pickupableNode is Component pickupableNodeComponent && pickupableNode.CanPickup( Player ) )
{
target = pickupableNodeComponent.GameObject;
}
if ( target != null )
{
if ( target.Components.TryGet<WorldItem>( out var worldItem ) )
{
worldItem.ItemHighlight.Enabled = true;
}
}
if ( Cursor.IsValid() && Cursor.Enabled )
{
var gridPosition = Player.GetAimingGridPosition();
var worldPosition = WorldManager.Instance.ActiveWorld.ItemGridToWorld( gridPosition );
Cursor.WorldPosition = worldPosition;
}
}
public IPickupable GetPickupableNode()
{
var touchingItems = InteractCollider.Touching;
foreach ( var collider in touchingItems )
{
if ( collider.GameObject.Components.TryGet<IPickupable>( out var pickupable ) )
{
return pickupable;
}
}
return null;
}
/*public WorldItem GetWorldItemFromInteract()
{
foreach ( var collider in InteractCollider.Touching )
{
var checkGameObject = collider.GameObject;
while ( checkGameObject != null )
{
if ( checkGameObject.Components.TryGet<IInteract>( out var interactable ) )
{
if ( checkGameObject.Components.TryGet<WorldItem>( out var worldItem ) )
{
return worldItem;
}
}
checkGameObject = checkGameObject.Parent;
}
}
return null;
}*/
public IInteract FindInteractable()
{
foreach ( var collider in InteractCollider.Touching )
{
/*var checkGameObject = collider.GameObject;
while ( checkGameObject != null )
{
if ( checkGameObject.Components.TryGet<IInteract>( out var interactable ) )
{
return interactable;
}
checkGameObject = checkGameObject.Parent;
}*/
/*if ( collider.GameObject.Components.TryGet<IInteract>( out var interactable,
FindMode.EverythingInSelfAndAncestors ) )
{
return interactable;
}*/
var components = collider.GameObject.Components.GetAll<IInteract>( FindMode.EverythingInSelfAndAncestors );
foreach ( var component in components )
{
if ( component.CanInteract( Player ) )
{
return component;
}
}
}
// Log.Info( "# Reached root, no interactable found." );
return null;
}
public GameObject FindMoveable()
{
foreach ( var collider in InteractCollider.Touching )
{
var worldItem = collider.GameObject.Components.Get<WorldItem>( FindMode.EverythingInSelfAndAncestors );
if ( worldItem != null && worldItem.CanPickup( Player ) )
{
return collider.GameObject;
}
}
return null;
}
}
using System;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Clover.Data;
using Sandbox.Diagnostics;
namespace Clover;
[Category( "Clover/World" )]
public class WorldManager : Component, Component.INetworkSpawn
{
public static WorldManager Instance { get; private set; }
public static World Island => Instance.GetWorld( "island" );
// [Property] public List<World> Worlds { get; set; } = new();
[Property, Sync, Change, ReadOnly] public NetDictionary<int, World> Worlds { get; set; } = new();
[Property, JsonIgnore, ReadOnly] public int ActiveWorldIndex { get; set; }
[Property, JsonIgnore, ReadOnly] public World ActiveWorld => !Scene.IsEditor ? GetWorld( ActiveWorldIndex ) : null;
[Property] public WorldData DefaultWorldData { get; set; }
public delegate void WorldUnloadEventHandler( World world );
public delegate void WorldLoadedEventHandler( World world );
public delegate void ActiveWorldChangedEventHandler( World world );
[Property] public WorldLoadedEventHandler WorldLoaded { get; set; }
[Property] public WorldUnloadEventHandler WorldUnload { get; set; }
[Property] public ActiveWorldChangedEventHandler ActiveWorldChanged { get; set; }
public string CurrentWorldDataPath { get; set; }
public bool IsLoading;
public const float WorldOffset = 1024;
// public Array LoadingProgress { get; set; } = new Array();
protected override void OnAwake()
{
base.OnAwake();
Instance = this;
}
public void OnNetworkSpawn( Connection owner )
{
Instance = this;
}
public void OnWorldsChanged()
{
Log.Info( "Worlds changed." );
RebuildVisibility();
}
protected override void OnDestroy()
{
base.OnDestroy();
Instance = null;
}
public World GetWorld( string id )
{
var val = Worlds.Values.FirstOrDefault( w => w.Data.ResourceName == id );
if ( !val.IsValid() )
{
Log.Warning( $"World not found: {id}, searching scene..." );
val = Scene.GetAllComponents<World>().FirstOrDefault( w => w.Data.ResourceName == id );
}
return val;
}
public World GetWorld( int index )
{
if ( index < 0 )
{
return null;
}
var val = Worlds.Values.FirstOrDefault( w => w.Layer == index );
if ( !val.IsValid() )
{
Log.Warning( $"World not found at index: {index}, searching scene..." );
val = Scene.GetAllComponents<World>().FirstOrDefault( w => w.Layer == index );
}
return val;
}
public void SetActiveWorld( int index )
{
Log.Info( $"Setting active world to index: {index}" );
ActiveWorldIndex = index;
if ( !ActiveWorld.IsValid() )
{
Log.Warning( $"Active world is not valid: {index}" );
}
RebuildVisibility();
ActiveWorldChanged?.Invoke( ActiveWorld );
Scene.RunEvent<IWorldEvent>( x => x.OnWorldChanged( ActiveWorld ) );
}
private void RebuildVisibility()
{
if ( Worlds.Count == 0 )
{
Log.Warning( "No worlds to rebuild visibility for." );
return;
}
Log.Info( $"Rebuilding world visibility for {Worlds.Count} worlds..." );
// rebuild world visibility
for ( var i = 0; i < Worlds.Count; i++ )
{
var isVisible = i == ActiveWorldIndex;
var world = Worlds.TryGetValue( i, out var w ) ? w : null;
if ( world == null )
{
Log.Warning( $"World not found at index: {i}" );
continue;
}
world.Tags.Remove( "worldlayer_invisible" );
world.Tags.Remove( "worldlayer_visible" );
if ( isVisible )
{
world.Tags.Add( "worldlayer_visible" );
}
else
{
world.Tags.Add( "worldlayer_invisible" );
}
}
// rebuild object visibility
foreach ( var layerObject in Scene.GetAllComponents<WorldLayerObject>() )
{
layerObject.RebuildVisibility( layerObject.Layer );
}
}
public void SetActiveWorld( World world )
{
ActiveWorldIndex = world.Layer;
RebuildVisibility();
}
protected override void OnStart()
{
Instance = this;
}
public bool HasWorld( string id )
{
return Worlds.Values.Any( w => w.Data.ResourceName == id );
}
public bool HasWorld( WorldData data )
{
return Worlds.Values.Any( w => w.Data == data );
}
public async Task<World> LoadWorld( WorldData data )
{
Log.Info( $"Loading world: {data.ResourceName}" );
// use the first available index
var index = 0;
while ( Worlds.ContainsKey( index ) )
{
index++;
}
if ( !data.Prefab.IsValid() )
{
Log.Error( $"Invalid prefab for world: {data.ResourceName}" );
return null;
}
var gameObject = data.Prefab.Clone();
// gameObject.BreakFromPrefab();
var world = gameObject.GetComponent<World>();
world.Data = data; // already set
world.Layer = index;
gameObject.WorldPosition = new Vector3( new Vector3( 0, 0, index * WorldOffset ) );
gameObject.Transform.ClearInterpolation();
gameObject.SetParent( GameObject );
gameObject.Tags.Add( "dworld" );
gameObject.Tags.Add( $"dworldlayer_{index}" );
gameObject.NetworkMode = NetworkMode.Object;
gameObject.NetworkSpawn();
Worlds[index] = world;
world.Setup();
await world.Load();
Log.Info( $"Loaded world: {data.ResourceName}, now has {Worlds.Count} worlds." );
RebuildVisibility();
// ActiveWorldChanged?.Invoke( world );
OnWorldLoadedRpc( data.ResourceName );
// return dummy task to shut up the compiler
await Task.Frame();
return world;
}
public async Task<World> GetWorldOrLoad( WorldData data )
{
Assert.NotNull( data, "World data is null." );
var world = GetWorld( data.ResourceName );
return world.IsValid() ? world : await LoadWorld( data );
}
[Rpc.Owner]
public void RequestLoadWorld( string id )
{
var worldData = ResourceLibrary.GetAll<WorldData>().FirstOrDefault( w => w.ResourceName == id );
if ( worldData != null )
{
_ = LoadWorld( worldData );
}
else
{
Log.Warning( $"Could not find world with id: {id}" );
}
}
[Rpc.Broadcast( NetFlags.HostOnly )]
public void OnWorldLoadedRpc( string id )
{
Log.Info( $"World loaded: {id}" );
var world = GetWorld( id );
if ( !world.IsValid() )
{
Log.Error( $"World not found: {id}" );
return;
}
WorldLoaded?.Invoke( world );
Scene.RunEvent<IWorldEvent>( x => x.OnWorldLoaded( world ) );
world.OnWorldLoaded();
foreach ( var world2 in Worlds )
{
Log.Info( $"World #{world2.Key}: {world2.Value.Data.ResourceName}" );
}
}
public void UnloadWorld( string id )
{
var world = GetWorld( id );
if ( world.IsValid() )
{
UnloadWorld( world );
}
}
public void UnloadWorld( World world )
{
Log.Info( $"Unloading world: {world.Data.ResourceName}" );
world.OnWorldUnloaded();
world.DestroyGameObject();
Worlds.Remove( world.Layer );
RebuildVisibility();
WorldUnload?.Invoke( world );
Scene.RunEvent<IWorldEvent>( x => x.OnWorldUnloaded( world ) );
}
public void UnloadWorld( int index )
{
var world = GetWorld( index );
if ( world.IsValid() )
{
UnloadWorld( world );
}
}
[ConCmd( "clover_world_load" )]
public static void LoadWorldCmd( string id )
{
var worldManager = NodeManager.WorldManager;
var worldData = ResourceLibrary.GetAll<WorldData>().FirstOrDefault( w => w.ResourceName == id );
if ( worldData != null )
{
_ = worldManager.LoadWorld( worldData );
}
else
{
Log.Warning( $"Could not find world with id: {id}" );
}
}
[ConCmd( "clover_world_set_active" )]
public static void SetActiveWorldCmd( int index )
{
NodeManager.WorldManager.SetActiveWorld( index );
}
[ConCmd( "clover_world_move_to_entrance" )]
public static void MoveToEntranceCmd( int worldIndex, string entranceId )
{
var world = Instance.GetWorld( worldIndex );
if ( !world.IsValid() ) throw new Exception( $"Invalid world index: {worldIndex}" );
var entrance = world.GetEntrance( entranceId );
if ( entrance == null ) throw new Exception( $"Invalid entrance id: {entranceId}" );
Instance.SetActiveWorld( worldIndex );
var player = NodeManager.Player;
player.WorldLayerObject.SetLayer( worldIndex, true );
player.WorldPosition = entrance.WorldPosition;
player.GetComponent<CameraController>().SnapCamera();
}
[ConCmd( "clover_world_save_all" )]
public static void SaveAllCmd()
{
foreach ( var world in Instance.Worlds.Values )
{
world.Save();
}
}
/*public WorldNodeLink GetWorldNodeLink( GameObject gameObject )
{
foreach ( var world in Worlds.Values )
{
var link = world.GetNodeLink( gameObject );
if ( link != null )
{
return link;
}
}
return null;
}*/
}
public interface IWorldEvent
{
void OnWorldLoaded( World world ) { }
void OnWorldUnloaded( World world ) { }
void OnWorldChanged( World world ) { }
}
@using System
@using Clover.Carriable
@using Clover.Components
@using Clover.Player
@using Sandbox.UI;
@inherits Panel
@namespace Clover.Ui
@attribute [StyleSheet]
<root>
@if ( PlayAlongMaster.IsValid() && !string.IsNullOrEmpty( PlayAlongMaster.PlaybackTitle ) )
{
<div class="playalong">
<h1>@( $"Play along with {PlayAlongMaster.Player.Network.Owner.DisplayName}" )</h1>
<section>
<h2>Song</h2>
<main>
@PlayAlongMaster.PlaybackTitle
</main>
</section>
<section>
<h2>Tracks</h2>
<main class="tracks">
@for ( int i = 0; i < PlayAlongMaster.PlaybackTracksInstruments.Count; i++ )
{
var i1 = i;
<button class="track-button @( PlayAlongMaster.IsPlaybackTrackEnabled( i ) ? "enabled" : "disabled" ) @( PlayAlongMaster.PlaybackTracksInstruments[i1] == Instrument ? "me" : "other" ) button-sounds" @onclick=@( () => PlayAlongMaster.RequestTrackPlayback( Instrument, i1 ) )>
<div class="index">@i1</div>
<div class="player">
@if ( PlayAlongMaster.PlaybackTracksInstruments[i].IsValid() )
{
<img src="@( $"avatar://{PlayAlongMaster.PlaybackTracksInstruments[i].Network.Owner.SteamId}" )"/>
}
</div>
</button>
}
</main>
</section>
</div>
}
<div class="playback">
<h1>
@( $"Loaded: {Instrument.PlaybackTitle}" )
</h1>
@if ( !Instrument.IsPlayingBack )
{
<div class="midis">
@foreach ( var midi in GetMidiFiles() )
{
<button class="midi button-sounds @( Instrument.PlaybackTitle == midi ? "active" : "" )" @onclick=@( () => Instrument.LoadFile( midi ) )>
@midi
</button>
}
</div>
}
<div class="actions">
<button class="clover-button button-sounds @( string.IsNullOrEmpty( Instrument.PlaybackTitle ) ? "disabled" : "" )" @onclick=@( () => Instrument.StartPlayback() )>Play</button>
<button class="clover-button button-sounds" @onclick=@( () => Instrument.StopPlayback() )>Stop</button>
@*<TextEntry Numeric=@( true ) Value:[email protected]/>*@
<section class="transpose">
<h2 class="title">Transpose</h2>
<main>
<button class="clover-button small button-sounds" @onclick=@( () => Instrument.TransposePlayback -= 12 )>-1</button>
<div class="value">@( Instrument.TransposePlayback / 12 )</div>
<button class="clover-button small button-sounds" @onclick=@( () => Instrument.TransposePlayback += 12 )>+1</button>
</main>
</section>
<section>
<h2 class="title">Tracks</h2>
<main class="tracks">
@for ( int i = 0; i < Instrument.PlaybackTracksInstruments.Count; i++ )
{
var i1 = i;
<button class="track-button @( Instrument.IsPlaybackTrackEnabled( i ) ? "enabled" : "disabled" ) @( Instrument.PlaybackTracksInstruments[i1] == Instrument ? "me" : "other" ) button-sounds" @onclick=@( () => Instrument.ToggleTrackPlayback( i1 ) )>
<div class="index">@i1</div>
<div class="player">
@if ( Instrument.PlaybackTracksInstruments[i].IsValid() )
{
<img src="@( $"avatar://{Instrument.PlaybackTracksInstruments[i].Network.Owner.SteamId}" )"/>
}
</div>
</button>
}
</main>
</section>
<section class="progress">
<h2 class="title">Progress</h2>
<main>
@*<div class="progress-bar">
<div class="progress-bar-fill" style="width: @( Instrument.PlaybackProgress * 100 )px"></div>
</div>*@
@( $"{(Instrument.PlaybackProgress * 100):0.00}%" )
</main>
</section>
</div>
</div>
<div class="notes">
@foreach ( var note in Enum.GetValues( typeof(BaseInstrument.Note) ) )
{
<div class="note @( BaseInstrument.IsBlackNote( (BaseInstrument.Note)note ) ? "black" : "white" )">
<div class="name">@BaseInstrument.NoteNames[(BaseInstrument.Note)note]</div>
<div class="action">
<Image [email protected]( $"PlayNote{(BaseInstrument.Note)note}", InputGlyphSize.Small, GlyphStyle.Knockout )/>
</div>
</div>
}
</div>
</root>
@code {
private BaseInstrument Instrument => PlayerCharacter.Local.Equips.GetEquippedItem<BaseInstrument>( Equips.EquipSlot.Tool );
private BaseInstrument PlayAlongMaster => Scene.GetAllComponents<BaseInstrument>().FirstOrDefault( x => x != Instrument && x.Player.IsValid() && x.WorldPosition.Distance( Instrument.WorldPosition ) < 300 );
private List<string> GetMidiFiles()
{
return FileSystem.Data.FindFile( "midi", "*.mid" ).ToList();
}
protected override int BuildHash()
{
return HashCode.Combine( Instrument?.PlaybackProgress, PlayAlongMaster?.PlaybackTracksInstruments.Select( x => x.Network.Owner.SteamId ).ToArray() );
}
}
@using System
@using Clover.Data
@using Sandbox;
@using Sandbox.Network
@using Sandbox.UI;
@inherits Panel
@namespace Clover.Ui
<root>
@if ( Lobbies.Any() )
{
@foreach ( var lobby in Lobbies )
{
<div class="lobby">
<h2>@lobby.Name</h2>
<button @onclick=@( () => Networking.Connect( lobby.LobbyId ) )>Join</button>
</div>
}
}
@if ( _isRefreshing )
{
<div class="loading">Refreshing...</div>
}
<button @onclick=@( () => Refresh() )>Refresh</button>
<div style="flex-direction: column;" onclick=@( () => Clipboard.SetText( Game.SteamId.ToString() ) )>
<span>@($"Steam ID: {Game.SteamId}")</span>
<span>@($"Host: {Connection.Host.SteamId}")</span>
<span>@($"Address: {Connection.Host.Address}")</span>
<span>@($"Party: {Connection.Host.PartyId}")</span>
</div>
</root>
@code {
private List<LobbyInformation> Lobbies = new();
private bool _isRefreshing = false;
private async void Refresh()
{
_isRefreshing = true;
Lobbies = await Networking.QueryLobbies();
_isRefreshing = false;
}
protected override int BuildHash()
{
return HashCode.Combine( _isRefreshing, Lobbies );
}
}
@using System
@using Clover.Carriable
@using Clover.Components
@using Clover.Player
@using Clover.WorldBuilder
@using Sandbox;
@using Sandbox.UI;
@using Clover.Ui;
@inherits PanelComponent
@namespace Clover
<root>
@if ( PlayerCharacter.Local.IsValid() )
{
<div class="is-saving" @ref=" IsSavingPanel">
<i class="icon">save</i>
</div>
<div class="status @( ShouldShowUi ? "active" : "" )">
<div class="weather">
<Image [email protected]()/>
</div>
<div class="time">@TimeManager.Time.ToString( "HH:mm" )</div>
</div>
<div class="cutscene-bars @( PlayerCharacter.Local.InCutscene ? "active" : "" )">
<div class="bar top"></div>
<div class="bar bottom"></div>
</div>
@if ( ShowInputs )
{
<div class="inputs @( ShouldShowUi ? "active" : "" )">
@{
var group = "";
var actionPairsPrinted = new List<string>();
}
@foreach ( var input in GetCurrentInputs().OrderBy( x => x.Group ).ThenBy( x => x.ActionPair ) )
{
if ( input.Group != group )
{
group = input.Group;
<div class="input-group">@group</div>
}
if ( !string.IsNullOrEmpty( input.ActionPair ) && actionPairsPrinted.Contains( input.ActionPair ) )
{
continue;
}
if ( !string.IsNullOrEmpty( input.ActionPair ) && !actionPairsPrinted.Contains( input.ActionPair ) )
{
actionPairsPrinted.Add( input.ActionPair );
var allPairs = GetCurrentInputs().Where( x => x.ActionPair == input.ActionPair ).ToList();
<div class="input-entry">
<div class="input-glyphs">
@foreach ( var pair in allPairs )
{
<Image [email protected]( pair.Action, InputGlyphSize.Small, GlyphStyle.Dark )/>
}
</div>
<div class="input-name">@input.Name</div>
</div>
}
else
{
<div class="input-entry">
<div class="input-glyphs">
<Image [email protected]( input.Action, InputGlyphSize.Small, GlyphStyle.Dark )/>
</div>
<div class="input-name">@input.Name</div>
</div>
}
}
</div>
}
@if ( PlayerCharacter.Local.Components.TryGet<HideAndSeek>( out var hideAndSeek ) && HideAndSeek.Leader.IsValid() && HideAndSeek.Leader.IsRoundActive )
{
if ( hideAndSeek.IsBlind )
{
<div class="hide-and-seek-blind">
<h1>Hiding...</h1>
<div class="sub">
<div class="blind-icon">
<i class="icon">visibility_off</i>
</div>
<div class="blind-text">@Math.Round( HideAndSeek.Leader.BlindSecondsLeft )</div>
</div>
</div>
}
else
{
<div class="hide-and-seek">
@if ( hideAndSeek.IsSeeker )
{
<h1>Seeking...</h1>
}
else
{
<h1>Hiding...</h1>
}
@if ( hideAndSeek.BlindSecondsLeft > 0 )
{
<div class="time">
<i class="icon">visibility_off</i>
<span>@Math.Round( hideAndSeek.BlindSecondsLeft )</span>
</div>
}
else
{
<div class="time">
<i class="icon">timer</i>
<span>@Math.Round( hideAndSeek.RoundSecondsLeft )</span>
</div>
}
</div>
}
}
@if ( PlayerCharacter.Local.Equips.TryGetEquippedItem<BaseInstrument>( Equips.EquipSlot.Tool, out var instrument ) && instrument.IsPlaying )
{
<InstrumentUi/>
}
}
else if ( string.IsNullOrEmpty( PlayerCharacter.SpawnPlayerId ) )
{
<PlayerSelect/>
}
</root>
@using System
@using Clover.Data
@using Sandbox;
@using Sandbox.UI;
@inherits Panel
@namespace Clover.Ui
<root>
<ScenePanel @ref=" _scenePanel"/>
</root>
using Sandbox;
public sealed class CardBreaker : Component
{
protected override void OnStart()
{
}
public void Init(Material material, Rotation rot, float scale)
{
LocalRotation = rot;
LocalScale = new Vector3( scale );
for ( int i = GameObject.Children.Count - 1; i >= 0; i-- )
{
var child = GameObject.Children[i];
child.SetParent( null );
var modelRenderer = child.Components.Get<ModelRenderer>();
modelRenderer.Tint = Color.White;
modelRenderer.MaterialOverride = material;
var rigidBody = child.Components.Get<Rigidbody>();
rigidBody.ApplyImpulse( (child.WorldPosition - WorldPosition).Normal * Game.Random.Float( 10f, 100f ) * rigidBody.PhysicsBody.Mass );
rigidBody.ApplyImpulse( new Vector3( 0f, 0f, 1f ) * Game.Random.Float( 400f, 800f ) * rigidBody.PhysicsBody.Mass );
rigidBody.AngularVelocity = Vector3.Random * Game.Random.Float( -10f, 10f );
}
GameObject.Destroy();
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
using System.Text.Json;
public enum EasingType
{
None = -1,
Linear = 0,
SineIn, SineOut, SineInOut,
QuadIn, QuadOut, QuadInOut,
CubicIn, CubicOut, CubicInOut,
QuartIn, QuartOut, QuartInOut,
QuintIn, QuintOut, QuintInOut,
ExpoIn, ExpoOut, ExpoInOut,
ExtremeIn, ExtremeOut, ExtremeInOut,
ElasticIn, ElasticOut, ElasticInOut,
ElasticSoftIn, ElasticSoftOut, ElasticSoftInOut,
BackIn, BackOut, BackInOut,
BounceIn, BounceOut, BounceInOut
};
public struct IntVector2 : IEquatable<IntVector2>
{
public static implicit operator Vector2( IntVector2 vec )
{
return new Vector2( vec.x, vec.y );
}
public static explicit operator IntVector2( Vector2 vec )
{
return new IntVector2( (int)vec.x, (int)vec.y );
}
/// <summary>
/// A vector with zero for all components.
/// </summary>
public static readonly IntVector2 Zero = new IntVector2( 0, 0 );
/// <summary>
/// A normalized vector along the positive X axis.
/// </summary>
public static readonly IntVector2 UnitX = new IntVector2( 1, 0 );
/// <summary>
/// A normalized vector along the positive Y axis.
/// </summary>
public static readonly IntVector2 UnitY = new IntVector2( 0, 1 );
#region Operators
/// <summary>
/// The identity operator.
/// </summary>
public static IntVector2 operator +( IntVector2 vec )
{
return vec;
}
/// <summary>
/// Component-wise addition of a vector to another.
/// </summary>
public static IntVector2 operator +( IntVector2 a, IntVector2 b )
{
return new IntVector2( a.x + b.x, a.y + b.y );
}
/// <summary>
/// Finds the negation of a vector.
/// </summary>
public static IntVector2 operator -( IntVector2 vec )
{
return new IntVector2( -vec.x, -vec.y );
}
/// <summary>
/// Component-wise subtraction of a vector from another.
/// </summary>
public static IntVector2 operator -( IntVector2 a, IntVector2 b )
{
return new IntVector2( a.x - b.x, a.y - b.y );
}
/// <summary>
/// Component-wise multiplication of a vector by another.
/// </summary>
public static IntVector2 operator *( IntVector2 a, IntVector2 b )
{
return new IntVector2( a.x * b.x, a.y * b.y );
}
/// <summary>
/// Multiplies a vector by a scalar.
/// </summary>
public static IntVector2 operator *( IntVector2 vec, int val )
{
return new IntVector2( vec.x * val, vec.y * val );
}
/// <summary>
/// Multiplies a vector by a scalar.
/// </summary>
public static Vector2 operator *( IntVector2 vec, float val )
{
return new Vector2( vec.x * val, vec.y * val );
}
/// <summary>
/// Multiplies a vector by a scalar.
/// </summary>
public static IntVector2 operator *( int val, IntVector2 vec )
{
return new IntVector2( vec.x * val, vec.y * val );
}
/// <summary>
/// Multiplies a vector by a scalar.
/// </summary>
public static Vector2 operator *( float val, IntVector2 vec )
{
return new Vector2( vec.x * val, vec.y * val );
}
/// <summary>
/// Component-wise division of a vector by another.
/// </summary>
public static IntVector2 operator /( IntVector2 a, IntVector2 b )
{
return new IntVector2( a.x / b.x, a.y / b.y );
}
/// <summary>
/// Component-wise division of a vector by another.
/// </summary>
public static Vector2 operator /( Vector2 a, IntVector2 b )
{
return new Vector2( a.x / b.x, a.y / b.y );
}
/// <summary>
/// Component-wise division of a vector by another.
/// </summary>
public static Vector2 operator /( IntVector2 a, Vector2 b )
{
return new Vector2( a.x / b.x, a.y / b.y );
}
/// <summary>
/// Division of this vector by a scalar.
/// </summary>
public static IntVector2 operator /( IntVector2 vec, int val )
{
return new IntVector2( vec.x / val, vec.y / val );
}
public static Vector2 operator /( IntVector2 vec, float val )
{
return new Vector2( vec.x / val, vec.y / val );
}
public static IntVector2 operator /( int val, IntVector2 vec )
{
return new IntVector2( val / vec.x, val / vec.y );
}
public static Vector2 operator /( float val, IntVector2 vec )
{
return new Vector2( val / vec.x, val / vec.y );
}
#endregion
/// <summary>
/// Horizontal component.
/// </summary>
public int x;
/// <summary>
/// Vertical component.
/// </summary>
public int y;
/// <summary>
/// Floating point magnitude of the vector.
/// </summary>
public float Length { get { return (float)MathF.Sqrt( x * x + y * y ); } }
/// <summary>
/// Magnitude of the vector in Taxicab geometry.
/// </summary>
public int ManhattanLength { get { return Math.Abs( x ) + Math.Abs( y ); } }
/// <summary>
/// Sum of each component squared.
/// </summary>
public int LengthSquared { get { return x * x + y * y; } }
/// <summary>
/// Gets a normalized vector in the same direction as this one.
/// </summary>
public Vector2 Normalized { get { return LengthSquared >= float.Epsilon ? this / Length : Zero; } }
/// <summary>
/// Gets a vector equal to this one rotated counter-clockwise by 90 degrees.
/// </summary>
public IntVector2 Left { get { return new IntVector2( -y, x ); } }
/// <summary>
/// Gets a vector equal to this one rotated clockwise 90 degrees.
/// </summary>
public IntVector2 Right { get { return new IntVector2( y, -x ); } }
/// <summary>
/// Gets a vector equal to this one rotated 180 degrees.
/// </summary>
public IntVector2 Back { get { return -this; } }
/// <summary>
/// Constructs a vector from the given X and Y components.
/// </summary>
public IntVector2( int x, int y )
{
this.x = x; this.y = y;
}
/// <summary>
/// Finds the scalar product of this vector and another.
/// </summary>
public int Dot( IntVector2 vec )
{
return x * vec.x + y * vec.y;
}
public override bool Equals( object obj )
{
return obj is IntVector2 && Equals( (IntVector2)obj );
}
/// <summary>
/// Tests for equality with another vector.
/// </summary>
public bool Equals( IntVector2 vec )
{
return x == vec.x && y == vec.y;
}
public override int GetHashCode()
{
return x ^ y;
}
/// <summary>
/// Gets a string representing this vector in (X, Y) format.
/// </summary>
public override string ToString()
{
return string.Format( "({0}, {1})", x, y );
}
}
public struct AStarEdge<T>
{
/// <summary>
/// The node this connection leads to.
/// </summary>
public readonly T Dest;
/// <summary>
/// The cost of using this connection.
/// </summary>
public readonly float Cost;
internal AStarEdge( T dest, float cost )
{
Dest = dest;
Cost = cost;
}
}
public static class Utils
{
// projects vector A onto B
public static Vector3 ProjectVector( Vector3 a, Vector3 b )
{
float dotProduct = Vector3.Dot( a, b );
float lengthSquared = Vector3.Dot( b, b );
return (dotProduct / lengthSquared) * b;
}
// returns vector B or C, whichever is in the closest direction compared to A
public static Vector3 FindCloserVector( Vector3 a, Vector3 b, Vector3 c )
{
// Normalize B and C
b = b.Normal;
c = c.Normal;
// Compute dot products
float dotProductB = Vector3.Dot( a, b );
float dotProductC = Vector3.Dot( a, c );
// Determine which dot product is larger
return dotProductB > dotProductC ? b : c;
}
public static GameObject FindChild( this Component component, string name )
{
return component.GameObject.Children.Where( x => x.Name == name ).FirstOrDefault();
}
public static Color GetRandomColor( float total = 1.5f )
{
float r = Game.Random.Float( 0f, 1f );
total = Math.Max( total - r, 0f );
float g = MathF.Min( Game.Random.Float( 0f, 1f ), total );
total = Math.Max( total - g, 0f );
float b = MathF.Min( Game.Random.Float( 0f, 1f ), total );
return new Color( r, g, b );
}
public static T ReadJsonCustom<T>( this BaseFileSystem fs, string filename, T defaultValue = default( T ) )
{
string text = fs.ReadAllText( filename );
if ( string.IsNullOrWhiteSpace( text ) )
{
return defaultValue;
}
JsonSerializerOptions options = new JsonSerializerOptions
{
ReadCommentHandling = JsonCommentHandling.Skip,
PropertyNameCaseInsensitive = true,
AllowTrailingCommas = true,
Converters = { new System.Text.Json.Serialization.JsonStringEnumConverter() },
};
return JsonSerializer.Deserialize<T>( text, options );
}
public const float Deg2Rad = MathF.PI / 180f;
public const float Rad2Deg = 360f / (MathF.PI * 2f);
public static Vector2 RotateVector( Vector2 v, float degrees )
{
var rads = Deg2Rad * degrees;
var ca = MathF.Cos( rads );
var sa = MathF.Sin( rads );
return new Vector2( ca * v.x - sa * v.y, sa * v.x + ca * v.y );
}
public static Vector2 RotatePointAround( Vector2 p, Vector2 anchor, float degrees )
{
var offset = p - anchor;
var newVec = RotateVector( offset, degrees );
return anchor + newVec;
}
public static Vector2 DegreesToVector( float degrees )
{
var rads = Deg2Rad * degrees;
return new Vector2( MathF.Cos( rads ), MathF.Sin( rads ) );
}
public static float VectorToDegrees( Vector2 vec )
{
float radians = (float)Math.Atan2( vec.y, vec.x );
float degrees = radians * (180f / (float)Math.PI);
return degrees;
}
public static Vector2 GetPerpendicularVector( Vector2 vec )
{
return new Vector2( -vec.y, vec.x );
}
public static int GetDistance( int x, int y )
{
return (int)Math.Round( Math.Sqrt( x * x + y * y ) );
}
public static Vector2 GetRandomVector()
{
return DegreesToVector( Game.Random.Float( 0f, 360f ) );
}
public static float FastSin( float input )
{
// wrap input angle to -PI..PI
while ( input < -3.14159265f )
input += 6.28318531f;
while ( input > 3.14159265f )
input -= 6.28318531f;
return 1.27323954f * input + 0.405284735f * (input < 0f ? 1f : -1f) * input * input;
}
public static float DynamicEaseTo( float current, float goal, float factorPercent, float dt, float referenceFrameRate = 60f )
{
if ( float.IsPositiveInfinity( dt ) )
return goal;
return current + (goal - current) * (1f - MathF.Pow( 1f - MathX.Clamp( factorPercent, 0f, 1f ), dt * referenceFrameRate ));
}
public static Vector2 DynamicEaseTo( Vector2 current, Vector2 goal, float factorPercent, float dt, float referenceFrameRate = 60f )
{
if ( float.IsPositiveInfinity( dt ) )
return goal;
var diff = goal - current;
return current + diff * (1f - MathF.Pow( 1f - MathX.Clamp( factorPercent, 0f, 1f ), dt * referenceFrameRate ));
}
public static Vector3 DynamicEaseTo( Vector3 current, Vector3 goal, float factorPercent, float dt, float referenceFrameRate = 60f )
{
if ( float.IsPositiveInfinity( dt ) )
return goal;
var diff = goal - current;
return current + diff * (1f - MathF.Pow( 1f - MathX.Clamp( factorPercent, 0f, 1f ), dt * referenceFrameRate ));
}
public static Rotation DynamicEaseTo( Rotation currentRot, Rotation goalRot, float factorPercent, float dt, float referenceFrameRate = 60f )
{
if ( float.IsPositiveInfinity( dt ) )
return goalRot;
Vector3 current = currentRot.Angles().AsVector3();
Vector3 goal = goalRot.Angles().AsVector3();
var diff = goal - current;
var output = current + diff * (1f - MathF.Pow( 1f - MathX.Clamp( factorPercent, 0f, 1f ), dt * referenceFrameRate ));
return Rotation.From( new Angles( output ) );
}
public static float EaseUnclamped( float value, EasingType easingType )
{
switch ( easingType )
{
case EasingType.SineIn: return SineIn( value );
case EasingType.SineOut: return SineOut( value );
case EasingType.SineInOut: return SineInOut( value );
case EasingType.QuadIn: return QuadIn( value );
case EasingType.QuadOut: return QuadOut( value );
case EasingType.QuadInOut: return QuadInOut( value );
case EasingType.CubicIn: return CubicIn( value );
case EasingType.CubicOut: return CubicOut( value );
case EasingType.CubicInOut: return CubicInOut( value );
case EasingType.QuartIn: return QuartIn( value );
case EasingType.QuartOut: return QuartOut( value );
case EasingType.QuartInOut: return QuartInOut( value );
case EasingType.QuintIn: return QuintIn( value );
case EasingType.QuintOut: return QuintOut( value );
case EasingType.QuintInOut: return QuintInOut( value );
case EasingType.ExpoIn: return ExpoIn( value );
case EasingType.ExpoOut: return ExpoOut( value );
case EasingType.ExpoInOut: return ExpoInOut( value );
case EasingType.ExtremeIn: return ExtremeIn( value );
case EasingType.ExtremeOut: return ExtremeOut( value );
case EasingType.ExtremeInOut: return ExtremeInOut( value );
case EasingType.ElasticIn: return ElasticIn( value );
case EasingType.ElasticOut: return ElasticOut( value );
case EasingType.ElasticInOut: return ElasticInOut( value );
case EasingType.ElasticSoftIn: return ElasticSoftIn( value );
case EasingType.ElasticSoftOut: return ElasticSoftOut( value );
case EasingType.ElasticSoftInOut: return ElasticSoftInOut( value );
case EasingType.BackIn: return BackIn( value );
case EasingType.BackOut: return BackOut( value );
case EasingType.BackInOut: return BackInOut( value );
case EasingType.BounceIn: return BounceIn( value );
case EasingType.BounceOut: return BounceOut( value );
case EasingType.BounceInOut: return BounceInOut( value );
default: return value;
}
}
public static float Map( float value, float inputMin, float inputMax, float outputMin, float outputMax, EasingType easingType = EasingType.Linear, bool clamp = true )
{
if ( inputMin.Equals( inputMax ) || outputMin.Equals( outputMax ) )
return outputMin;
// if(inputMin.Equals(inputMax) || outputMin.Equals(outputMax))
// return outputMax;
if ( clamp )
{
// clamp input
if ( inputMax > inputMin )
{
if ( value < inputMin ) value = inputMin;
else if ( value > inputMax ) value = inputMax;
}
else if ( inputMax < inputMin )
{
if ( value > inputMin ) value = inputMin;
else if ( value < inputMax ) value = inputMax;
}
}
var ratio = EaseUnclamped( (value - inputMin) / (inputMax - inputMin), easingType );
var outVal = outputMin + ratio * (outputMax - outputMin);
// // clamp output
// if(outputMax < outputMin) {
// if(outVal < outputMax) outVal = outputMax;
// else if(outVal > outputMin) outVal = outputMin;
// } else {
// if(outVal > outputMax) outVal = outputMax;
// else if(outVal < outputMin) outVal = outputMin;
// }
return outVal;
}
public static float MapReturn( float value, float inputMin, float inputMax, float outputMin, float outputMax, EasingType easingType = EasingType.Linear )
{
var halfway = inputMin + (inputMax - inputMin) * 0.5f;
if ( value < halfway ) return Map( value, inputMin, halfway, outputMin, outputMax, easingType );
else return Map( value, halfway, inputMax, outputMax, outputMin, GetOppositeEasingType( easingType ) );
}
public static float EasePercent( float percent, EasingType easingType )
{
return Map( percent, 0f, 1f, 0f, 1f, easingType );
}
public static EasingType GetOppositeEasingType( EasingType easingType )
{
var opposite = EasingType.Linear;
switch ( easingType )
{
case EasingType.SineIn: opposite = EasingType.SineOut; break;
case EasingType.SineOut: opposite = EasingType.SineIn; break;
case EasingType.SineInOut: opposite = EasingType.SineInOut; break;
case EasingType.QuadIn: opposite = EasingType.QuadOut; break;
case EasingType.QuadOut: opposite = EasingType.QuadIn; break;
case EasingType.QuadInOut: opposite = EasingType.QuadInOut; break;
case EasingType.CubicIn: opposite = EasingType.CubicOut; break;
case EasingType.CubicOut: opposite = EasingType.CubicIn; break;
case EasingType.CubicInOut: opposite = EasingType.CubicInOut; break;
case EasingType.QuartIn: opposite = EasingType.QuartOut; break;
case EasingType.QuartOut: opposite = EasingType.QuartIn; break;
case EasingType.QuartInOut: opposite = EasingType.QuartInOut; break;
case EasingType.QuintIn: opposite = EasingType.QuintOut; break;
case EasingType.QuintOut: opposite = EasingType.QuintIn; break;
case EasingType.QuintInOut: opposite = EasingType.QuintInOut; break;
case EasingType.ExpoIn: opposite = EasingType.ExpoOut; break;
case EasingType.ExpoOut: opposite = EasingType.ExpoIn; break;
case EasingType.ExpoInOut: opposite = EasingType.ExpoInOut; break;
case EasingType.ExtremeIn: opposite = EasingType.ExtremeOut; break;
case EasingType.ExtremeOut: opposite = EasingType.ExtremeIn; break;
case EasingType.ExtremeInOut: opposite = EasingType.ExtremeInOut; break;
case EasingType.ElasticIn: opposite = EasingType.ElasticOut; break;
case EasingType.ElasticOut: opposite = EasingType.ElasticIn; break;
case EasingType.ElasticInOut: opposite = EasingType.ElasticInOut; break;
case EasingType.ElasticSoftIn: opposite = EasingType.ElasticSoftOut; break;
case EasingType.ElasticSoftOut: opposite = EasingType.ElasticSoftIn; break;
case EasingType.ElasticSoftInOut: opposite = EasingType.ElasticSoftInOut; break;
case EasingType.BackIn: opposite = EasingType.BackOut; break;
case EasingType.BackOut: opposite = EasingType.BackIn; break;
case EasingType.BackInOut: opposite = EasingType.BackInOut; break;
case EasingType.BounceIn: opposite = EasingType.BounceOut; break;
case EasingType.BounceOut: opposite = EasingType.BounceIn; break;
case EasingType.BounceInOut: opposite = EasingType.BounceInOut; break;
}
return opposite;
}
public static float SineIn( float t ) { return 1f - MathF.Cos( t * MathF.PI * 0.5f ); }
public static float SineOut( float t ) { return MathF.Sin( t * (MathF.PI * 0.5f) ); }
public static float SineInOut( float t ) { return -0.5f * (MathF.Cos( MathF.PI * t ) - 1f); }
public static float QuadIn( float t ) { return t * t; }
public static float QuadOut( float t ) { return t * (2f - t); }
public static float QuadInOut( float t ) { return t < 0.5f ? 2f * t * t : -1f + (4f - 2f * t) * t; }
public static float CubicIn( float t ) { return t * t * t; }
public static float CubicOut( float t ) { var t1 = t - 1f; return t1 * t1 * t1 + 1f; }
public static float CubicInOut( float t ) { return t < 0.5f ? 4f * t * t * t : (t - 1f) * (2f * t - 2f) * (2f * t - 2f) + 1f; }
public static float QuartIn( float t ) { return t * t * t * t; }
public static float QuartOut( float t ) { var t1 = t - 1f; return 1f - t1 * t1 * t1 * t1; }
public static float QuartInOut( float t ) { return t < 0.5f ? 4f * t * t * t : (t - 1f) * (2f * t - 2f) * (2f * t - 2f) + 1f; }
public static float QuintIn( float t ) { return t * t * t * t * t; }
public static float QuintOut( float t ) { var t1 = t - 1f; return 1f + t1 * t1 * t1 * t1 * t1; }
public static float QuintInOut( float t ) { var t1 = t - 1f; return t < 0.5f ? 16f * t * t * t * t * t : 1f + 16f * t1 * t1 * t1 * t1 * t1; }
public static float ExpoIn( float t ) { return MathF.Pow( 2f, 10f * (t - 1f) ); }
public static float ExpoOut( float t ) { return 1f - MathF.Pow( 2f, -10f * t ); }
public static float ExpoInOut( float t ) { return t < 0.5f ? ExpoIn( t * 2f ) * 0.5f : 1f - ExpoIn( 2f - t * 2f ) * 0.5f; }
public static float ExtremeIn( float t ) { return MathF.Pow( 10f, 10f * (t - 1f) ); }
public static float ExtremeOut( float t ) { return 1f - MathF.Pow( 10f, -10f * t ); }
public static float ExtremeInOut( float t ) { return t < 0.5f ? ExtremeIn( t * 2f ) * 0.5f : 1f - ExtremeIn( 2f - t * 2f ) * 0.5f; }
public static float ElasticIn( float t ) { return 1f - ElasticOut( 1f - t ); }
public static float ElasticOut( float t ) { var p = 0.3f; return MathF.Pow( 2f, -10f * t ) * MathF.Sin( (t - p / 4f) * (2f * (float)Math.PI) / p ) + 1f; }
public static float ElasticInOut( float t ) { return t < 0.5f ? ElasticIn( t * 2f ) * 0.5f : 1f - ElasticIn( 2f - t * 2f ) * 0.5f; }
public static float ElasticSoftIn( float t ) { return 1f - ElasticSoftOut( 1f - t ); }
public static float ElasticSoftOut( float t ) { var p = 0.5f; return MathF.Pow( 2f, -10f * t ) * MathF.Sin( (t - p / 4f) * (2f * (float)Math.PI) / p ) + 1f; }
public static float ElasticSoftInOut( float t ) { return t < 0.5f ? ElasticSoftIn( t * 2f ) * 0.5f : 1f - ElasticSoftIn( 2f - t * 2f ) * 0.5f; }
public static float BackIn( float t ) { var p = 1f; return t * t * ((p + 1f) * t - p); }
public static float BackOut( float t ) { var p = 1f; var scaledTime = t / 1f - 1f; return scaledTime * scaledTime * ((p + 1f) * scaledTime + p) + 1f; }
public static float BackInOut( float t )
{
var p = 1f;
var scaledTime = t * 2f;
var scaledTime2 = scaledTime - 2f;
var s = p * 1.525f;
if ( scaledTime < 1f ) return 0.5f * scaledTime * scaledTime * ((s + 1f) * scaledTime - s);
else return 0.5f * (scaledTime2 * scaledTime2 * ((s + 1f) * scaledTime2 + s) + 2f);
}
public static float BounceIn( float t ) { return 1f - BounceOut( 1f - t ); }
public static float BounceOut( float t )
{
var scaledTime = t / 1f;
if ( scaledTime < 1 / 2.75f )
{
return 7.5625f * scaledTime * scaledTime;
}
else if ( scaledTime < 2 / 2.75 )
{
var scaledTime2 = scaledTime - 1.5f / 2.75f;
return 7.5625f * scaledTime2 * scaledTime2 + 0.75f;
}
else if ( scaledTime < 2.5 / 2.75 )
{
var scaledTime2 = scaledTime - 2.25f / 2.75f;
return 7.5625f * scaledTime2 * scaledTime2 + 0.9375f;
}
else
{
var scaledTime2 = scaledTime - 2.625f / 2.75f;
return 7.5625f * scaledTime2 * scaledTime2 + 0.984375f;
}
}
public static float BounceInOut( float t )
{
if ( t < 0.5 ) return BounceIn( t * 2f ) * 0.5f;
else return BounceOut( t * 2f - 1f ) * 0.5f + 0.5f;
}
public static void Shuffle<T>( this IList<T> list )
{
System.Random rng = new System.Random();
int n = list.Count;
while ( n > 1 )
{
n--;
int k = rng.Next( n + 1 );
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}
public static string FirstCharToUpper( this string input ) =>
input switch
{
null => throw new ArgumentNullException( nameof( input ) ),
"" => throw new ArgumentException( $"{nameof( input )} cannot be empty", nameof( input ) ),
_ => string.Concat( input[0].ToString().ToUpper(), input.AsSpan( 1 ) )
};
public static string GetRandomIcon( string i0, string i1 ) { int rand = Game.Random.Int( 0, 1 ); switch ( rand ) { case 0: default: return i0; case 1: return i1; } }
public static string GetRandomIcon( string i0, string i1, string i2 ) { int rand = Game.Random.Int( 0, 2 ); switch ( rand ) { case 0: default: return i0; case 1: return i1; case 2: return i2; } }
public static string GetRandomIcon( string i0, string i1, string i2, string i3 ) { int rand = Game.Random.Int( 0, 3 ); switch ( rand ) { case 0: default: return i0; case 1: return i1; case 2: return i2; case 3: return i3; } }
public static string GetRandomIcon( string i0, string i1, string i2, string i3, string i4 ) { int rand = Game.Random.Int( 0, 4 ); switch ( rand ) { case 0: default: return i0; case 1: return i1; case 2: return i2; case 3: return i3; case 4: return i4; } }
public static string GetRandomIcon( string i0, string i1, string i2, string i3, string i4, string i5 ) { int rand = Game.Random.Int( 0, 5 ); switch ( rand ) { case 0: default: return i0; case 1: return i1; case 2: return i2; case 3: return i3; case 4: return i4; case 5: return i5; } }
public static string GetRandomIcon( string i0, string i1, string i2, string i3, string i4, string i5, string i6 ) { int rand = Game.Random.Int( 0, 6 ); switch ( rand ) { case 0: default: return i0; case 1: return i1; case 2: return i2; case 3: return i3; case 4: return i4; case 5: return i5; case 6: return i6; } }
public static string GetRandomIcon( string i0, string i1, string i2, string i3, string i4, string i5, string i6, string i7 ) { int rand = Game.Random.Int( 0, 7 ); switch ( rand ) { case 0: default: return i0; case 1: return i1; case 2: return i2; case 3: return i3; case 4: return i4; case 5: return i5; case 6: return i6; case 7: return i7; } }
private class NodeInfo<T>
{
private const int MaxPoolSize = 8192;
private static List<NodeInfo<T>> _sPool;
internal static NodeInfo<T> Create( T node, NodeInfo<T> prev = null, float costAdd = 0f )
{
NodeInfo<T> nodeInfo;
if ( _sPool == null || _sPool.Count == 0 )
{
nodeInfo = new NodeInfo<T>();
}
else
{
nodeInfo = _sPool[_sPool.Count - 1];
_sPool.RemoveAt( _sPool.Count - 1 );
}
nodeInfo.Node = node;
nodeInfo.Prev = prev;
if ( prev == null )
{
nodeInfo.Depth = 0;
nodeInfo.Cost = 0f;
}
else
{
nodeInfo.Depth = prev.Depth + 1;
nodeInfo.Cost = prev.Cost + costAdd;
}
return nodeInfo;
}
internal static void Pool( NodeInfo<T> nodeInfo )
{
if ( _sPool == null ) _sPool = new List<NodeInfo<T>>( MaxPoolSize );
if ( _sPool.Count >= MaxPoolSize ) return;
_sPool.Add( nodeInfo );
}
private float _heuristic;
public T Node { get; private set; }
public NodeInfo<T> Prev { get; private set; }
public int Depth { get; private set; }
public float Cost { get; private set; }
public float Total { get; private set; }
public float Heuristic
{
get { return _heuristic; }
set
{
_heuristic = value;
Total = Cost + value;
}
}
}
/// <summary>
/// Convenience method to produce a graph connection for use when calling AStar().
/// </summary>
/// <typeparam name="T">Graph node type.</typeparam>
/// <param name="dest">Destination node of the connection.</param>
/// <param name="cost">Cost of taking the connection.</param>
public static AStarEdge<T> Edge<T>( T dest, float cost )
{
return new AStarEdge<T>( dest, cost );
}
private static class AStarWrapper<T>
where T : IEquatable<T>
{
public static NodeInfo<T> FirstMatchOrDefault( List<NodeInfo<T>> list, T toCompare )
{
var count = list.Count;
for ( var i = count - 1; i >= 0; --i )
{
var item = list[i];
if ( item.Node.Equals( toCompare ) )
return item;
}
return null;
}
private static List<NodeInfo<T>> _sOpen;
private static List<NodeInfo<T>> _sClosed;
public static bool AStar( T origin, T target, List<T> destList,
Func<T, IEnumerable<AStarEdge<T>>> adjFunc, Func<T, T, float> heuristicFunc )
{
var open = _sOpen ?? (_sOpen = new List<NodeInfo<T>>());
var clsd = _sClosed ?? (_sClosed = new List<NodeInfo<T>>());
open.Clear();
clsd.Clear();
var first = NodeInfo<T>.Create( origin );
first.Heuristic = heuristicFunc( origin, target );
open.Add( first );
try
{
while ( open.Count > 0 )
{
NodeInfo<T> cur = null;
foreach ( var node in open )
{
if ( cur == null || node.Total < cur.Total ) cur = node;
}
if ( cur.Node.Equals( target ) )
{
for ( var i = cur.Depth; i >= 0; --i )
{
destList.Add( cur.Node );
cur = cur.Prev;
}
destList.Reverse();
return true;
}
open.Remove( cur );
clsd.Add( cur );
foreach ( var adj in adjFunc( cur.Node ) )
{
var node = NodeInfo<T>.Create( adj.Dest, cur, adj.Cost );
var existing = FirstMatchOrDefault( clsd, adj.Dest );
if ( existing != null )
{
if ( existing.Cost <= node.Cost ) continue;
clsd.Remove( existing );
node.Heuristic = existing.Heuristic;
NodeInfo<T>.Pool( existing );
}
existing = FirstMatchOrDefault( open, adj.Dest );
if ( existing != null )
{
if ( existing.Cost <= node.Cost ) continue;
open.Remove( existing );
node.Heuristic = existing.Heuristic;
NodeInfo<T>.Pool( existing );
}
else
{
node.Heuristic = heuristicFunc( node.Node, target );
}
open.Add( node );
}
}
return false;
}
finally
{
foreach ( var nodeInfo in open )
{
NodeInfo<T>.Pool( nodeInfo );
}
foreach ( var nodeInfo in clsd )
{
NodeInfo<T>.Pool( nodeInfo );
}
}
}
}
/// <summary>
/// An implementation of the AStar path finding algorithm.
/// </summary>
/// <typeparam name="T">Graph node type.</typeparam>
/// <param name="origin">Node to start path finding from.</param>
/// <param name="target">The goal node to reach.</param>
/// <param name="adjFunc">Function returning the neighbouring connections for a node.</param>
/// <param name="heuristicFunc">Function returning the estimated cost of travelling between two nodes.</param>
/// <returns>A sequence of nodes representing a path if one is found, otherwise an empty array.</returns>
public static bool AStar<T>( T origin, T target, List<T> destPath,
Func<T, IEnumerable<AStarEdge<T>>> adjFunc, Func<T, T, float> heuristicFunc )
where T : IEquatable<T>
{
return AStarWrapper<T>.AStar( origin, target, destPath, adjFunc, heuristicFunc );
}
}
using Sandbox;
using System.Threading.Tasks;
public class CardAnt : Card
{
public override bool IsAlive => true;
public override bool ShouldHandleEvent( EventType eventType )
{
return eventType == EventType.TurnStart && Manager.Instance.GetNumNearbyEmptyGridPositions( GridPos, adjacentOnly: false ) > 0;
}
public override async Task HandleEventAsync( EventType eventType )
{
var emptyGridPos = Manager.Instance.GetRandomNearbyGridPos( GridPos, empty: true, adjacentOnly: false );
await Task.DelayRealtime( 200 );
Manager.Instance.PlayCardSfx( "card_move", this, volume: 1.1f, pitch: Game.Random.Float( 0.85f, 1.15f ) );
await Task.DelayRealtime( 100 );
Manager.Instance.RemoveCardGridPos( this );
await Manager.Instance.SetCardGridPos( this, emptyGridPos );
await Task.DelayRealtime( 300 );
await Manager.Instance.EventHappened( EventType.AfterCardsMoved );
}
}
using Sandbox;
using System.Threading.Tasks;
public class CardCompass : Card
{
public override bool ShouldHandleEvent( EventType eventType )
{
return eventType == EventType.Mismatch && Manager.Instance.ChosenCards.Contains(this ) && !Manager.Instance.IsMismatchALockedMatch;
}
public override async Task HandleEventAsync( EventType eventType )
{
var otherCard = Manager.Instance.ChosenCards[0] == this ? Manager.Instance.ChosenCards[1] : Manager.Instance.ChosenCards[0];
var matches = Manager.Instance.GetCardsOfType( otherCard.CardType, except: otherCard );
if ( matches.Count == 0 )
return;
var match = matches[Game.Random.Int( 0, matches.Count - 1 )];
Manager.Instance.PushEventMessage( this, eventType );
await Task.DelayRealtime( 300 );
MoveToPos( LocalPosition + (match.LocalPosition - LocalPosition).Normal * 5f, 0.25f, EasingType.QuadInOut, removeControlAfter: true );
Manager.Instance.PlayCardSfx( "compass", this, volume: 1.1f, pitch: Game.Random.Float( 0.85f, 0.95f ) );
await Task.DelayRealtime( 500 );
Manager.Instance.PopEventMessage();
}
}
using Sandbox;
using System.Threading.Tasks;
public class CardFlashlight : Card
{
public override bool ShouldHandleEvent( EventType eventType )
{
return eventType == EventType.Choose && Manager.Instance.ChosenCard == this;
}
public override async Task HandleEventAsync( EventType eventType )
{
var nearbyCards = Manager.Instance.GetNearbyCards( GridPos ).Where(x => !x.IsRevealed).ToList();
if ( nearbyCards.Count == 0 )
return;
Manager.Instance.PushEventMessage( this, eventType );
await Task.DelayRealtime( 150 );
Manager.Instance.PlayCardSfx( "flashlight", this, volume: 1.2f, pitch: Game.Random.Float( 1f, 1.05f ) );
await Task.DelayRealtime( 150 );
Card cardToReveal = nearbyCards[Game.Random.Int( 0, nearbyCards.Count - 1 )]; ;
await Manager.Instance.RevealCard( cardToReveal );
await Task.DelayRealtime( 500 );
Manager.Instance.PlayCardSfx( "flashlight", this, volume: 1.2f, pitch: Game.Random.Float( 0.75f, 0.8f ) );
await Task.DelayRealtime( 250 );
Manager.Instance.HideCard( cardToReveal );
await Task.DelayRealtime( 200 );
Manager.Instance.PopEventMessage();
}
}
using Sandbox;
using System.Threading.Tasks;
using static Sandbox.Physics.CollisionRules;
public class CardRabbit : Card
{
public override bool IsAlive => true;
public override bool ValidateStartingGridPos()
{
var rabbits = Manager.Instance.Cards.Where( x => x.CardType == CardType.Rabbit ).ToList();
var rabbit0 = rabbits[0];
var rabbit1 = rabbits[1];
int farthestDist = GetFarthestDistance( rabbit0, rabbit1 );
if ( rabbits.Count < 2 || farthestDist > 3 )
return true;
int NUM_TRIES = 100;
for ( int i = 0; i < NUM_TRIES; i++ )
{
var newGridPos = Manager.Instance.GetRandomGridPos( except: rabbit0.GridPos );
var otherCard = Manager.Instance.GetCardAtGridPos( newGridPos );
if ( GetFarthestDistance( otherCard, rabbit1 ) > 3 && otherCard.CardType != CardType.Map )
{
Manager.Instance.SwapCardPositionsNonAsync( rabbit0, otherCard );
break;
}
}
if ( !Manager.IsNearby( rabbit0.GridPos, rabbit1.GridPos ) )
return true;
NUM_TRIES = 100;
for ( int i = 0; i < NUM_TRIES; i++ )
{
var newGridPos = Manager.Instance.GetRandomGridPos( except: rabbit0.GridPos );
var otherCard = Manager.Instance.GetCardAtGridPos( newGridPos );
if ( !Manager.IsNearby( newGridPos, rabbit1.GridPos ) && otherCard.CardType != CardType.Map )
{
Manager.Instance.SwapCardPositionsNonAsync( rabbit0, otherCard );
break;
}
}
return false;
}
int GetFarthestDistance(Card card0, Card card1)
{
return Math.Max(Math.Abs( card0.GridPos.x - card1.GridPos.x ), Math.Abs( card0.GridPos.y - card1.GridPos.y ));
}
public override bool ShouldHandleEvent( EventType eventType )
{
return eventType == EventType.TurnStart && Game.Random.Float(0f, 1f) < 0.5f;
}
public override async Task HandleEventAsync( EventType eventType )
{
var otherRabbits = Manager.Instance.Cards.Where(x => x.CardType == CardType.Rabbit && x != this).ToList();
if ( otherRabbits.Count == 0 )
return;
var match = otherRabbits.First();
if ( Manager.IsAdjacent( GridPos, match.GridPos ) )
return;
var path = Manager.Instance.GetPathTo( GridPos, match.GridPos );
if ( path.Count <= 1 )
return;
//Manager.Instance.PushEventMessage( this, eventType );
await Task.DelayRealtime( 150 );
IntVector2 targetGridPos = path.FirstOrDefault();
var targetCard = Manager.Instance.GetCardAtGridPos( targetGridPos );
if ( targetCard != null )
Manager.Instance.PlayCardSfxBetween( "card_move", this, targetCard, volume: 1.1f, pitch: Game.Random.Float( 0.85f, 1.15f ) );
else
Manager.Instance.PlayCardSfx( "card_move", this, volume: 1.1f, pitch: Game.Random.Float( 0.85f, 1.15f ) );
this.MoveToPos( this.LocalPosition.WithZ( Game.Random.Float( 70f, 90f ) ), 0.3f, EasingType.SineOut );
targetCard?.MoveToPos( targetCard.LocalPosition.WithZ( Game.Random.Float( 70f, 90f ) ), 0.25f, EasingType.SineOut );
await Task.DelayRealtime( 250 );
this.MoveToPos( Manager.GetCardPos( targetGridPos ).WithZ( this.LocalPosition.z ), 0.45f, EasingType.SineInOut );
targetCard?.MoveToPos( Manager.GetCardPos( this.GridPos ).WithZ( targetCard.LocalPosition.z ), 0.45f, EasingType.SineInOut );
await Task.DelayRealtime( 450 );
this.MoveToPos( this.LocalPosition.WithZ( Globals.CARD_DEFAULT_HEIGHT + Globals.CARD_ADD_HEIGHT_REVEALED_OR_HOVERED ), 0.2f, EasingType.SineOut );
targetCard?.MoveToPos( targetCard.LocalPosition.WithZ( Globals.CARD_DEFAULT_HEIGHT ), 0.2f, EasingType.SineOut );
await Task.DelayRealtime( 200 );
if ( targetCard != null )
{
await Manager.Instance.SwapCardPositions( this, targetCard );
}
else
{
Manager.Instance.RemoveCardGridPos( this );
await Manager.Instance.SetCardGridPos( this, targetGridPos );
}
this.IsMovementControlled = false;
if ( targetCard != null )
targetCard.IsMovementControlled = false;
await Task.DelayRealtime( 300 );
//Manager.Instance.PopEventMessage();
await Manager.Instance.EventHappened( EventType.AfterCardsMoved );
}
}
using Sandbox;
using System.Threading.Tasks;
public class CardSteak : Card
{
public override bool IsFoodOrBeverage => true;
public override bool ShouldHandleEvent( EventType eventType )
{
return eventType == EventType.Match && Manager.Instance.ChosenCards[1] == this;
}
public override async Task HandleEventAsync( EventType eventType )
{
Manager.Instance.PushEventMessage( this, eventType );
await Task.DelayRealtime( 50 );
Manager.Instance.PlayCardSfxBetween( "meat_pay", Manager.Instance.ChosenCards[0], Manager.Instance.ChosenCards[1], volume: 1.2f, pitch: Game.Random.Float( 0.95f, 1.05f ) );
await Task.DelayRealtime( 50 );
int loseMoneyAmount = 1;
Manager.Instance.SpawnLoseMoneyFloater( loseMoneyAmount, WorldPosition );
await Manager.Instance.LoseMoney( loseMoneyAmount );
await Task.DelayRealtime( 900 );
Manager.Instance.PlayCardSfxBetween( "meat_eat", Manager.Instance.ChosenCards[0], Manager.Instance.ChosenCards[1], volume: 1.3f, pitch: Game.Random.Float( 0.95f, 1.05f ) );
int healAmount = 4 + (int)Manager.Instance.Stats[StatType.FoodAdditionalHeal];
Manager.Instance.SpawnHealHPFloater( healAmount, WorldPosition );
await Manager.Instance.GainHP( healAmount );
await Task.DelayRealtime( 800 );
Manager.Instance.PopEventMessage();
}
}
using Sandbox;
using System.Reflection.PortableExecutable;
using System.Runtime.Versioning;
using System.Threading.Tasks;
public class RelicBandage : Relic
{
public override void LevelUp()
{
base.LevelUp();
int healAmount = 3;
Manager.Instance.HP = Math.Min( Manager.Instance.HP + healAmount, Manager.Instance.MaxHP );
Manager.Instance.TimeSinceHPChanged = 0f;
var mouse = Mouse.Position;
var camera = Scene.Camera;
var ray = camera.ScreenPixelToRay( mouse );
var tr = Scene.Trace.Ray( ray, 10000f ).Run();
Manager.Instance.SpawnHealHPFloater( healAmount, tr.EndPosition );
// todo: better sfx
Manager.Instance.PlaySfx( "bandage", tr.EndPosition.WithZ( camera.WorldPosition.z - 100f ), volume: 0.7f, pitch: Game.Random.Float( 0.95f, 1.05f ) );
//Manager.Instance.RemoveRelic( this );
}
}
using Sandbox;
using System.Reflection.PortableExecutable;
using System.Runtime.Versioning;
using System.Threading.Tasks;
public class RelicClipboard : Relic
{
public override void LevelUp()
{
base.LevelUp();
Manager.Instance.Stats[StatType.ExistingItemExtraChance] += 1f;
}
}
using Sandbox;
using System.Reflection.PortableExecutable;
using System.Runtime.Versioning;
using System.Threading.Tasks;
public class RelicMedicHelmet : Relic
{
public override bool ShouldHandleEvent( EventType eventType )
{
return eventType == EventType.LevelStart;
}
public override async Task HandleEventAsync( EventType eventType )
{
Manager.Instance.PushEventMessage( this, eventType );
await Task.DelayRealtime( 200 );
var camera = Scene.Camera;
var ray = camera.ScreenPixelToRay( new Vector3( Screen.Width / 2, Screen.Height / 2, 0f ) );
var tr = Scene.Trace.Ray( ray, 10000f ).Run();
Manager.Instance.PlaySfx( "medic_helmet", tr.EndPosition.WithZ( Scene.Camera.WorldPosition.z - Globals.CARD_SFX_DEPTH_DIFF ), volume: 2.1f, pitch: Game.Random.Float( 1.2f, 1.3f ) );
await Task.DelayRealtime( 300 );
int healAmount = Level;
Manager.Instance.SpawnHealHPFloater( healAmount, tr.EndPosition );
await Task.DelayRealtime( 350 );
await Manager.Instance.GainHP( healAmount );
await Task.DelayRealtime( 250 );
Manager.Instance.PopEventMessage();
}
}
using Sandbox;
using System.Reflection.PortableExecutable;
using System.Runtime.Versioning;
using System.Threading.Tasks;
public class RelicSatellite : Relic
{
public override bool ShouldHandleEvent( EventType eventType )
{
return eventType == EventType.LevelStart;
}
public override async Task HandleEventAsync( EventType eventType )
{
Manager.Instance.PushEventMessage( this, eventType );
List<Card> validCards = new();
foreach ( var card in Manager.Instance.Cards )
validCards.Add( card );
await Task.DelayRealtime( 150 );
Manager.Instance.PlaySfxCenter( "satellite", volume: 0.95f, pitch: Game.Random.Float( 0.9f, 1f ) );
await Task.DelayRealtime( 350 );
validCards.Shuffle();
for ( int i = 0; i < Math.Min( Level * 3, validCards.Count ); i++ )
{
var card = validCards[i];
Manager.Instance.PlayCardSfx( "card_flip", card, volume: 0.8f, pitch: Game.Random.Float( 1.15f, 1.2f ) );
await Manager.Instance.RevealCard( card );
await Task.DelayRealtime( 360 );
}
await Task.DelayRealtime( 400 );
for ( int i = 0; i < Math.Min( Level * 3, validCards.Count ); i++ )
{
var card = validCards[i];
Manager.Instance.HideCard( card );
Manager.Instance.PlayCardSfx( "card_flip", card, volume: 0.7f, pitch: Game.Random.Float( 0.65f, 0.75f ) );
await Task.DelayRealtime( 300 );
}
await Task.DelayRealtime( 200 );
Manager.Instance.PopEventMessage();
//Manager.Instance.RemoveRelic( this );
}
}
using Sandbox;
using System.Reflection.PortableExecutable;
using System.Runtime.Versioning;
using System.Threading.Tasks;
public class RelicShoppingCart : Relic
{
public override void LevelUp()
{
base.LevelUp();
Manager.Instance.Stats[StatType.ShopExtraItems] += 1f;
}
}
using Sandbox;
using System.Reflection.PortableExecutable;
using System.Runtime.Versioning;
using System.Threading.Tasks;
public class RelicTriangleRuler : Relic
{
public override bool ShouldHandleEvent( EventType eventType )
{
return eventType == EventType.Match && Manager.Instance.Stats[StatType.NumMatches] % (5 - Level) == 0;
}
public override async Task HandleEventAsync( EventType eventType )
{
List<Card> validCards = new();
CheckCornerCard( new IntVector2( 0, 0 ), validCards );
CheckCornerCard( new IntVector2( 0, Manager.Instance.GridHeight - 1 ), validCards );
CheckCornerCard( new IntVector2( Manager.Instance.GridWidth - 1, 0 ), validCards );
CheckCornerCard( new IntVector2( Manager.Instance.GridWidth - 1, Manager.Instance.GridHeight - 1 ), validCards );
if ( validCards.Count == 0 )
return;
validCards.Shuffle();
var cardToReveal = validCards.First();
await Task.DelayRealtime( 300 );
Manager.Instance.PushEventMessage( this, eventType );
await Task.DelayRealtime( 100 );
Manager.Instance.PlaySfxCenter( "triangle_ruler", volume: 0.3f, pitch: Game.Random.Float( 1.5f, 1.55f ) );
await Task.DelayRealtime( 200 );
Manager.Instance.PlayCardSfx( "card_flip", cardToReveal, volume: 0.8f, pitch: Game.Random.Float( 1.15f, 1.2f ) );
await Manager.Instance.RevealCard( cardToReveal );
await Task.DelayRealtime( 850 );
Manager.Instance.PlayCardSfx( "card_flip", cardToReveal, volume: 0.7f, pitch: Game.Random.Float( 0.65f, 0.75f ) );
Manager.Instance.HideCard( cardToReveal );
await Task.DelayRealtime( 350 );
Manager.Instance.PopEventMessage();
}
void CheckCornerCard( IntVector2 gridPos, List<Card> cards )
{
var card = Manager.Instance.GetCardAtGridPos( gridPos );
if ( card != null && !Manager.Instance.ChosenCards.Contains( card ) && !card.IsRevealed )
cards.Add( card );
}
}