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
180 changes: 180 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# Top-most EditorConfig file
root = true

# All files
[*]
indent_style = space
indent_size = 4
end_of_line = crlf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

# Markdown and JSON files
[*.{md,json}]
indent_size = 2

# XML / Project files
[*.{csproj,props,targets,xml}]
indent_size = 2

# C# files
[*.cs]

#### Core Roslyn Analyzer Defaults ####
dotnet_analyzer_diagnostic.category-Style.severity = suggestion
dotnet_analyzer_diagnostic.category-Performance.severity = suggestion
dotnet_analyzer_diagnostic.category-Design.severity = suggestion
dotnet_analyzer_diagnostic.category-Reliability.severity = warning
dotnet_analyzer_diagnostic.category-Usage.severity = warning

#### Dead / Unused Code (IDE Rules) ####
dotnet_diagnostic.IDE0051.severity = warning # Remove unused private members
dotnet_diagnostic.IDE0052.severity = warning # Remove unread private members
dotnet_diagnostic.IDE0059.severity = warning # Unnecessary value assignment
dotnet_diagnostic.IDE0060.severity = suggestion # Remove unused parameter
dotnet_diagnostic.IDE0005.severity = suggestion # Remove unnecessary using directives
dotnet_diagnostic.IDE0035.severity = warning # Remove unreachable code
csharp_style_unused_value_assignment_preference = discard_variable:warning
csharp_style_unused_value_expression_statement_preference = discard_variable:warning

# Compiler warnings for unused items
dotnet_diagnostic.CS0168.severity = warning # Variable declared but never used
dotnet_diagnostic.CS0219.severity = warning # Variable assigned but never used


#### Code Style & Formatting ####
dotnet_sort_system_directives_first = true
dotnet_separate_import_directive_groups = false
csharp_style_namespace_declarations = file_scoped:suggestion

# Newline preferences
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true

# Indentation preferences
csharp_indent_case_contents = true
csharp_indent_switch_labels = true
csharp_indent_labels = one_less_than_current

# Space preferences
csharp_space_after_cast = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_around_binary_operators = before_and_after

#### Language Preferences ####
# Var preferences
csharp_style_var_for_built_in_types = false:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = false:suggestion

# Expression-bodied members
csharp_style_expression_bodied_methods = when_on_single_line:suggestion
csharp_style_expression_bodied_constructors = false:suggestion
csharp_style_expression_bodied_operators = when_on_single_line:suggestion
csharp_style_expression_bodied_properties = when_on_single_line:suggestion
csharp_style_expression_bodied_indexers = when_on_single_line:suggestion
csharp_style_expression_bodied_accessors = when_on_single_line:suggestion
csharp_style_expression_bodied_lambdas = true:suggestion

# Pattern matching & modern language features
csharp_style_pattern_matching = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_prefer_simple_using_statement = true:suggestion
csharp_style_prefer_null_check_over_type_check = true:suggestion
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion

# 'this.' and qualification
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_event = false:suggestion

# Modifiers & Nullability
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
dotnet_style_readonly_field = true:suggestion
dotnet_style_prefer_auto_properties = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion

#### Naming Conventions ####
# PascalCase for types (classes, structs, enums, delegates)
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion

dotnet_naming_symbols.types.applicable_kinds = class, struct, enum, delegate
dotnet_naming_symbols.types.applicable_accessibilities = *

# Interfaces must begin with 'I'
dotnet_naming_rule.interfaces_should_start_with_i.symbols = interfaces
dotnet_naming_rule.interfaces_should_start_with_i.style = begins_with_i
dotnet_naming_rule.interfaces_should_start_with_i.severity = suggestion

dotnet_naming_symbols.interfaces.applicable_kinds = interface
dotnet_naming_symbols.interfaces.applicable_accessibilities = *
dotnet_naming_styles.begins_with_i.required_prefix = I
dotnet_naming_styles.begins_with_i.capitalization = pascal_case

# Private and internal fields with underscore prefix (_camelCase)
dotnet_naming_rule.private_fields_should_have_underscore_prefix.symbols = private_fields
dotnet_naming_rule.private_fields_should_have_underscore_prefix.style = underscore_camel_case
dotnet_naming_rule.private_fields_should_have_underscore_prefix.severity = suggestion

dotnet_naming_symbols.private_fields.applicable_kinds = field
dotnet_naming_symbols.private_fields.applicable_accessibilities = private, internal, private_protected
dotnet_naming_styles.underscore_camel_case.required_prefix = _
dotnet_naming_styles.underscore_camel_case.capitalization = camel_case

# Public and protected fields PascalCase
dotnet_naming_rule.public_fields_should_be_pascal_case.symbols = public_fields
dotnet_naming_rule.public_fields_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.public_fields_should_be_pascal_case.severity = suggestion

dotnet_naming_symbols.public_fields.applicable_kinds = field
dotnet_naming_symbols.public_fields.applicable_accessibilities = public, protected, protected_internal

# Methods, properties, events PascalCase
dotnet_naming_rule.members_should_be_pascal_case.symbols = members
dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion

dotnet_naming_symbols.members.applicable_kinds = method, property, event
dotnet_naming_symbols.members.applicable_accessibilities = *

# Parameters and local variables camelCase
dotnet_naming_rule.parameters_and_locals_should_be_camel_case.symbols = parameters_and_locals
dotnet_naming_rule.parameters_and_locals_should_be_camel_case.style = camel_case
dotnet_naming_rule.parameters_and_locals_should_be_camel_case.severity = suggestion

dotnet_naming_symbols.parameters_and_locals.applicable_kinds = parameter, local
dotnet_naming_symbols.parameters_and_locals.applicable_accessibilities = *

dotnet_naming_styles.pascal_case.capitalization = pascal_case
dotnet_naming_styles.camel_case.capitalization = camel_case

#### Common Code Analysis Quality Rules (CA) ####
dotnet_diagnostic.CA1822.severity = suggestion # Mark members as static
dotnet_diagnostic.CA1806.severity = warning # Do not ignore method results
dotnet_diagnostic.CA2000.severity = suggestion # Dispose objects before losing scope
dotnet_diagnostic.CA2208.severity = warning # Instantiate argument exceptions correctly
dotnet_diagnostic.CA1051.severity = suggestion # Do not declare visible instance fields

#### Test Project Overrides ####
[**{Test,Tests}*/**.cs]
# Allow underscores in unit test method names (e.g. Method_Condition_ExpectedResult)
dotnet_diagnostic.CA1707.severity = none
dotnet_naming_rule.members_should_be_pascal_case.severity = none
# Relax static member and unused parameter suggestions for tests
dotnet_diagnostic.CA1822.severity = none
dotnet_diagnostic.IDE0060.severity = none
61 changes: 61 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,67 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.1.1-dev-alpha] - 2026-10-02

### Added

* `SceneLoadWaiter.GetPlayer()` — extracted helper that resolves the player `GameObject` from `Deadshot.GameManager`.
* `SceneLoadWaiter.DisableAllCameras()` — extracted helper that disables all cameras except the player's `Head/MainCamera`.
* `SceneLoadWaiter.DisableMenuAndSceneRenderers()` — extracted helper that hides `MainMenu` canvases and `C1L2` scene renderers, excluding the player.
* `SceneLoadWaiter.GetCustomScene()` — extracted helper that scans loaded scenes by name and returns the matching `Scene`.
* `SceneLoadWaiter.FindCustomPlayerSpawn(Scene)` — updated to accept the already-resolved `Scene` instead of re-scanning for it.
* Added null guards and restored deep recursive traversal in `FindChildRecursive` to support nested hierarchy spawn points.
* Player `CharacterController` is now restored in a `finally` block, ensuring it is re-enabled even if player placement throws.
* `SceneManager.BaseGameplaySceneName` constant to avoid hardcoded scene name strings.
* `AsyncOperation` tracking for asynchronous scene unloads to prevent reload race conditions.

### Changed

* `SceneLoadWaiter.InitializePlayer()` refactored to delegate player, camera, and renderer setup to dedicated helper methods.
* Corrected the renderer-filtering scene name from `C1L1` to `C1L2`.
* `SceneManager.LoadScene()` cleans up existing waiters and passes any pending unload operation to the new waiter.
* `SceneLoadWaiter` destroys its `GameObject` upon placement completion or spawn search timeout.
* `EventManager.Update()` converted from a static method to an instance lifecycle method on `MonoBehaviour`.

### Fixed

* Resolved persistent `SceneLoadWaiter` memory leak and CPU overhead after scene load.
* Fixed broken child traversal in `FindChildRecursive` failing on nested spawn points.
* Fixed race condition where reloading an already loaded scene attempted placement before unloader completed.
* Fixed `EventManager.Update()` not being invoked by Unity's engine loop.
* Removed stray blank lines from `EventManager.LevelPlaytimeEvent()`.

## [1.1.0-dev-alpha] - 2026-10-02

### Added
- `SceneLoadWaiter` MonoBehaviour for additive custom-scene loading while preserving the existing gameplay scene and player.
- `SceneManager.LoadScene(string)` replacing the old `Load()` method — unloads an already-loaded scene before re-loading it, and bootstraps `C1L2` if it is not yet loaded.
- `AssetBundleManager.GetPlayerObject()` helper to retrieve the player `GameObject` from `Deadshot.GameManager`.
- `AssetBundleManager.SpawnPlayerObject()` helper to instantiate the player object.
- `LevelCompleteScreen.SetSecret(string)` to write to `_menu.secretText`.
- `Plugin.cs` now registers `SceneLoadWaiter` as a component on startup.
- `UnityEngine.UIModule` and `UnityEngine.PhysicsModule` references added to `DeadshotModAPI.csproj`.
- `.editorconfig` with comprehensive C# and project-wide style rules, including test-project overrides.
- `EnforceCodeStyleInBuild` enabled in `DeadshotModAPI.csproj`.
- `UniverseLib` explicit project reference added to `DeadshotModAPI.csproj`.

### Changed
- `SceneManager.LoadModsBundle()` is now `public` and delegates to `AssetBundleManager` instead of using a raw `AssetBundle` field.
- `AssetBundleManager` bundle root path changed from `BepInEx/mods` to `BepInEx/plugins/mods`.
- `AssetBundleManager.LoadAssetBundles()` now returns an empty `List<>` instead of `null` on individual bundle load failure.
- `Input.CheckKeys()` refactored to filter with `Where(IsPressed)` before iterating, removing the inner `if` branch.
- `Input.CheckKeys()` loop variables changed from `var` to explicit `Key[]` / `Key` types.
- `EventManager` level-time comparison changed from `==` to `< 0.001f` float epsilon check.
- `LevelCompleteScreen.SetTime()` simplified — redundant string interpolation removed.
- `Plugin.cs` `_harmony` static field removed; `Harmony` instance is now a local variable.
- `ModLoader.Start()` calls `SceneManager.LoadModsBundle()` on startup.
- `ModLoader` null-guard braces added for `types == null` and `type == null` checks.
- `SceneManager` using directives re-ordered and `Deadshot.Player` namespace added.

### Fixed
- Fixed `catch(Exception` missing space — normalised to `catch (Exception` throughout `EventManager.cs`.
- Fixed stray blank line inside `EventManager.LevelPlaytimeEvent()`.

## [1.0.0-dev-alpha] - 2026-09-28

### Added
Expand Down
13 changes: 13 additions & 0 deletions DeadshotModAPI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>disable</Nullable>
<DefaultItemExcludes>$(DefaultItemExcludes);DeadshotModAPI.Tests/**;examples/**</DefaultItemExcludes>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>

<ItemGroup>
Expand Down Expand Up @@ -65,6 +66,18 @@
<HintPath>lib\Il2CppSystem.dll</HintPath>
</Reference>

<Reference Include="UniverseLib">
<HintPath>lib\UniverseLib.BIE.IL2CPP.Interop.dll</HintPath>
</Reference>

<Reference Include="UnityEngine.UIModule">
<HintPath>lib\UnityEngine.UIModule.dll</HintPath>
</Reference>

<Reference Include="UnityEngine.PhysicsModule">
<HintPath>lib\UnityEngine.PhysicsModule.dll</HintPath>
</Reference>

</ItemGroup>

<ItemGroup>
Expand Down
28 changes: 9 additions & 19 deletions Events/EventManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,28 @@ namespace DeadshotModAPI;

public class EventManager : MonoBehaviour
{
public static void Update()
internal void Update()
{
LevelEvent.Update();
LevelEvent.LevelPlaytimeEvent();
}

public static class LevelEvent
{
private static float _lastLevelTime;

public static event Action<float> LevelTimeChanged;

public static event Action<LevelCompleteScreen> LevelCompleted;

internal static void Update()
{
LevelPlaytimeEvent();
}

internal static void LevelPlaytimeEvent()
{
try
{

if (DeadshotGameManager.INSTANCE == null)
{
Logger.Error("Cannot restart level: DeadshotGameManager.INSTANCE is null.");
return;
}


float levelTime = DeadshotGameManager.INSTANCE.CompletionTime;
if (levelTime == _lastLevelTime)
if (levelTime - _lastLevelTime < 0.001f)
{
return;
}
Expand All @@ -48,7 +38,7 @@ internal static void LevelPlaytimeEvent()

LevelTimeChanged?.Invoke(levelTime);
}
catch(Exception ex)
catch (Exception ex)
{
Logger.Error($"Error in LevelPlaytimeEvent(): {ex}");
}
Expand All @@ -64,15 +54,15 @@ public static float GetLevelPlaytime()
{
if (DeadshotGameManager.INSTANCE == null)
{
Logger.Error($"DeadshotGameManager.INSTANCE is null.");
return 0f;
}

return DeadshotGameManager.INSTANCE.CompletionTime;
}
catch(Exception ex)
catch (Exception ex)
{
Logger.Error($"Error in GetLevelPlaytime(): {ex}");
return 0;
return 0f;
}
}

Expand All @@ -82,7 +72,7 @@ internal static void LevelCompletedEvent(LevelCompleteScreen menu)
{
LevelCompleted?.Invoke(menu);
}
catch(Exception ex)
catch (Exception ex)
{
Logger.Error($"Error in LevelCompleted?.Invoke(): {ex}");
}
Expand All @@ -108,7 +98,7 @@ private static void OnEnable(LevelCompleteMenu __instance)

LevelEvent.LevelCompletedEvent(screen);
}
catch(Exception ex)
catch (Exception ex)
{
Logger.Error($"Error in LevelCompleteMenu.OnEnable postfix: {ex}");
}
Expand Down
7 changes: 6 additions & 1 deletion Events/LevelCompleteScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ internal LevelCompleteScreen(

public void SetTime(string text)
{
_menu.timeText.text = $"{text}";
_menu.timeText.text = text;
}

public void SetSecret(string text)
{
_menu.secretText.text = text;
}

// TODO: Add the rest
Expand Down
Loading
Loading