Skip to content

Commit e86a86b

Browse files
authored
Merge pull request #2 from SrLicht/13.1-update
* Added a command that allows you to spawn a SCP-575 to chase a specified player for any amount of time you want * Now the Audios folder is located inside the SCP-575 configuration folder. * Added a setting to loop the audio until the SCP-575 disappears. * Added a setting that if the player is running and is in the MediumDistance range, the speed of the 575 will increase. * Completely changed the way SCP-575 spawns by adding a Dummies API and other changes (you can copy it and use it in your plugins if you want, I don't care) * Recompiled to run in SCP:SL 13.1 * It is now possible to make it so that if the SCP-575 disappears before the end of the blackout, the blackout will end.
2 parents 28e2c76 + 8e34aa4 commit e86a86b

16 files changed

+738
-178
lines changed

Cerberus.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
<PropertyGroup>
1818
<!-- This is the global version and is used for all projects that don't have a version -->
19-
<Version Condition="$(Version) == ''">1.0.9</Version>
19+
<Version Condition="$(Version) == ''">1.1.0</Version>
2020
<!-- Enables public beta warning via the PUBLIC_BETA constant -->
2121
<PublicBeta>false</PublicBeta>
2222

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ This plugin only works for [NWAPI](https://github.com/northwood-studios/NwPlugin
1414

1515
This plugin uses as dependency [SCPSLAudioApi](https://github.com/CedModV2/SCPSLAudioApi)
1616

17+
# Dependency problem with SCPSLAudioApi
18+
Because of how NWAPI loads the plugin dependencies if more than one plugin installed uses SCPSLAudioApi it will give error and will not start the server, to solve this you will have to put ``NVorbis.dll`` in the dependencies folder.
19+
20+
**NVorbis.dll is included in a separate .zip file in the latest versions.**
21+
1722
# Sounds
1823
This plugin allows you to make the SCP-575 play sounds of your choice, the sound files must be placed in the ``%appdata%/SCP Secret Laboratory/PluginAPI/plugins/Scp575Sounds`` folder.
1924

SCP575/Command/Scp575Command.cs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
using CommandSystem;
2+
using Interactables.Interobjects.DoorUtils;
3+
using MapGeneration;
4+
using MEC;
5+
using PluginAPI.Core;
6+
using SCP575.Resources;
7+
using System;
8+
using System.Linq;
9+
using UnityEngine;
10+
using VoiceChat;
11+
12+
namespace SCP575.Command
13+
{
14+
[CommandHandler(typeof(RemoteAdminCommandHandler))]
15+
internal class Scp575Command : ICommand, IUsageProvider
16+
{
17+
public string Command => "scp575";
18+
19+
public string[] Aliases => new string[] { "575" };
20+
21+
public string Description => "This command allows you to spawn instances of SCP-575.";
22+
23+
public string[] Usage { get; } = { "Player ID", "Duration" };
24+
25+
public bool Execute(ArraySegment<string> arguments, ICommandSender sender, out string response)
26+
{
27+
if (sender != null)
28+
{
29+
if (!Round.IsRoundStarted)
30+
{
31+
response = Scp575.Instance.Config.CommandResponses.RoundHasNotStarted;
32+
return false;
33+
}
34+
// Display help response.
35+
if (arguments.Count < 1 || arguments.IsEmpty() || arguments.Count > 2)
36+
{
37+
var text = string.Format(Scp575.Instance.Config.CommandResponses.HelpResponse, this.DisplayCommandUsage());
38+
response = text;
39+
return false;
40+
}
41+
42+
if (!int.TryParse(arguments.At(0), out int playerId))
43+
{
44+
response = string.Format(Scp575.Instance.Config.CommandResponses.InvalidPlayerId, arguments.At(0));
45+
return false;
46+
}
47+
48+
var victim = Player.Get(playerId);
49+
if (victim is null)
50+
{
51+
response = Scp575.Instance.Config.CommandResponses.PlayerNotFound;
52+
return false;
53+
}
54+
55+
if (!int.TryParse(arguments.At(1), out var duration))
56+
{
57+
response = string.Format(Scp575.Instance.Config.CommandResponses.InvalidDuration, arguments.At(1));
58+
return false;
59+
}
60+
61+
SpawnScp575(victim, duration);
62+
63+
response = string.Format(Scp575.Instance.Config.CommandResponses.Spawning, victim.Nickname, duration);
64+
return true;
65+
}
66+
else
67+
{
68+
response = "Sender is null";
69+
return false;
70+
}
71+
}
72+
73+
public void SpawnScp575(Player victim, float duration)
74+
{
75+
var scp575 = Dummies.CreateDummy("Scp575", "SCP-575");
76+
77+
scp575.ReferenceHub.nicknameSync.ShownPlayerInfo &= ~PlayerInfoArea.Role;
78+
79+
scp575.ReferenceHub.nicknameSync.ViewRange = Scp575.Instance.Config.Scp575.ViewRange;
80+
81+
scp575.Nickname = Scp575.Instance.Config.Scp575.Nickname;
82+
83+
scp575.Role = Scp575.Instance.Config.Scp575.RoleType;
84+
85+
scp575.IsGodModeEnabled = true;
86+
87+
Timing.CallDelayed(0.3f, () =>
88+
{
89+
var room = victim.Room;
90+
91+
if (room.Name == RoomName.Lcz173)
92+
{
93+
scp575.Position = room.ApiRoom.Position + new Vector3(0f, 13.5f, 0f);
94+
}
95+
else if (room.Name == RoomName.HczTestroom)
96+
{
97+
if (DoorVariant.DoorsByRoom.TryGetValue(room, out var hashSet))
98+
{
99+
var door = hashSet.FirstOrDefault();
100+
if (door != null) scp575.Position = door.transform.position + Vector3.up;
101+
}
102+
}
103+
else
104+
{
105+
scp575.Position = room.ApiRoom.Position + Vector3.up;
106+
}
107+
});
108+
109+
var comp = scp575.GameObject.AddComponent<Resources.Components.Scp575Component>();
110+
comp.Victim = victim;
111+
comp.Destroy(duration);
112+
113+
if (!Scp575.Instance.Config.Scp575.PlaySounds) return;
114+
115+
if (!Extensions.AudioFileExist())
116+
Log.Error($"There is no .ogg file in the folder {Scp575.Instance.Config.PathToAudios}");
117+
scp575.AudioPlayerBase.LogDebug = Scp575.Instance.Config.AudioDebug;
118+
119+
var audioFile = Extensions.GetRandomAudioFile();
120+
121+
if (string.IsNullOrEmpty(audioFile))
122+
{
123+
Log.Error("AudioFile is null audio file no longer exists in the folder or something is broken");
124+
return;
125+
}
126+
127+
scp575.PlayAudio(audioFile, channel: VoiceChatChannel.RoundSummary, volume: Scp575.Instance.Config.Scp575.SoundVolume);
128+
Log.Debug($"Playing sound {audioFile}", Scp575.Instance.Config.Debug);
129+
}
130+
}
131+
}

SCP575/Config.cs

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
using System.Collections.Generic;
2-
using System.ComponentModel;
31
using MapGeneration;
42
using PlayerRoles;
3+
using System.Collections.Generic;
4+
using System.ComponentModel;
5+
using System.IO;
6+
using YamlDotNet.Serialization;
57

68
namespace SCP575
79
{
@@ -13,6 +15,9 @@ public class Config
1315
[Description("Enable the Logs.Debug of light points and other logs.")]
1416
public bool Debug { get; set; } = false;
1517

18+
[Description("Path to the folder where the audios are located")]
19+
public string PathToAudios { get; set; } = Path.Combine(ConfigPath, "Audios");
20+
1621
[Description("Enable the Logs.Debug of SCPSLAudioApi, warning can be very spammy.")]
1722
public bool AudioDebug { get; set; } = false;
1823

@@ -39,16 +44,22 @@ public class Config
3944

4045
[Description("All configuration related to the SCP-575")]
4146
public Scp575Config Scp575 { get; set; } = new Scp575Config();
47+
48+
[Description("Here you can translate the responses given by the command when executed, unfortunately due to NWAPI limitations I cannot give a configuration to change the command description.")]
49+
public ResponseCommandConfig CommandResponses { get; set; } = new ResponseCommandConfig();
50+
51+
[YamlIgnore]
52+
private static string ConfigPath => Path.Combine(PluginAPI.Helpers.Paths.LocalPlugins.Plugins, "SCP-575");
4253
}
4354

4455
public class BlackoutConfig
4556
{
4657
[Description("After this time, the constant blackouts will begin to be executed.")]
4758
public float InitialDelay { get; set; } = 300f;
48-
59+
4960
[Description("If this value is true initial_delay will be ignored and a calculation will be made between initial_max_delay and initial_min_delay which will result in the delay")]
5061
public bool RandomInitialDelay { get; set; } = false;
51-
62+
5263
[Description("The maximum time that the main delay can have")]
5364
public float InitialMaxDelay { get; set; } = 250f;
5465

@@ -67,13 +78,16 @@ public class BlackoutConfig
6778
[Description("The minimum duration of a delay after a blackout")]
6879
public int MaxDelay { get; set; } = 400;
6980

81+
[Description("If the SCP-575 disappears before the duration of the blackout should the blackout end?")]
82+
public bool EndBlackoutWhenDisappearing { get; set; } = true;
83+
7084
[Description("Before starting the blackout Cassie will say this message")]
7185
public string CassieMessage { get; set; } =
7286
"facility power system failure in 3 . pitch_.80 2 . pitch_.60 1 . pitch_.49 . .g1 pitch_.42 .g2 pitch_.31 .g5";
7387

7488
[Description("I have no idea what it does")]
7589
public bool CassieIsHold { get; set; } = false;
76-
90+
7791
[Description("Enable o disable bells in cassie announcement")]
7892
public bool CassieIsNoise { get; set; } = true;
7993

@@ -99,6 +113,9 @@ public class BlackoutConfig
99113

100114
public class Scp575Config
101115
{
116+
[Description("Enabling this will activate the patch that prevents the server from making the SCP-575 not float, causing a rather strange movement. I thought it was fun to leave it as an option")]
117+
public bool WeirdMovement { get; set; } = false;
118+
102119
[Description("The name the dummy will have")]
103120
public string Nickname { get; set; } = "SCP-575-B";
104121

@@ -121,6 +138,9 @@ public class Scp575Config
121138
"Should SCP-575 play the sounds files found in its folder? | The sound file must be .ogg need to be mono channel and have a frequency of 48000 Hz")]
122139
public bool PlaySounds { get; set; } = false;
123140

141+
[Description("The audio track replayed by the SCP-575 will loop until it is destroyed.")]
142+
public bool AudioIsLooped { get; set; } = false;
143+
124144
[Description("The volume of the sound to be reproduced by the SCP-575, high values violate the VSR.")]
125145
public float SoundVolume { get; set; } = 85f;
126146

@@ -135,8 +155,7 @@ public class Scp575Config
135155
"The maximum distance that SCP-575 can be from its victim, remember that it must be greater than 16")]
136156
public float MaxDistance { get; set; } = 28f;
137157

138-
[Description(
139-
"If the distance is equal to or greater than this value, the speed that is movement_speed_fast will be applied to the SCP-575.")]
158+
[Description("If the distance is equal to or greater than this value, the speed that is movement_speed_fast will be applied to the SCP-575.")]
140159
public float MediumDistance { get; set; } = 16f;
141160

142161
[Description(
@@ -155,6 +174,12 @@ public class Scp575Config
155174
"If the distance between SCP-575 and its victim is equal to or greater than 5, it will have this movement speed")]
156175
public float MovementSpeed { get; set; } = 22;
157176

177+
[Description("Enabling this setting if the victim is running and is in the MediumDistance range the 575 will move faster.")]
178+
public bool ChangeMovementSpeedIfRun { get; set; } = false;
179+
180+
[Description("At what speed will the SCP-575 move if the target is running if ChangeMovementSpeedIfRun is false this will not be used.")]
181+
public float MovementSpeedRunning { get; set; } = 25;
182+
158183
[Description(
159184
"This is complicated to explain, so I'll just tell you what I do in the code. If a player has a flashlight on and points it at SCP-575 I fire a ray of light that if it touches SCP-575 adds a point of light, when it reaches a certain point of light SCP-575 disappears. The coroutine that checks these points is executed every 0.1s.")]
160185
public int LightPoints { get; set; } = 85;
@@ -163,4 +188,19 @@ public class Scp575Config
163188
"When a player makes SCP-575 disappear using the LightPoints, this message will be sent to the player.")]
164189
public string LightPointKillMessage { get; set; } = "SCP-575 disappears for now";
165190
}
191+
192+
public class ResponseCommandConfig
193+
{
194+
public string RoundHasNotStarted { get; set; } = "You cannot use this command if the round has not started.";
195+
196+
public string InvalidPlayerId { get; set; } = "{0} is not an valid player id";
197+
public string PlayerNotFound { get; set; } = "Player not found";
198+
199+
public string InvalidDuration { get; set; } = "{0} is not an valid duration";
200+
201+
public string Spawning { get; set; } = "Spawning a SCP-575 to hunt {0} for {1} seconds";
202+
203+
[YamlMember(ScalarStyle = YamlDotNet.Core.ScalarStyle.DoubleQuoted)]
204+
public string HelpResponse { get; set; } = "Correct use of the command {0}\nPlayer ID | It is a numerical ID that changes with each new round and each time someone connects to the server again.\nDuration | The time (in seconds) that the SCP-575 will hunt someone\n\nNote that this command does not turn off the lights, so if the SCP-575 is in a lit room for more than 5 seconds it will disappear.";
205+
}
166206
}
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
using System.Collections.Generic;
2-
using System.Reflection.Emit;
31
using HarmonyLib;
42
using NorthwoodLib.Pools;
3+
using SCP575.Resources;
4+
using System.Collections.Generic;
5+
using System.Reflection.Emit;
56

