Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion SecretAPI/Debugging/PrefabDebugger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ namespace SecretAPI.Debugging;
/// <summary>
/// Debugs base-game prefabs by logging information about them.
/// </summary>
// ? Possibly write to file instead of server console?
internal static class PrefabDebugger
{
/// <summary>
Expand Down
3 changes: 3 additions & 0 deletions SecretAPI/Extensions/MirrorExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ public static void SendFakeRpcMessage(this Player target, NetworkBehaviour behav
public static void SendFakeSyncListData<T>(this Player target, NetworkBehaviour behaviour, ulong listIndex, SyncListChange<T> change)
{
if (listIndex <= 0)
{
Logger.Warn($"[MirrorExtensions.SendFakeSyncListData] Index is {listIndex} - Expected 1 or higher!: + {new StackTrace()}");
return; // avoid client kick
}

SendFakeState(target, behaviour, writer =>
{
Expand Down
28 changes: 13 additions & 15 deletions SecretAPI/Features/DecalHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ namespace SecretAPI.Features;
/// </summary>
public static class DecalHelpers
{
private static bool hasData = false;
private static ItemType itemType = ItemType.None;
private static byte subcomponentIndex = 0;
private static AutoSyncData? autoSyncData;

/// <summary>
/// Creates an <see cref="AutosyncMessage"/> for spawning a decal that can be sent to players.
Expand All @@ -31,16 +29,16 @@ public static AutosyncMessage GetDecalMessage(Vector3 position, Vector3 startPos
RelativePosition hitPoint = new(position);
RelativePosition startRaycastPoint = new(startPosition);

(ItemType itemTypeId, byte moduleIndex) = GetItemData();
AutoSyncData data = GetAutoSyncData();

using NetworkWriterPooled? writer = NetworkWriterPool.Get();
writer.WriteByte(moduleIndex);
using NetworkWriterPooled writer = NetworkWriterPool.Get();
writer.WriteByte(data.SubcomponentIndex);
writer.WriteSubheader(ImpactEffectsModule.RpcType.ImpactDecal);
writer.WriteByte((byte)type);
writer.WriteRelativePosition(hitPoint);
writer.WriteRelativePosition(startRaycastPoint);

return new AutosyncMessage(writer, new ItemIdentifier(itemTypeId, 0));
return new AutosyncMessage(writer, new ItemIdentifier(data.ItemType, 0));
}

/// <summary>
Expand Down Expand Up @@ -88,24 +86,24 @@ public static void SpawnDecalFromDirection(Vector3 position, Vector3 direction,
public static void SpawnDecalFromDirection(Vector3 position, Quaternion direction, DecalPoolType type = DecalPoolType.Blood)
=> GetDecalMessage(position, position - (direction * Vector3.forward), type).SendToAuthenticated();

private static (ItemType ItemType, byte SubcomponentIndex) GetItemData()
private static AutoSyncData GetAutoSyncData()
{
if (hasData)
return (itemType, subcomponentIndex);
if (autoSyncData != null)
return autoSyncData;

foreach (ModularAutosyncItem? autoItem in ModularAutosyncItem.AllTemplates)
foreach (ModularAutosyncItem autoItem in ModularAutosyncItem.AllTemplates)
{
for (byte b = 0; b < autoItem.AllSubcomponents.Length; b++)
{
if (autoItem.AllSubcomponents[b] is not ImpactEffectsModule)
continue;
subcomponentIndex = b;
itemType = autoItem.ItemTypeId;
hasData = true;
return (itemType, subcomponentIndex);

return autoSyncData = new AutoSyncData(autoItem.ItemTypeId, b);
}
}

throw new InvalidOperationException("Couldn't find the `InventorySystem.Items.Firearms.Modules.ImpactEffectsModule` in the any ModularAutosyncItem!");
}

private sealed record AutoSyncData(ItemType ItemType, byte SubcomponentIndex);
}
40 changes: 37 additions & 3 deletions SecretAPI/Features/Effects/CustomPlayerEffect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,54 @@

using Logger = LabApi.Features.Console.Logger;

// ! PLEASE NW MAKE CUSTOM EFFECTS GOOD I'M GONNA CRY

/// <summary>
/// Handles custom player effects.
/// <remarks>Must register to <see cref="EffectsToRegister"/> to work.</remarks>
/// <remarks>Must register with <see cref="Register"/> in order to work.</remarks>
/// </summary>
public abstract class CustomPlayerEffect : StatusEffectBase
{
/// <summary>
/// Gets a list of types to register (Must inherit <see cref="StatusEffectBase"/>).
/// <remarks>Must be <see cref="Type"/>, can be gotten through <code>typeof(Scp207)</code></remarks>
/// </summary>
public static List<Type> EffectsToRegister { get; } = [];
[Obsolete("This is becoming readonly in 4.0 - Use Register<T>()")]
public static List<Type> EffectsToRegister { get; } = []; // TODO 4.0: private static HashSet<Type> RegisteredEffects

/// <summary>
/// Gets the <see cref="Player"/> with this effect.
/// </summary>
public Player Owner => field ??= Player.Get(Hub);

/// <summary>
/// Registers a custom effect type.
/// </summary>
/// <typeparam name="T">The effect type to register.</typeparam>
public static void Register<T>()
where T : StatusEffectBase => Register(typeof(T));

/// <summary>
/// Registers a custom effect type.
/// </summary>
/// <param name="type">The <see cref="Type"/> to register. May not be abstract and must inherit <see cref="StatusEffectBase"/>.</param>
public static void Register(Type type)
{
if (!type.IsAssignableFrom(typeof(StatusEffectBase)))
{
Logger.Error($"[CustomPlayerEffect.Register] {type.FullName} does not inherit StatusEffectBase!");
return;
}

if (type.IsAbstract)
{
Logger.Error($"[CustomPlayerEffect.Register] {type.FullName} is abstract!");
return;
}

#pragma warning disable CS0618 // Type or member is obsolete
EffectsToRegister.Add(type);
}

/// <inheritdoc/>
public override string ToString() => $"{GetType().Name}: Owner ({Owner}) - Intensity ({Intensity}) - Duration {Duration}";

Expand All @@ -41,6 +72,9 @@ internal static void Initialize()
EffectsToRegister.Add(typeof(Depleted));
EffectsToRegister.Add(typeof(BlastResistance));

// it would probably be better to patch PlayerEffectsController::Awake instead of using sceneLoaded
// this would also allow registering after players join
// although the current way might be the fastest since its just base unity handling it on the prefab
SceneManager.sceneLoaded += OnSceneLoaded;
}

Expand Down
2 changes: 1 addition & 1 deletion SecretAPI/Features/Effects/CustomTickingPlayerEffect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using UnityEngine;

/// <summary>
/// Custom Effect for <see cref="TickingEffectBase"/>.
/// Custom version of <see cref="TickingEffectBase"/> for <see cref="CustomPlayerEffect"/>.
/// </summary>
public abstract class CustomTickingPlayerEffect : CustomPlayerEffect
{
Expand Down
26 changes: 14 additions & 12 deletions SecretAPI/Features/UserSettings/CustomSetting.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,8 @@ protected CustomSetting(ServerSpecificSettingBase setting)
public ServerSpecificSettingBase Base { get; }

/// <summary>
/// Gets the known owner.
/// Gets the known owner or <see langword="null"/> on the initial object.
/// </summary>
/// <remarks>This is null on the original object.</remarks>
public Player? KnownOwner { get; private set; }

/// <summary>
Expand All @@ -75,7 +74,7 @@ protected CustomSetting(ServerSpecificSettingBase setting)
public SettingResponseType LastUpdateType { get; private set; } = SettingResponseType.None;

/// <summary>
/// Gets a value indicating whether the current value received is different to that prior to the most recent <see cref="CustomSetting.HandleSettingUpdate"/> call.
/// Gets a value indicating whether the current value received is different to that prior to the most recent <see cref="CustomSetting.HandleSettingUpdate"/> call.
/// </summary>
public virtual bool HasValueChanged { get; } = false;

Expand Down Expand Up @@ -154,10 +153,7 @@ public bool IsShared
set => CollectionId = value ? byte.MinValue : byte.MaxValue;
}

/// <summary>
/// Registers a collection of settings.
/// </summary>
/// <param name="settings">The settings to register.</param>
/// <inheritdoc cref="Register(IEnumerable{CustomSetting})"/>
public static void Register(params CustomSetting[] settings) => Register((IEnumerable<CustomSetting>)settings);

/// <summary>
Expand All @@ -173,18 +169,24 @@ public static void Register(IEnumerable<CustomSetting> settings)
}
}

/// <summary>
/// Unregisters collection of settings.
/// </summary>
/// <param name="settings">The settings to unregister.</param>
public static void UnRegister(params CustomSetting[] settings) => CustomSettings.RemoveAll(settings.Contains);
/// <inheritdoc cref="UnRegister(IEnumerable{CustomSetting})"/>
public static void UnRegister(params CustomSetting[] settings) => UnRegister((IEnumerable<CustomSetting>)settings);

/// <summary>
/// Unregisters a collection of settings.
/// </summary>
/// <param name="settings">The settings to unregister.</param>
public static void UnRegister(IEnumerable<CustomSetting> settings) => CustomSettings.RemoveAll(settings.Contains);

/// <inheritdoc cref="UnRegister(IEnumerable{Type})"/>
public static void UnRegister(params Type[] types) => UnRegister((IEnumerable<Type>)types);

/// <summary>
/// Unregisters a collection of setting based on their <see cref="Type"/>.
/// </summary>
/// <param name="types">A collection of setting types to unregister. This should be the exact type, not the base.</param>
public static void UnRegister(IEnumerable<Type> types) => CustomSettings.RemoveAll(s => types.Any(t => s.GetType() == t));

/// <summary>
/// Tries to get player specific setting.
/// </summary>
Expand Down
Loading