Skip to content

Commit 333887d

Browse files
committed
bring back detective
1 parent f3bda84 commit 333887d

File tree

8 files changed

+383
-0
lines changed

8 files changed

+383
-0
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using LaunchpadReloaded.Features;
2+
using LaunchpadReloaded.Modifiers;
3+
using LaunchpadReloaded.Options.Roles.Crewmate;
4+
using LaunchpadReloaded.Roles.Crewmate;
5+
using MiraAPI.GameOptions;
6+
using MiraAPI.Utilities.Assets;
7+
using System.Linq;
8+
using MiraAPI.Modifiers;
9+
using Rewired;
10+
using UnityEngine;
11+
12+
namespace LaunchpadReloaded.Buttons.Crewmate;
13+
14+
public class InstinctButton : BaseLaunchpadButton
15+
{
16+
public override string Name => "INSTINCT";
17+
public override float Cooldown => OptionGroupSingleton<DetectiveOptions>.Instance.InstinctCooldown;
18+
public override float EffectDuration => OptionGroupSingleton<DetectiveOptions>.Instance.InstinctDuration;
19+
public override int MaxUses => (int)OptionGroupSingleton<DetectiveOptions>.Instance.InstinctUses;
20+
public override KeyboardKeyCode Defaultkeybind => KeyboardKeyCode.F;
21+
public override LoadableAsset<Sprite> Sprite => LaunchpadAssets.InstinctButton;
22+
public override bool TimerAffectedByPlayer => true;
23+
public override bool AffectedByHack => true;
24+
25+
public override bool Enabled(RoleBehaviour? role)
26+
{
27+
return role is LpDetectiveRole;
28+
}
29+
30+
public override void OnEffectEnd()
31+
{
32+
foreach (var player in PlayerControl.AllPlayerControls.ToArray().Where(plr => plr.HasModifier<FootstepsModifier>()))
33+
{
34+
player.GetModifierComponent().RemoveModifier<FootstepsModifier>();
35+
}
36+
}
37+
38+
protected override void OnClick()
39+
{
40+
foreach (var player in PlayerControl.AllPlayerControls.ToArray().Where(plr => !plr.Data.IsDead))
41+
{
42+
player.GetModifierComponent().AddModifier<FootstepsModifier>();
43+
}
44+
}
45+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using LaunchpadReloaded.Components;
2+
using LaunchpadReloaded.Features;
3+
using LaunchpadReloaded.Roles.Crewmate;
4+
using LaunchpadReloaded.Utilities;
5+
using MiraAPI.Utilities;
6+
using MiraAPI.Utilities.Assets;
7+
using Rewired;
8+
using UnityEngine;
9+
using Helpers = MiraAPI.Utilities.Helpers;
10+
11+
namespace LaunchpadReloaded.Buttons.Crewmate;
12+
13+
public class InvestigateButton : BaseLaunchpadButton<DeadBody>
14+
{
15+
public override string Name => "INVESTIGATE";
16+
public override float Cooldown => 1;
17+
public override float EffectDuration => 0;
18+
public override int MaxUses => 0;
19+
public override KeyboardKeyCode Defaultkeybind => KeyboardKeyCode.V;
20+
public override LoadableAsset<Sprite> Sprite => LaunchpadAssets.InvestigateButton;
21+
public override float Distance => PlayerControl.LocalPlayer.MaxReportDistance / 4f;
22+
public override bool TimerAffectedByPlayer => true;
23+
public override bool AffectedByHack => true;
24+
25+
public override bool Enabled(RoleBehaviour? role)
26+
{
27+
return role is LpDetectiveRole;
28+
}
29+
30+
public override DeadBody? GetTarget()
31+
{
32+
return PlayerControl.LocalPlayer.GetNearestObjectOfType<DeadBody>(Distance, Helpers.CreateFilter(Constants.NotShipMask), "DeadBody", IsTargetValid);
33+
}
34+
35+
public override bool IsTargetValid(DeadBody? target)
36+
{
37+
return target != null && target.GetCacheComponent() is
38+
{
39+
hidden: false,
40+
};
41+
}
42+
43+
public override void SetOutline(bool active)
44+
{
45+
if (Target == null)
46+
{
47+
return;
48+
}
49+
50+
foreach (var renderer in Target.bodyRenderers)
51+
{
52+
renderer.UpdateOutline(active ? PlayerControl.LocalPlayer.Data.Role.NameColor : null);
53+
}
54+
}
55+
56+
protected override void OnClick()
57+
{
58+
if (Target == null)
59+
{
60+
return;
61+
}
62+
63+
var gameObject = Object.Instantiate(LaunchpadAssets.DetectiveGame.LoadAsset(), HudManager.Instance.transform);
64+
var minigame = gameObject.AddComponent<JournalMinigame>();
65+
minigame.Open(GameData.Instance.GetPlayerById(Target.ParentId).Object);
66+
67+
ResetTarget();
68+
}
69+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using LaunchpadReloaded.Modifiers;
2+
using Reactor.Utilities.Attributes;
3+
using System;
4+
using System.Linq;
5+
using MiraAPI.Modifiers;
6+
using TMPro;
7+
using UnityEngine;
8+
using UnityEngine.Events;
9+
10+
namespace LaunchpadReloaded.Components;
11+
12+
[RegisterInIl2Cpp]
13+
public class JournalMinigame(nint ptr) : Minigame(ptr)
14+
{
15+
public TextMeshPro deadPlayerInfo = null!;
16+
public PassiveButton closeButton = null!;
17+
public PassiveButton outsideButton = null!;
18+
public SpriteRenderer deadBodyIcon = null!;
19+
20+
private void Awake()
21+
{
22+
outsideButton = transform.FindChild("Background/OutsideCloseButton").GetComponent<PassiveButton>();
23+
closeButton = transform.FindChild("CloseButton").GetComponent<PassiveButton>();
24+
deadPlayerInfo = transform.FindChild("BodyInfo/DeadPlayerInfo").GetComponent<TextMeshPro>();
25+
deadBodyIcon = transform.FindChild("BodyInfo/Icon").GetComponent<SpriteRenderer>();
26+
27+
closeButton.OnClick.AddListener((UnityAction)(() => Close()));
28+
outsideButton.OnClick.AddListener((UnityAction)(() => Close()));
29+
}
30+
31+
public void Open(PlayerControl deadPlayer)
32+
{
33+
var deathData = deadPlayer.GetModifier<DeathData>();
34+
35+
if (deathData == null)
36+
{
37+
return;
38+
}
39+
40+
var suspectTemplate = gameObject.transform.FindChild("SuspectTemplate");
41+
var suspectsHolder = gameObject.transform.FindChild("Suspects");
42+
43+
var timeSinceDeath = DateTime.UtcNow.Subtract(deathData.DeathTime);
44+
deadPlayerInfo.text = timeSinceDeath.Minutes < 1 ? $"{deadPlayer.Data.PlayerName}\n<size=70%>Died {timeSinceDeath.Seconds} seconds ago</size>" :
45+
$"{deadPlayer.Data.PlayerName}\n<size=70%>Died {timeSinceDeath.Minutes} minutes ago</size>";
46+
47+
deadPlayer.SetPlayerMaterialColors(deadBodyIcon);
48+
49+
//if (GameManager.Instance.LogicFlow.GetPlayerCounts().Item1 < OptionGroupSingleton<DetectiveOptions>.Instance.SuspectCount - 1 || OptionGroupSingleton<DetectiveOptions>.Instance.HideSuspects)
50+
//{
51+
// suspectsHolder.gameObject.SetActive(false);
52+
// gameObject.transform.FindChild("BottomText").gameObject.SetActive(false);
53+
// gameObject.transform.FindChild("SuspectsTitle").GetComponent<TextMeshPro>().text = "Suspects cannot be shown.";
54+
// Begin(null);
55+
// return;
56+
//}
57+
58+
var rnd = new System.Random();
59+
foreach (var suspect in deathData.Suspects.OrderBy(_ => rnd.Next()))
60+
{
61+
var newTemplate = Instantiate(suspectTemplate, suspectsHolder);
62+
suspect.SetPlayerMaterialColors(newTemplate.GetComponent<SpriteRenderer>());
63+
newTemplate.gameObject.SetActive(true);
64+
newTemplate.transform.position = new Vector3(0, 0, -120);
65+
66+
var nameTag = newTemplate.transform.GetChild(0).GetComponent<TextMeshPro>();
67+
nameTag.text = suspect.Data.PlayerName;
68+
}
69+
70+
Begin(null);
71+
}
72+
}

LaunchpadReloaded/Events/GenericEvents.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@ namespace LaunchpadReloaded.Events;
2424

2525
public static class GenericEvents
2626
{
27+
[RegisterEvent]
28+
public static void AfterMurderEvent(AfterMurderEvent @event)
29+
{
30+
var suspects = PlayerControl.AllPlayerControls.ToArray()
31+
.Where(pc => pc != @event.Target && pc != @event.Source && !pc.Data.IsDead && pc.Data.Role is not LpDetectiveRole)
32+
.Take((int)OptionGroupSingleton<DetectiveOptions>.Instance.SuspectCount)
33+
.Append(@event.Source);
34+
35+
var deathData = new DeathData(DateTime.UtcNow, @event.Source, suspects);
36+
@event.Target.GetModifierComponent().AddModifier(deathData);
37+
}
38+
2739
[RegisterEvent]
2840
public static void SetRoleEvent(SetRoleEvent @event)
2941
{
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using MiraAPI.Modifiers;
2+
using System;
3+
using System.Collections.Generic;
4+
5+
namespace LaunchpadReloaded.Modifiers;
6+
7+
public class DeathData(DateTime deathTime, PlayerControl killer, IEnumerable<PlayerControl> suspects)
8+
: BaseModifier
9+
{
10+
public override string ModifierName => "DeathData";
11+
public override bool HideOnUi => true;
12+
13+
public DateTime DeathTime { get; } = deathTime;
14+
public PlayerControl Killer { get; } = killer;
15+
public IEnumerable<PlayerControl> Suspects { get; } = suspects;
16+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
using LaunchpadReloaded.Features;
2+
using LaunchpadReloaded.Options.Roles.Crewmate;
3+
using LaunchpadReloaded.Utilities;
4+
using MiraAPI.GameOptions;
5+
using MiraAPI.Modifiers;
6+
using Reactor.Utilities;
7+
using Reactor.Utilities.Extensions;
8+
using System.Collections;
9+
using System.Collections.Generic;
10+
using UnityEngine;
11+
12+
namespace LaunchpadReloaded.Modifiers;
13+
14+
public class FootstepsModifier : BaseModifier
15+
{
16+
public override string ModifierName => "Footsteps";
17+
18+
public override bool HideOnUi => true;
19+
20+
private Vector3 _lastPos;
21+
22+
private Dictionary<GameObject, SpriteRenderer> _currentSteps = null!;
23+
24+
public override void OnActivate()
25+
{
26+
_currentSteps = new Dictionary<GameObject, SpriteRenderer>();
27+
_lastPos = Player.transform.position;
28+
}
29+
30+
public override void OnDeactivate()
31+
{
32+
foreach (var obj in _currentSteps)
33+
{
34+
Coroutines.Start(FootstepFadeout(obj.Key, obj.Value));
35+
}
36+
}
37+
38+
private static IEnumerator FootstepFadeout(GameObject obj, SpriteRenderer rend)
39+
{
40+
yield return Helpers.FadeOut(rend, 0.0001f, 0.05f);
41+
obj.DestroyImmediate();
42+
}
43+
44+
private static IEnumerator FootstepDisappear(GameObject obj, SpriteRenderer rend)
45+
{
46+
yield return new WaitForSeconds(OptionGroupSingleton<DetectiveOptions>.Instance.FootstepsDuration);
47+
yield return FootstepFadeout(obj, rend);
48+
}
49+
50+
private bool _lastFlip;
51+
52+
public override void FixedUpdate()
53+
{
54+
if (Vector3.Distance(_lastPos, Player.transform.position) < 1)
55+
{
56+
return;
57+
}
58+
59+
var angle = Mathf.Atan2(Player.MyPhysics.Velocity.y, Player.MyPhysics.Velocity.x) * Mathf.Rad2Deg;
60+
61+
var footstep = new GameObject("Footstep")
62+
{
63+
transform =
64+
{
65+
parent = ShipStatus.Instance.transform,
66+
position = new Vector3(Player.transform.position.x, Player.transform.position.y, -2f),
67+
rotation = Quaternion.AngleAxis(angle - 90, Vector3.forward)
68+
}
69+
};
70+
71+
var sprite = footstep.AddComponent<SpriteRenderer>();
72+
sprite.sprite = LaunchpadAssets.Footstep.LoadAsset();
73+
sprite.material = LaunchpadAssets.GradientMaterial.LoadAsset();
74+
footstep.layer = LayerMask.NameToLayer("Players");
75+
76+
if (_lastFlip == false)
77+
{
78+
_lastFlip = true;
79+
sprite.flipX = true;
80+
}
81+
else
82+
{
83+
_lastFlip = false;
84+
sprite.flipX = false;
85+
}
86+
87+
sprite.transform.localScale = new Vector3(0.06f, 0.06f, 0.06f);
88+
Player.SetPlayerMaterialColors(sprite);
89+
90+
_currentSteps.Add(footstep, sprite);
91+
_lastPos = Player.transform.position;
92+
Coroutines.Start(FootstepDisappear(footstep, sprite));
93+
}
94+
95+
public override void OnDeath(DeathReason reason)
96+
{
97+
ModifierComponent!.RemoveModifier(this);
98+
}
99+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using LaunchpadReloaded.Roles.Crewmate;
2+
using MiraAPI.GameOptions;
3+
using MiraAPI.GameOptions.Attributes;
4+
using MiraAPI.Utilities;
5+
6+
namespace LaunchpadReloaded.Options.Roles.Crewmate;
7+
8+
public class DetectiveOptions : AbstractOptionGroup<LpDetectiveRole>
9+
{
10+
public override string GroupName => "Detective";
11+
12+
[ModdedToggleOption("Hide Suspects")]
13+
public bool HideSuspects { get; set; } = false;
14+
15+
[ModdedNumberOption("Suspect Count", 2, 8, 1, MiraNumberSuffixes.None)]
16+
public float SuspectCount { get; set; } = 4;
17+
18+
[ModdedNumberOption("Footsteps Duration", 1, 10, 1, MiraNumberSuffixes.Seconds)]
19+
public float FootstepsDuration { get; set; } = 3;
20+
21+
[ModdedNumberOption("Instinct Duration", 3, 76, 3, MiraNumberSuffixes.Seconds)]
22+
public float InstinctDuration { get; set; } = 18;
23+
24+
[ModdedNumberOption("Instinct Uses", 0, 10, zeroInfinity: true)]
25+
public float InstinctUses { get; set; } = 3;
26+
27+
[ModdedNumberOption("Instinct Cooldown", 0, 45, 1, MiraNumberSuffixes.Seconds)]
28+
public float InstinctCooldown { get; set; } = 15;
29+
30+
}

0 commit comments

Comments
 (0)