67
namespace SCP575.Patch
78
{
@@ -22,12 +23,12 @@ private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstructi
2223

2324
newInstructions.InsertRange(0, new List<CodeInstruction>()
2425
{
25-
new(OpCodes.Ldsfld, AccessTools.Field(typeof(Scp575), nameof(Scp575.Dummies))),
26+
new(OpCodes.Ldsfld, AccessTools.Field(typeof(Dummies), nameof(Dummies.AllDummies))),
2627
new(OpCodes.Ldarg_0),
2728
new(OpCodes.Ldfld,
2829
AccessTools.Field(typeof(CharacterClassManager), nameof(CharacterClassManager._hub))),
2930
new(OpCodes.Callvirt,
30-
AccessTools.Method(typeof(List<ReferenceHub>), nameof(List<ReferenceHub>.Contains))),
31+
AccessTools.Method(typeof(HashSet<ReferenceHub>), nameof(HashSet<ReferenceHub>.Contains))),
3132
new(OpCodes.Brfalse_S, skip),
3233
new(OpCodes.Ldc_I4_2),
3334
new(OpCodes.Starg_S, 1),
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
using System.Collections.Generic;
2-
using System.Reflection.Emit;
31
using HarmonyLib;
42
using NorthwoodLib.Pools;
53
using PlayerRoles.FirstPersonControl;
64
using SCP575.Resources;
5+
using System.Collections.Generic;
6+
using System.Reflection.Emit;
77

88
namespace SCP575.Patch
99
{
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace SCP575.Patch
2+
{
3+
/*[HarmonyPatch(typeof(CharacterClassManager), nameof(CharacterClassManager.InstanceMode), MethodType.Setter)]
4+
public class InstanceModeSetterPatch
5+
{
6+
public static bool Prefix(CharacterClassManager __instance)
7+
=> !Extensions.IsDummy(__instance._hub);
8+
}*/
9+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using HarmonyLib;
2+
using PlayerRoles.FirstPersonControl;
3+
using SCP575.Resources;
4+
5+
namespace SCP575.Patch
6+
{
7+
[HarmonyPatch(typeof(FpcMotor), nameof(FpcMotor.UpdateFloating))]
8+
public class UpdateFloating
9+
{
10+
public static bool Prefix(FpcMotor __instance)
11+
{
12+
if (Scp575.Instance.Config.Scp575.WeirdMovement && Extensions.IsDummy(__instance.Hub))
13+
{
14+
return false;
15+
}
16+
17+
return true;
18+
}
19+
}
20+
}

0 commit comments

Comments
 (0)