mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-08-01 08:18:20 -04:00
https://sbox.game/dev/doc/gameplay/inventory-weapons/ Added InventoryComponent - a slot based inventory, hotbar or shared bucket slots, with a starting loadout BaseInventoryItem - base class for inventory items, with hooks to veto switching, holstering and dropping BaseWeapon - a GMod style base weapon - fire modes, magazines and reserve ammo, reloading, dry fire, ballistics, spread, crosshair and HUD BaseWeaponModel - view/world model base with muzzle flash, brass ejection, tracers and animation hooks Ammo types are resources (.ammo) - weapons share reserve pools by type, clamped to a max reserve BaseWeapon.ShootBullets / ShootBullet - bullet tracing with damage, impulse and hitbox tags Weapon hold types editor Animgraph enum parameters can be set by option name - Renderer.Set( "holdtype", "pistol" ) NpcUsage on BaseWeapon - engagement range and burst behaviour so NPCs can use any weapon ICameraModifier - cameras compose their view through an ordered modifier chain, read the result from CameraComponent.View CameraEffectSystem - screen shake, directional punches and tilts, with world epicenter falloff - camera.AddShake / AddPunch / AddTilt EnvShake component - shakes the view, rumbles controllers, kicks physics TracerEffect - an animated tracer line, ITargetedEffect for effects that fly at a target SceneAnchor - a world position that can follow an object or bone SkinnedModelRenderer.GetClosestBone( worldPosition ) Vector3.WithAimCone can take a custom Random for reproducible spread Improve/Fix PlayerController searches parents for IPressable when pressing PlayerController camera runs through the modifier chain - IEvents.PostCameraSetup is obsolete SoundPlayer preview uses shortcuts - Space toggles playback, Ctrl+Space restarts Fixed Radius( 0 ) traces missing everything Fixed NRE in AssetPreviewWidget Removed Removed CameraComponent.ISceneCameraSetup (obsolete since October, nothing uses)
89 lines
2.7 KiB
C#
89 lines
2.7 KiB
C#
using System.ComponentModel;
|
|
|
|
namespace Sandbox;
|
|
|
|
/// <summary>
|
|
/// Dispatches <see cref="ChangeAttribute"/> callbacks - the code generator wraps the setter of a
|
|
/// [Change] property to come through here. Never called manually.
|
|
/// </summary>
|
|
[EditorBrowsable( EditorBrowsableState.Never )]
|
|
public static class ChangeCallback
|
|
{
|
|
/// <summary>
|
|
/// Sets the property and invokes its [Change] callback when the value differs.
|
|
/// </summary>
|
|
public static void OnPropertySet<T>( in WrappedPropertySet<T> p )
|
|
{
|
|
var attribute = p.GetAttribute<ChangeAttribute>();
|
|
var property = Game.TypeLibrary.GetMemberByIdent( p.MemberIdent ) as PropertyDescription;
|
|
|
|
// The type system doesn't know this property - a private one needs [Expose] for the change
|
|
// callback to resolve. Still set the value, just skip the callback.
|
|
if ( property is null )
|
|
{
|
|
Log.Warning( $"[Change] property {p.PropertyName} isn't in the type library - is it private without [Expose]?" );
|
|
p.Setter( p.Value );
|
|
return;
|
|
}
|
|
|
|
var type = property.TypeDescription;
|
|
var functionName = attribute.Name ?? $"On{property.Name}Changed";
|
|
var isStatic = p.IsStatic;
|
|
|
|
var method = type.Methods.FirstOrDefault( x =>
|
|
x.IsNamed( functionName ) &&
|
|
x.IsStatic == isStatic &&
|
|
x.Parameters.Length == 2 &&
|
|
x.Parameters[0].ParameterType == typeof( T ) &&
|
|
x.Parameters[1].ParameterType == typeof( T ) );
|
|
|
|
var methodWithoutParams = method is not null ? null : type.Methods.FirstOrDefault( x =>
|
|
x.IsNamed( functionName ) &&
|
|
x.IsStatic == isStatic &&
|
|
x.Parameters.Length == 0 );
|
|
|
|
var oldValue = property.GetValue( p.Object );
|
|
var isTheSame = Equals( p.Value, oldValue );
|
|
|
|
p.Setter( p.Value );
|
|
|
|
if ( isTheSame )
|
|
return;
|
|
|
|
if ( p.Object is Component component )
|
|
{
|
|
if ( component.Flags.HasFlag( ComponentFlags.Deserializing ) )
|
|
{
|
|
// Do nothing if the component is deserializing. This should be the first
|
|
// time the property is loaded, so we don't want to invoke a callback.
|
|
return;
|
|
}
|
|
|
|
var go = component.GameObject;
|
|
if ( go.IsValid()
|
|
&& (go.Flags.HasFlag( GameObjectFlags.Deserializing )
|
|
|| go.Flags.HasFlag( GameObjectFlags.Loading )) )
|
|
{
|
|
// Do nothing if the component's GameObject is deserializing or
|
|
// we're loading.
|
|
return;
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
if ( method is not null )
|
|
method.Invoke( p.Object, new[] { oldValue, p.Value } );
|
|
else if ( methodWithoutParams is not null )
|
|
methodWithoutParams.Invoke( p.Object );
|
|
else
|
|
Log.Warning(
|
|
$"{type.Name}.{property.Name} has [Change] but we can not find {functionName}( {property.PropertyType} oldValue, {property.PropertyType} newValue )" );
|
|
}
|
|
catch ( Exception e )
|
|
{
|
|
Log.Error( e );
|
|
}
|
|
}
|
|
}
|