diff --git a/.github/workflows/block-pr-to-master.yml b/.github/workflows/block-pr-to-master.yml
new file mode 100644
index 00000000..bbc1ad35
--- /dev/null
+++ b/.github/workflows/block-pr-to-master.yml
@@ -0,0 +1,37 @@
+name: Block external PRs to master
+on:
+ pull_request_target:
+ branches: [ "master" ]
+ types: [opened, reopened]
+
+permissions:
+ pull-requests: write
+ issues: write
+
+jobs:
+ close-pr:
+ if: >
+ github.event.pull_request.author_association != 'OWNER' &&
+ github.event.pull_request.author_association != 'MEMBER' &&
+ github.event.pull_request.author_association != 'COLLABORATOR'
+ runs-on: ubuntu-latest
+ steps:
+ - name: Debug - Afficher le statut
+ run: echo "Le statut de l'auteur de cette PR est ${{ github.event.pull_request.author_association }}"
+
+ - name: Close the PR
+ uses: actions/github-script@v6
+ with:
+ script: |
+ github.rest.issues.createComment({
+ issue_number: context.issue.number,
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ body: If you wish to contribute, please target the `dev` branch.'
+ })
+ github.rest.pulls.update({
+ pull_number: context.issue.number,
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ state: 'closed'
+ })
diff --git a/.github/workflows/buildtoserver.yml b/.github/workflows/buildtoserver.yml
new file mode 100644
index 00000000..42b086c5
--- /dev/null
+++ b/.github/workflows/buildtoserver.yml
@@ -0,0 +1,53 @@
+name: Build .NET Solution
+
+on:
+ push:
+ branches: [ "master" ]
+ pull_request:
+ branches: [ "master" ]
+ workflow_dispatch:
+
+env:
+ OUTPUT_FOLDER_PATH: /home/grosserv/.config/EXILED/Plugins/
+ EXILED_REFERENCES: ${{ github.workspace }}/references
+ EXILED_DEV_REFERENCES: ${{ github.workspace }}/devreferences
+ SERVER_LOCATION: /home/grosserv/SCPSL/
+ OTHER_DEPENDENCIES: /home/grosserv/OtherDependencies/
+jobs:
+ build:
+ runs-on: self-hosted
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Restore NuGet packages
+ run: |
+ SLN=$(find . -name "*.sln" | head -n 1)
+ nuget restore "$SLN"
+
+
+ - name : Fill Exiled References
+ run: |
+ mkdir ${{ env.EXILED_REFERENCES }}
+ mkdir ${{ env.EXILED_DEV_REFERENCES }}
+ find ${{env.SERVER_LOCATION}}/SCPSL_Data/Managed/ -name "*.dll" | xargs cp -t ${{env.EXILED_REFERENCES}}
+ find ${{env.OTHER_DEPENDENCIES}} -name "*.dll" | xargs cp -t ${{env.EXILED_REFERENCES}}
+
+ - name: Build solution
+ run: |
+ export EXILED_REFERENCES=${{ env.EXILED_REFERENCES }}
+ export EXILED_DEV_REFERENCES=${{ env.EXILED_DEV_REFERENCES }}
+ SLN=$(find . -name "*.sln" | head -n 1)
+ msbuild "$SLN" /p:Configuration=Release
+
+ - name: Copy plugin to server
+ run: |
+ DLL=$(find . -path "*/bin/Release/*KruacentExiled.dll" | head -n 1)
+
+ if [ -z "$DLL" ]; then
+ echo "DLL not found!"
+ exit 1
+ fi
+ cp "$DLL" $OUTPUT_FOLDER_PATH/
+
diff --git a/.gitignore b/.gitignore
index 344f050a..ec239c70 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,7 +32,7 @@ bld/
[Oo]ut/
[Ll]og/
[Ll]ogs/
-
+lib
# Visual Studio 2015/2017 cache/options directory
diff --git a/KruacentE.BlackoutNDoor/API/Features/Controller.cs b/KruacentE.BlackoutNDoor/API/Features/Controller.cs
deleted file mode 100644
index d5b03951..00000000
--- a/KruacentE.BlackoutNDoor/API/Features/Controller.cs
+++ /dev/null
@@ -1,182 +0,0 @@
-using Exiled.API.Enums;
-using Exiled.API.Features;
-using Exiled.API.Features.Doors;
-using MEC;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace BlackoutKruacent.API.Features
-{
- internal class Controller
- {
- ///
- /// Select a random zone and close and lock all door of the zone
- ///
- public IEnumerator RandomDoorStuck()
- {
- yield return Timing.WaitForOneFrame;
- var zone = SelectZone();
- Log.Debug($"DoorStuck in {zone}");
- yield return Timing.WaitUntilFalse(() => Cassie.IsSpeaking);
- CassieVoiceLine(zone, false);
- yield return Timing.WaitUntilFalse(() => Cassie.IsSpeaking);
- yield return Timing.WaitForSeconds(5);
- // if the zone is light and there is only 30s left then skip
- if (!(zone == ZoneType.LightContainment && Map.DecontaminationState == DecontaminationState.Countdown))
- {
- List doorList = Door.List
- .Where(d => !new[] { DoorType.ElevatorGateA, DoorType.ElevatorGateB, DoorType.ElevatorLczA, DoorType.ElevatorLczB, DoorType.ElevatorNuke, DoorType.ElevatorScp049, DoorType.UnknownElevator }.Contains(d.Type))
- .ToList();
- foreach (Door door in doorList)
- {
- if (door.Zone == zone)
- {
- door.IsOpen = false;
- door.ChangeLock(DoorLockType.Lockdown2176);
- Timing.CallDelayed(MainPlugin.Instance.Config.DurationMalfunction / 2, () => door.IsOpen = true);
- Timing.CallDelayed(MainPlugin.Instance.Config.DurationMalfunction, () => door.Unlock());
- if (UnityEngine.Random.value < .5f)
- {
- door.IsOpen = false;
- }
- }
- }
- }
- }
-
- ///
- /// Select a random zone and blackout it
- ///
- public IEnumerator RandomBlackout()
- {
- yield return Timing.WaitForOneFrame;
-
- var zone = SelectZone();
- yield return Timing.WaitUntilFalse(() => Cassie.IsSpeaking);
- CassieVoiceLine(zone, true);
- yield return Timing.WaitUntilFalse(() => Cassie.IsSpeaking);
- yield return Timing.WaitForSeconds(5);
- Log.Debug($"BlackOut in {zone}");
-
-
- Map.TurnOffAllLights(MainPlugin.Instance.Config.DurationMalfunction, zone);
- }
-
-
- private void CassieVoiceLine(ZoneType zone,bool isBlackout)
- {
- if (isBlackout)
- {
- switch (zone)
- {
- case ZoneType.LightContainment:
- Cassie.MessageTranslated("Warning Failure Of All Lights In Light Containment Zone in 5 seconds",
- "Warning Failure Of All Lights In Light Containment Zone in 5 seconds", false, false);
- break;
- case ZoneType.HeavyContainment:
- Cassie.MessageTranslated("Warning Failure Of All Lights In Heavy Containment Zone in 5 seconds",
- "Warning Failure Of All Lights In Heavy Containment Zone in 5 seconds", false, false);
- break;
- case ZoneType.Entrance:
- Cassie.MessageTranslated("Warning Failure Of All Lights In Entrance Zone in 5 seconds",
- "Warning Failure Of All Lights In Entrance Zone in 5 seconds", false, false);
- break ;
- case ZoneType.Surface:
- Cassie.MessageTranslated("Warning Failure Of All Lights In Surface in 5 seconds",
- "Warning Failure Of All Lights In Surface in 5 seconds", false, false);
- break ;
- case ZoneType.Unspecified:
- Cassie.MessageTranslated("Warning Failure Of All Lights In All Of The Facility in 5 seconds",
- "Warning Failure Of All Lights In All Of The Facility in 5 seconds", false, false);
- break;
- }
- }
- else
- {
- switch (zone)
- {
- case ZoneType.LightContainment:
- Cassie.MessageTranslated("Door system malfunction in Light Containment Zone in 5 seconds",
- "Door system malfunction in Light containment zone in 5 seconds", false,false);
- break;
- case ZoneType.HeavyContainment:
- Cassie.MessageTranslated("Door system malfunction in Heavy Containment Zone in 5 seconds",
- "Door system malfunction in Heavy zone in 5 seconds", false, false);
- break;
- case ZoneType.Entrance:
- Cassie.MessageTranslated("Door system malfunction in Entrance zone in 5 seconds",
- "Door system malfunction in Entrance zone in 5 seconds", false, false);
- break;
- case ZoneType.Surface:
- Cassie.MessageTranslated("Door system malfunction in Surface Zone in 5 seconds",
- "Door system malfunction in Surface zone in 5 seconds", false, false);
- break;
- case ZoneType.Unspecified:
- Cassie.MessageTranslated("Door system malfunction in All of the facility in 5 seconds",
- "Door system malfunction in All of the facility in 5 seconds", false, false);
- break;
- }
- }
- }
-
- ///
- /// Select a random zone
- /// If the nuke is detonated return only SurfaceZone
- ///
- /// a zone
- private ZoneType SelectZone()
- {
- ZoneType z = ZoneType.Unspecified;
- float random = UnityEngine.Random.value;
- if (Warhead.IsDetonated)
- {
- z= ZoneType.Surface;
- }
- if (!Map.IsLczDecontaminated)
- {
- z= GetZoneType(GetProbabilityIndex(MainPlugin.Instance.Config.ChancePreConta, random));
- }
- else
- {
- z= GetZoneType(GetProbabilityIndex(MainPlugin.Instance.Config.ChancePostConta, random));
- }
- Log.Debug($"zone={z}");
- return z;
- }
-
- private int GetProbabilityIndex(float[] probabilities, float randomValue)
- {
- float cumulative = 0f;
-
- for (int i = 0; i < probabilities.Length; i++)
- {
- cumulative += probabilities[i];
- if (randomValue <= cumulative)
- {
- return i;
- }
- }
-
- throw new ArgumentException("Random value is outside the range of probabilities.");
- }
-
- private ZoneType GetZoneType(int index)
- {
- switch (index)
- {
- case 0:
- return ZoneType.LightContainment;
- case 1:
- return ZoneType.HeavyContainment;
- case 2:
- return ZoneType.Entrance;
- case 3:
- return ZoneType.Surface;
- default:
- case 4:
- return ZoneType.Unspecified;
- }
- }
- }
-}
diff --git a/KruacentE.BlackoutNDoor/BlackoutKruacent.csproj b/KruacentE.BlackoutNDoor/BlackoutKruacent.csproj
deleted file mode 100644
index 3fa6f206..00000000
--- a/KruacentE.BlackoutNDoor/BlackoutKruacent.csproj
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
- net48
-
-
-
-
-
-
-
-
- C:\Program Files (x86)\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Assembly-CSharp-firstpass.dll
-
-
- C:\Program Files (x86)\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\System.ComponentModel.Composition.dll
-
-
- C:\Program Files (x86)\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.CoreModule.dll
-
-
-
-
diff --git a/KruacentE.BlackoutNDoor/BlackoutKruacent.sln b/KruacentE.BlackoutNDoor/BlackoutKruacent.sln
deleted file mode 100644
index 53bbd945..00000000
--- a/KruacentE.BlackoutNDoor/BlackoutKruacent.sln
+++ /dev/null
@@ -1,22 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.12.35506.116 d17.12
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlackoutKruacent", "BlackoutKruacent.csproj", "{347FC6BF-9742-4687-A244-85028B70D158}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {347FC6BF-9742-4687-A244-85028B70D158}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {347FC6BF-9742-4687-A244-85028B70D158}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {347FC6BF-9742-4687-A244-85028B70D158}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {347FC6BF-9742-4687-A244-85028B70D158}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
-EndGlobal
diff --git a/KruacentE.BlackoutNDoor/Config.cs b/KruacentE.BlackoutNDoor/Config.cs
deleted file mode 100644
index b0d43fc4..00000000
--- a/KruacentE.BlackoutNDoor/Config.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using Exiled.API.Interfaces;
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace BlackoutKruacent
-{
- public class Config : IConfig
- {
- public bool IsEnabled { get; set; } = true;
- public bool Debug { get; set; } = false;
-
- [Description("the minimum interval between 2 malfunctions")]
- public int MinInterval { get; set; } = 300;
- [Description("the maximum interval between 2 malfunctions")]
- public int MaxInterval { get; set; } = 600;
- [Description("chance of having a Blackout at the start of the game")]
- public double InitialChanceBO { get; set; } = 0.5;
- [Description("the duration of a malfunction")]
- public int DurationMalfunction { get; set; } = 30;
-
- public float[] ChancePreConta = { .2f, .3f, .3f, .15f, .05f };
- public float[] ChancePostConta = { 0, .4f, .4f, .15f, .05f };
- }
-}
diff --git a/KruacentE.BlackoutNDoor/Handlers/ServerHandler.cs b/KruacentE.BlackoutNDoor/Handlers/ServerHandler.cs
deleted file mode 100644
index 6984114f..00000000
--- a/KruacentE.BlackoutNDoor/Handlers/ServerHandler.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-using BlackoutKruacent.API.Features;
-using Exiled.API.Features;
-using Exiled.Events.EventArgs.Server;
-using MEC;
-using PluginAPI.Events;
-using System.Collections.Generic;
-
-namespace BlackoutKruacent.Handlers
-{
- internal class ServerHandler
- {
- internal double ChanceBO { get; set; } = MainPlugin.Instance.Config.InitialChanceBO;
- private Controller controller;
- internal ServerHandler(Controller con)
- {
- controller = con;
- }
- public void OnRoundStarted()
- {
- Timing.RunCoroutine(Update());
- }
-
- private IEnumerator Update()
- {
- Log.Debug("startUpdate");
- var wait = UnityEngine.Random.Range(MainPlugin.Instance.Config.MinInterval, MainPlugin.Instance.Config.MaxInterval);
- Log.Debug($"waiting for {wait}");
- yield return Timing.WaitForSeconds(wait);
- while (true)
- {
- var a = UnityEngine.Random.value;
- Log.Debug("random =" + a);
- if (a <= ChanceBO)
- {
- Log.Debug("BlackOut");
- CoroutineHandle coroutine = Timing.RunCoroutine(controller.RandomBlackout());
- yield return Timing.WaitUntilDone(coroutine);
- }
- else
- {
- Log.Debug("DoorStuck");
- CoroutineHandle coroutine = Timing.RunCoroutine(controller.RandomDoorStuck());
- yield return Timing.WaitUntilDone(coroutine);
- }
- wait = UnityEngine.Random.Range(MainPlugin.Instance.Config.MinInterval, MainPlugin.Instance.Config.MaxInterval);
- Log.Debug($"waiting : {wait}");
- yield return Timing.WaitForSeconds(wait);
- yield return Timing.WaitUntilFalse(() => Warhead.IsInProgress);
-
- ChanceBO = -(1 / 60) * Round.ElapsedTime.TotalMinutes + 0.5;
- Log.Debug($"new ChanceBO = {ChanceBO}");
- }
- Log.Debug("end");
- }
-
- }
-}
diff --git a/KruacentE.BlackoutNDoor/MainPlugin.cs b/KruacentE.BlackoutNDoor/MainPlugin.cs
deleted file mode 100644
index 757224a1..00000000
--- a/KruacentE.BlackoutNDoor/MainPlugin.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using BlackoutKruacent.API.Features;
-using BlackoutKruacent.Handlers;
-using Exiled.API.Features;
-using System.ComponentModel;
-using Server = Exiled.Events.Handlers.Server;
-
-namespace BlackoutKruacent
-{
- public class MainPlugin : Plugin
- {
- internal static MainPlugin Instance;
- private Controller _c;
- private ServerHandler _server;
-
- public override void OnEnabled()
- {
- Instance = this;
- _c = new Controller();
- this.RegisterEvent();
- }
- public override void OnDisabled()
- {
-
- Instance = null;
- _c = null;
- this.UnregisterEvent();
- }
-
- private void RegisterEvent()
- {
- _server = new ServerHandler(_c);
- Server.RoundStarted += _server.OnRoundStarted;
-
- }
- private void UnregisterEvent()
- {
- Server.RoundStarted -= _server.OnRoundStarted;
-
- _server = null;
- }
-
-
-
-
-
- }
-}
diff --git a/KruacentE.GlobalEventFramework/Config.cs b/KruacentE.GlobalEventFramework/Config.cs
deleted file mode 100644
index 8ac73e5b..00000000
--- a/KruacentE.GlobalEventFramework/Config.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using Exiled.API.Interfaces;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace GEFExiled
-{
- internal class Config : IConfig
- {
-
- public bool IsEnabled { get; set; } = true;
- public bool Debug { get; set; } = true;
-
-
-
-
-
- }
-}
diff --git a/KruacentE.GlobalEventFramework/GEFE.Examples/Config.cs b/KruacentE.GlobalEventFramework/GEFE.Examples/Config.cs
deleted file mode 100644
index 04bcd01f..00000000
--- a/KruacentE.GlobalEventFramework/GEFE.Examples/Config.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using Exiled.API.Interfaces;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace GEFExiled.GEFE.Examples
-{
- public class Config : IConfig
- {
- public bool IsEnabled { get; set; } = true;
- public bool Debug { get; set; } = true;
- }
-}
diff --git a/KruacentE.GlobalEventFramework/GEFE.Examples/GE/Shuffle.cs b/KruacentE.GlobalEventFramework/GEFE.Examples/GE/Shuffle.cs
deleted file mode 100644
index 256b3c0d..00000000
--- a/KruacentE.GlobalEventFramework/GEFE.Examples/GE/Shuffle.cs
+++ /dev/null
@@ -1,80 +0,0 @@
-using System.Collections.Generic;
-using Exiled.API.Features;
-using GlobalEventFrameworkEXILED.API.Utils;
-using InventorySystem;
-using System.Linq;
-using Exiled.API.Features.Items;
-using Exiled.API.Extensions;
-using Exiled.API.Enums;
-using InventorySystem.Items.Firearms.Attachments.Components;
-using Exiled.API.Structs;
-using MEC;
-using GEFExiled.GEFE.API.Features;
-
-namespace GEFExiled.GEFE.Examples.GE
-{
- public class Shuffle : GlobalEvent
- {
- ///
- public override int Id { get; set; } = 31;
- ///
- public override string Name { get; set; } = "Shuffle";
- ///
- public override string Description { get; set; } = "et ça fait roomba café dans le scp";
- ///
- public override double Weight { get; set; } = 1;
- private Player[] players = Player.List.ToArray();
- private List- [] inventories;
- ///
- public override IEnumerator Start()
- {
- Coroutine.LaunchCoroutine(Update());
- yield return 0;
- }
-
- public Shuffle()
- {
- inventories = new List
- [players.Length];
- for (int i = 0; i < players.Length; i++)
- {
- inventories[i] = players[i].Items.ToList();
- }
- }
- private IEnumerator Update()
- {
- while (!Round.IsEnded)
- {
- yield return Timing.WaitForSeconds(UnityEngine.Random.Range(120, 240));
-
-
- }
- }
- //sprainte 9 fèr lé non ki rest
- private void Shuffling(int decale)
- {
- for (int i = 0; i < players.Length; i++)
- {
-
- }
- }
-
- private void ChangeInventory(int pid)
- {
- var fire = new Dictionary>();
- players[pid].ClearItems();
- foreach (Item item in inventories[pid])
- {
- //is a firearm
- if (item is Firearm firearm)
- {
- fire.Add(firearm, firearm.AttachmentIdentifiers);
- players[pid].AddItem(fire);
- }
- else
- {
- players[pid].AddItem(item);
- }
- }
- }
- }
-}
diff --git a/KruacentE.GlobalEventFramework/GEFE.Examples/GE/Speed.cs b/KruacentE.GlobalEventFramework/GEFE.Examples/GE/Speed.cs
deleted file mode 100644
index a5697b44..00000000
--- a/KruacentE.GlobalEventFramework/GEFE.Examples/GE/Speed.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using Exiled.API.Enums;
-using Exiled.API.Features;
-using GEFExiled.GEFE.API.Features;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace GEFExiled.GEFE.Examples.GE
-{
- public class Speed : GlobalEvent
- {
- public override int Id { get; set; } = 30;
- public override string Name { get; set; } = "Speed";
- public override string Description { get; set; } = "Gas! gas! gas!";
- public override double Weight { get; set; } = 1;
-
- public override IEnumerator Start()
- {
-
- yield return 0;
- }
-
-
- public override void SubscribeEvent()
- {
- Exiled.Events.Handlers.Server.RoundStarted += OnRoundStarted;
- Exiled.Events.Handlers.Server.RespawnedTeam +=;
- }
-
- public override void UnsubscribeEvent()
- {
- Exiled.Events.Handlers.Server.RoundStarted -= OnRoundStarted;
- }
-
- private void OnRoundStarted()
- {
- foreach(Player p in Player.List)
- {
- p.EnableEffect(EffectType.MovementBoost,100,99999999f);
- }
- }
-
- }
-}
diff --git a/KruacentE.GlobalEventFramework/GEFE.Examples/GE/SystemMalfunction.cs b/KruacentE.GlobalEventFramework/GEFE.Examples/GE/SystemMalfunction.cs
deleted file mode 100644
index 3cd247f6..00000000
--- a/KruacentE.GlobalEventFramework/GEFE.Examples/GE/SystemMalfunction.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using Exiled.API.Features;
-using GEFExiled.GEFE.API.Features;
-using GlobalEventFrameworkEXILED.API.Utils;
-using MEC;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace GEFExiled.GEFE.Examples.GE
-{
-
- public class SystemMalfunction : GlobalEvent
- {
- public override int Id { get; set; } = 1;
- public override string Name { get; set; } = "System Malfunction";
- public override string Description { get; set; } = "On dirait que les systèmes informatiques sont défaillants";
- public override double Weight { get; set; } = 1;
-
- public override IEnumerator Start()
- {
- Coroutine.LaunchCoroutine(EarlyNuke());
- yield return 0;
- }
-
- private IEnumerator EarlyNuke()
- {
- int timeNuke = UnityEngine.Random.Range(15, 30);
- Log.Debug($"the nuke will detonate in {timeNuke}min");
- yield return Timing.WaitUntilTrue(() => timeNuke == Round.ElapsedTime.TotalMinutes);
- Warhead.Start();
- Log.Debug($"kaboom");
- }
- }
-}
diff --git a/KruacentE.GlobalEventFramework/GEFE.Examples/MainPlugin.cs b/KruacentE.GlobalEventFramework/GEFE.Examples/MainPlugin.cs
deleted file mode 100644
index 70832aef..00000000
--- a/KruacentE.GlobalEventFramework/GEFE.Examples/MainPlugin.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using Exiled.API.Features;
-using MEC;
-using GEFExiled.GEFE.API.Features;
-
-namespace GEFExiled.GEFE.Examples
-{
- public class MainPlugin : Plugin
- {
-
- public override void OnEnabled()
- {
-
-
-
- Timing.CallDelayed(10f, () => {
- GlobalEvent.RegisterItems();
- });
- base.OnEnabled();
- }
- }
-}
diff --git a/KruacentE.GlobalEventFramework/GEFE/API/Features/GlobalEvent.cs b/KruacentE.GlobalEventFramework/GEFE/API/Features/GlobalEvent.cs
deleted file mode 100644
index 7c202975..00000000
--- a/KruacentE.GlobalEventFramework/GEFE/API/Features/GlobalEvent.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-using Exiled.API.Features;
-using System.Collections.Generic;
-using System.Linq;
-using MEC;
-using GEFExiled.GEFE.API.Interfaces;
-using Exiled.CustomItems.API.Features;
-using System.Reflection;
-using Exiled.API.Features.Attributes;
-using Exiled.API.Interfaces;
-using System.Collections;
-using System;
-using Exiled.API.Extensions;
-using Exiled.API.Features.Pools;
-
-namespace GEFExiled.GEFE.API.Features
-{
- public class GlobalEvent : IGlobalEvent
- {
- ///
- /// A list of Active GlobalEvents
- ///
- public static List ActiveGlobalEvents => ActiveGE.ToList();
- internal static List ActiveGE { get; } = new List();
- internal static Dictionary GlobalEvents { get; set; } = new Dictionary();
- ///
- /// A list of all registered GlobalEvents
- ///
- public static List GlobalEventsList => GlobalEvents.Values.ToList();
- public static HashSet Registered { get; } = new HashSet();
- public virtual int Id { get; set; } = -1;
- public virtual string Name { get; set; } = "GE NOT SET";
- public virtual string Description { get; set; } = "DESC NOT SET";
- public virtual double Weight { get; set; } = 1;
-
- public static void Register(IGlobalEvent globalEvent)
- {
- Log.Debug("REGISTERING" + globalEvent.Name);
- if (GlobalEvents.ContainsKey(globalEvent.Id))
- {
- Log.Warn($"id already used for {GlobalEvents.TryGetValue(globalEvent.Id, out IGlobalEvent geAlready)}");
- Log.Warn("Trying to attribute a new id...");
- int key = 0;
- while (GlobalEvents.ContainsKey(key))
- {
- key++;
- }
- globalEvent.Id = key;
- Log.Warn($"new id of {globalEvent.Name} : {globalEvent.Id}");
- }
- GlobalEvents.Add(globalEvent.Id, globalEvent);
- Log.Info($"{globalEvent.Name} is registered");
-
- }
-
- public virtual IEnumerator Start()
- {
- Log.Error($"{GetType().Name} Start is NOT overrided");
- yield return Timing.WaitForSeconds(30f);
- }
-
- public virtual void SubscribeEvent()
- {
- Log.Error($"{GetType().Name} SubscribeEvent is NOT overrided");
- }
- public virtual void UnsubscribeEvent()
- {
- Log.Error($"{GetType().Name} UnsubscribeEvent is NOT overrided");
- }
- }
-}
diff --git a/KruacentE.GlobalEventFramework/GEFE/API/Interfaces/IGlobalEvent.cs b/KruacentE.GlobalEventFramework/GEFE/API/Interfaces/IGlobalEvent.cs
deleted file mode 100644
index 11c0a535..00000000
--- a/KruacentE.GlobalEventFramework/GEFE/API/Interfaces/IGlobalEvent.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using MEC;
-
-namespace GEFExiled.GEFE.API.Interfaces
-{
- public interface IGlobalEvent
- {
- ///
- /// the UNIQUE id of the Global Event
- ///
- int Id { get; set; }
- ///
- /// Name used in the logs on the RA
- ///
- string Name { get; set; }
-
- ///
- /// The description that will be shown to the player when the round start
- ///
- string Description { get; set; }
-
- ///
- /// The chance this GE will be choosed at the start of a round
- ///
- double Weight { get; set; }
-
- ///
- /// Is launched at the start of a round
- ///
- IEnumerator Start();
-
- void SubscribeEvent();
-
- void UnsubscribeEvent();
- }
-}
diff --git a/KruacentE.GlobalEventFramework/GEFE/API/Utils/Coroutine.cs b/KruacentE.GlobalEventFramework/GEFE/API/Utils/Coroutine.cs
deleted file mode 100644
index 2d17f72f..00000000
--- a/KruacentE.GlobalEventFramework/GEFE/API/Utils/Coroutine.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using MEC;
-using System.Collections.Generic;
-
-namespace GEFExiled.API.Utils
-{
- public static class Coroutine
- {
- public static readonly List _coroutine = new List();
-
- //CoroutineHandle coroutine
- public static CoroutineHandle LaunchCoroutine(IEnumerator coroutine)
- {
- CoroutineHandle a = Timing.RunCoroutine(coroutine);
-
- _coroutine.Add(a);
- return a;
- }
- }
-}
diff --git a/KruacentE.GlobalEventFramework/GEFE/Commands/List.cs b/KruacentE.GlobalEventFramework/GEFE/Commands/List.cs
deleted file mode 100644
index 0aba91bf..00000000
--- a/KruacentE.GlobalEventFramework/GEFE/Commands/List.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-namespace KruacentExiled.KruacentE.GlobalEventFramework.GEFE.Commands
-{
- using Exiled.API.Features;
- using Exiled.API.Features.Pickups;
- using System;
- using CommandSystem;
- using System.Runtime.InteropServices.WindowsRuntime;
- using GEFExiled.GEFE.API.Features;
- using GEFExiled.GEFE.API.Interfaces;
-
- public class List : ICommand
- {
- public string Command { get; } = "list";
- public string[] Aliases { get; } = new string[] { "l", "ls" };
- public string Description { get; } = "get the list of all Global Events";
-
- public bool Execute(ArraySegment arguments, ICommandSender sender, out string response)
- {
- string result = "List of all global event ([o] if it's active in the round ; [ ] otherwise) : ";
- foreach (IGlobalEvent ge in GlobalEvent.GlobalEvents.Values)
- {
-
- if (GlobalEvent.ActiveGlobalEvents.Contains(ge))
- {
- result += "[o]";
- }
- else
- {
- result += "[ ]";
- }
- result += $" {ge.Id} : {ge.Name} : {ge.Description}";
- }
- response = result;
- return true;
- }
- }
-}
diff --git a/KruacentE.GlobalEventFramework/GEFE/Commands/ParentCommandGEFE.cs b/KruacentE.GlobalEventFramework/GEFE/Commands/ParentCommandGEFE.cs
deleted file mode 100644
index c20ba3cf..00000000
--- a/KruacentE.GlobalEventFramework/GEFE/Commands/ParentCommandGEFE.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using GEFExiled.Commands;
-
-namespace KruacentExiled.KruacentE.GlobalEventFramework.GEFE.Commands
-{
- using CommandSystem;
- using System;
-
- [CommandHandler(typeof(RemoteAdminCommandHandler))]
- public class ParentCommandGEFE : ParentCommand
- {
- public ParentCommandGEFE()
- {
- LoadGeneratedCommands();
- }
- public override string Command { get; } = "globalevent";
- public override string Description { get; } = "the parent command to check the Global Events";
- public override string[] Aliases { get; } = { "ge" };
-
- public override void LoadGeneratedCommands()
- {
- RegisterCommand(new List());
- }
-
- protected override bool ExecuteParent(ArraySegment arguments, ICommandSender sender, out string response)
- {
-
- response = "";
- return true;
- }
-
-
- }
-
-}
\ No newline at end of file
diff --git a/KruacentE.GlobalEventFramework/GEFE/Handlers/ServerHandler.cs b/KruacentE.GlobalEventFramework/GEFE/Handlers/ServerHandler.cs
deleted file mode 100644
index f5999932..00000000
--- a/KruacentE.GlobalEventFramework/GEFE/Handlers/ServerHandler.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using Exiled.API.Features;
-using Exiled.Events.EventArgs.Server;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using MEC;
-
-namespace GEFExiled.Handlers
-{
- internal class ServerHandler
- {
- MainPlugin _plugin;
- CoroutineHandle _coroutineHandle;
- public ServerHandler(MainPlugin mainPlugin)
- {
- this._plugin = mainPlugin;
- }
- public void OnRoundStarted()
- {
- Log.Debug("starting round");
- _plugin.ChooseGE();
- Log.Debug("end starting round");
- }
-
- public void OnEndingRound(EndingRoundEventArgs ev)
- {
- Log.Debug("ending round");
- this._plugin.StopCoroutines();
- }
- }
-}
diff --git a/KruacentE.GlobalEventFramework/GEFExiled.csproj b/KruacentE.GlobalEventFramework/GEFExiled.csproj
deleted file mode 100644
index 08b7aa74..00000000
--- a/KruacentE.GlobalEventFramework/GEFExiled.csproj
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
- net48
-
-
-
-
-
-
-
-
- ..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Assembly-CSharp-firstpass.dll
-
-
- C:\Program Files (x86)\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Mirror.dll
-
-
- C:\Program Files (x86)\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.dll
-
-
- C:\Program Files (x86)\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.CoreModule.dll
-
-
-
-
-
-
-
-
diff --git a/KruacentE.GlobalEventFramework/MainPlugin.cs b/KruacentE.GlobalEventFramework/MainPlugin.cs
deleted file mode 100644
index aefea34b..00000000
--- a/KruacentE.GlobalEventFramework/MainPlugin.cs
+++ /dev/null
@@ -1,141 +0,0 @@
-using Exiled.API.Features;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using MEC;
-using Exiled.API.Enums;
-using Player = Exiled.API.Features.Player;
-using GEFExiled.GEFE.API.Features;
-using GEFExiled.GEFE.API.Interfaces;
-using GEFExiled.GEFE.Examples.GE;
-namespace GEFExiled
-{
- internal class MainPlugin : Plugin
- {
- public override PluginPriority Priority => PluginPriority.Highest;
-
- internal Handlers.ServerHandler _server;
-
- internal static MainPlugin _instance;
-
- public static List coroutineHandles = new List();
- public override void OnEnabled()
- {
- _instance = this;
- GlobalEvent.Register(new SystemMalfunction());
- GlobalEvent.Register(new Shuffle());
-
- RegisterEvents();
- base.OnEnabled();
- }
-
- public override void OnDisabled()
- {
- UnregisterEvents();
- Timing.KillCoroutines();
- base.OnDisabled();
- _instance = null;
- }
-
- private void RegisterEvents()
- {
- _server = new Handlers.ServerHandler(this);
-
- Exiled.Events.Handlers.Server.RoundStarted += _server.OnRoundStarted;
- Exiled.Events.Handlers.Server.EndingRound += _server.OnEndingRound;
- }
-
- private void UnregisterEvents()
- {
- Exiled.Events.Handlers.Server.RoundStarted -= _server.OnRoundStarted;
- Exiled.Events.Handlers.Server.EndingRound -= _server.OnEndingRound;
-
- _server = null;
- }
-
- public void Show()
- {
- foreach (Player player in Player.List)
- {
- Exiled.API.Features.Broadcast b = new Exiled.API.Features.Broadcast();
- b.Content = ShowText();
- b.Duration = 10;
- player.Broadcast(b);
- }
- }
-
- private String ShowText()
- {
- String result = "Global Events: ";
- for (int i = 0; i < GlobalEvent.ActiveGE.Count(); i++)
- {
- result += GlobalEvent.ActiveGE[i].Description;
- if (GlobalEvent.ActiveGE.Count() > 1)
- {
- result += ",";
- }
- }
- return result;
- }
-
- public void ChooseGE()
- {
- Log.Debug("Choosine ge");
- List activeGE = ChooseRandomGE();
- Log.Debug("et go les ga c la fin du randomge");
-
- foreach (IGlobalEvent ge in activeGE)
- {
- Log.Debug($"GE : {ge.Name}");
- var a = Timing.RunCoroutine(ge.Start());
- Log.Debug("fin start corutn");
- coroutineHandles.Add(a); //crash
- Log.Debug("fin add corutn");
- }
- Log.Debug("fin choosein ge");
- }
-
-
- private List ChooseRandomGE(int nbGE = 1)
- {
- List result = new List();
- List gelist = GlobalEvent.GlobalEventsList.ToList();
-
- double totalWeight = GlobalEvent.GlobalEventsList.Sum(x => x.Weight);
- double randomWeight = UnityEngine.Random.value * totalWeight;
- double cumulativeWeight = 0.0;
-
-
- for (int i = 0; i < nbGE; i++)
- {
- Log.Debug("start foreach");
- bool found = false;
- foreach (IGlobalEvent ge in gelist)
- {
- cumulativeWeight += ge.Weight;
-
- if (randomWeight <= cumulativeWeight)
- {
- Log.Debug("hon hon hon le add du if");
- result.Add(ge);
- break;
- }
- }
- if (!found)
- {
- Log.Debug("nsm on a pa trouvé");
- result.Add(gelist.Last());
- gelist.Remove(gelist.Last());
- }
-
- }
- Log.Debug("fin :D");
- return result;
- }
-
- public void StopCoroutines()
- {
- Timing.KillCoroutines();
- }
- }
-}
diff --git a/KruacentE.Items/ArmeKruacent.csproj b/KruacentE.Items/ArmeKruacent.csproj
deleted file mode 100644
index bf48e06e..00000000
--- a/KruacentE.Items/ArmeKruacent.csproj
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
- net48
-
-
-
-
-
-
-
-
- C:\Program Files (x86)\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Assembly-CSharp-firstpass.dll
-
-
- ..\lib\CustomItems.dll
-
-
- C:\Program Files (x86)\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Mirror.dll
-
-
- C:\Program Files (x86)\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\System.ComponentModel.Composition.dll
-
-
- C:\Program Files (x86)\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.dll
-
-
- C:\Program Files (x86)\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.CoreModule.dll
-
-
- C:\Program Files (x86)\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.PhysicsModule.dll
-
-
-
-
diff --git a/KruacentE.Items/Items/AdrenalineDrogue.cs b/KruacentE.Items/Items/AdrenalineDrogue.cs
deleted file mode 100644
index 4beae8c3..00000000
--- a/KruacentE.Items/Items/AdrenalineDrogue.cs
+++ /dev/null
@@ -1,271 +0,0 @@
-using System.Collections.Generic;
-using Exiled.API.Enums;
-using Exiled.API.Features.Attributes;
-using Exiled.API.Features.Spawn;
-using Exiled.CustomItems.API.Features;
-using MEC;
-using Exiled.Events.EventArgs.Player;
-using Player = Exiled.Events.Handlers.Player;
-using Exiled.API.Features;
-using Exiled.API.Extensions;
-using UnityEngine;
-
-///
-[CustomItem(ItemType.Adrenaline)]
-public class AdrenalineDrogue : CustomItem
-{
- ///
- public override uint Id { get; set; } = 19;
-
- ///
- public override string Name { get; set; } = "DA-020";
-
- ///
- public override string Description { get; set; } = "La bonne drogue là, si vous le prenez vous êtes ienb pendant 20 secondes puis vous vous sentez pas bien !";
-
- ///
- public override float Weight { get; set; } = 0.65f;
-
- public List joueursSCP = new List();
-
- ///
- public override SpawnProperties SpawnProperties { get; set; } = new SpawnProperties()
- {
- Limit = 1,
- DynamicSpawnPoints = new List
- {
- new DynamicSpawnPoint()
- {
- Chance = 100,
- Location = SpawnLocationType.Inside079Secondary,
- },
- new DynamicSpawnPoint()
- {
- Chance = 30,
- Location = SpawnLocationType.InsideHid,
- },
- new DynamicSpawnPoint()
- {
- Chance = 30,
- Location = SpawnLocationType.InsideHczArmory,
- },
- new DynamicSpawnPoint()
- {
- Chance = 20,
- Location = SpawnLocationType.InsideIntercom,
- },
- new DynamicSpawnPoint()
- {
- Chance = 30,
- Location = SpawnLocationType.InsideNukeArmory
- },
- new DynamicSpawnPoint()
- {
- Chance = 1,
- Location = SpawnLocationType.Inside914
- },
- new DynamicSpawnPoint()
- {
- Chance = 2,
- Location = SpawnLocationType.Inside330
- }
- },
- };
-
- ///
- protected override void SubscribeEvents()
- {
- Player.UsedItem += OnUsingItem;
- base.SubscribeEvents();
- }
-
- ///
- protected override void UnsubscribeEvents()
- {
- Player.UsedItem -= OnUsingItem;
- base.UnsubscribeEvents();
- }
-
- private void OnUsingItem(UsedItemEventArgs ev)
- {
- if (TryGet(ev.Item, out var result))
- {
- if(result.Id == 19)
- {
- Timing.CallDelayed(0.5f, () =>
- {
- Timing.RunCoroutine(EffectAttribution(ev.Player));
- });
- }
-
- }
- }
-
- private IEnumerator EffectAttribution(Exiled.API.Features.Player joueur)
- {
- /* EFFET DE LA DROGUE */
- joueur.ShowHint("Vous êtes actuellement sous effet de la cocaïne liquide !");
- joueur.EnableEffect(EffectType.MovementBoost, 40, true);
- joueur.EnableEffect(EffectType.RainbowTaste, 30, true);
- joueur.EnableEffect(EffectType.InsufficientLighting, 30, true);
- joueur.EnableEffect(EffectType.BodyshotReduction, 30, true);
- joueur.EnableEffect(EffectType.Vitality, 30, true);
- joueur.EnableEffect(EffectType.Ghostly, 30, true);
- joueur.Health = 173;
-
- yield return Timing.WaitForSeconds(30);
-
- joueur.ShowHint("Mince vous êtes perdu chez le papi Rian !");
- joueur.Health = 9420;
-
- joueur.IsGodModeEnabled = true;
- joueur.EnableEffect(EffectType.Flashed, 2, 2);
- joueur.Teleport(RoomType.Pocket);
- yield return Timing.WaitForSeconds(6);
-
-
- joueur.EnableEffect(EffectType.Flashed, 2, 2);
- joueur.Teleport(Room.Random());
- joueur.Handcuff();
- yield return Timing.WaitForSeconds(20);
-
- joueur.Health = 1;
-
- joueur.EnableEffect(EffectType.SeveredHands, 4);
- yield return Timing.WaitForSeconds(4);
- joueur.DisableAllEffects();
-
-
- foreach (Exiled.API.Features.Player unJoueur in Exiled.API.Features.Player.List)
- {
- if (unJoueur.IsScp)
- {
- joueursSCP.Add(unJoueur);
- }
- }
-
- joueur.EnableEffect(EffectType.Flashed, 2, 2);
- if (joueursSCP.Count > 0)
- {
- joueur.Teleport(joueursSCP[UnityEngine.Random.Range(0, joueursSCP.Count)]);
- } else
- {
- joueur.Teleport(Room.Random());
- }
-
- yield return Timing.WaitForSeconds(10);
-
- joueur.Teleport(Room.Random());
-
- joueur.RemoveHandcuffs();
- joueur.IsGodModeEnabled = false;
- joueur.MaxHealth = 65;
- joueur.Heal(joueur.MaxHealth);
- joueur.DisplayNickname = "Sou Hiyori";
-
- joueur.DisableAllEffects();
- joueur.EnableEffect(EffectType.SilentWalk, 10);
- joueur.EnableEffect(EffectType.MovementBoost, 35);
-
-
- yield return Timing.WaitForSeconds(UnityEngine.Random.Range(10, 20));
-
- if (joueur.IsAlive)
- {
- int randomNumber = UnityEngine.Random.Range(1, 6);
-
- switch (randomNumber)
- {
- case 1:
- Log.Debug(joueur.Nickname + " a changé d'apparence !");
- joueur.PlayShieldBreakSound();
-
- joueur.ChangeAppearance(joueursSCP[0].Role);
- joueur.DisplayNickname = joueursSCP[0].Nickname;
-
- Exiled.API.Features.Server.FriendlyFire = true;
-
- joueur.Mute();
- yield return Timing.WaitForSeconds(15);
- joueur.UnMute();
- break;
- case 2:
- Log.Debug("Ne saute pas");
- joueur.ShowHint("Ne saute pas !");
-
- yield return Timing.WaitForSeconds(5);
-
- float duration = 300f;
- float interval = 0.1f;
-
- float elapsedTime = 0f;
-
- while (elapsedTime < duration)
- {
- if (joueur.IsJumping)
- {
- int randomSaute = UnityEngine.Random.Range(0, 1);
- if(randomSaute <= 0)
- {
- joueur.Explode();
- } else
- {
- joueur.ShowHint("Tu es joueur ! Voila une recompense");
- joueur.AddItem(ItemType.Coin);
- }
- }
-
- yield return Timing.WaitForSeconds(interval);
-
- elapsedTime += interval;
- }
- break;
- case 3:
- Log.Debug("Muet");
- joueur.ShowHint("Vous avez perdu votre langue ! (esperons que celui-ci repousse)");
- joueur.Mute();
- yield return Timing.WaitForSeconds(UnityEngine.Random.Range(30, 100));
- joueur.ShowHint("Je crois que c'est bon, ça a repoussé !");
- joueur.UnMute();
- break;
- case 4:
- joueur.ShowHint("Vous êtes devenu du caoutchouc !");
- Exiled.API.Features.TeslaGate.IgnoredPlayers.Add(joueur);
- joueur.SetScale(new Vector3(1.5f, 0.5f, 1.7f), Exiled.API.Features.Player.List);
- break;
- case 5:
- Log.Debug("Let's go party");
- foreach (var player in Exiled.API.Features.Player.List)
- {
- player.ShowHint(joueur.Nickname + " à commencer une fête d'anniversaire !");
- }
-
- float duration2 = 30f;
- float interval2 = 0.7f;
-
- float elapsedTime2 = 0f;
-
- while (elapsedTime2 < duration2)
- {
- float r = UnityEngine.Random.Range(0f, 1f);
- float g = UnityEngine.Random.Range(0f, 1f);
- float b = UnityEngine.Random.Range(0f, 1f);
-
- Exiled.API.Features.Map.ChangeLightsColor(new UnityEngine.Color(r, g, b));
-
- yield return Timing.WaitForSeconds(interval2);
-
- elapsedTime2 += interval2;
- }
-
- Exiled.API.Features.Map.ResetLightsColor();
- break;
- case 6:
- Log.Debug("Paper");
- joueur.ShowHint("Bienvenue dans le monde des papiers. Évite les ciseaux !");
- joueur.SetScale(new Vector3(1f, 0.5f, 1f), Exiled.API.Features.Player.List);
- break;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/KruacentE.Items/Items/Mine.cs b/KruacentE.Items/Items/Mine.cs
deleted file mode 100644
index 35f792cd..00000000
--- a/KruacentE.Items/Items/Mine.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using Exiled.CustomItems.API.Features;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace ArmeKruacent.Items
-{
-
- //une mine
- /*public class Mine : CustomGrenade
- {
-
- }
- */
-}
diff --git a/KruacentE.Items/Items/TPGrenada.cs b/KruacentE.Items/Items/TPGrenada.cs
deleted file mode 100644
index 34814ec7..00000000
--- a/KruacentE.Items/Items/TPGrenada.cs
+++ /dev/null
@@ -1,132 +0,0 @@
-
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using Exiled.API.Enums;
-using Exiled.API.Features;
-using Exiled.API.Features.Attributes;
-using Exiled.API.Features.Pools;
-using Exiled.API.Features.Spawn;
-using Exiled.CustomItems.API.Features;
-using Exiled.Events.EventArgs.Map;
-using PlayerRoles;
-using UnityEngine;
-
-namespace ArmeKruacent.Items
-{
- //grenade qui tp
- public class TPGrenada : CustomGrenade
- {
- private List effectedPlayers = new List();
- public override uint Id { get; set; } = 20;
- public override string Name { get; set; } = "Teleportation Grenade";
- public override string Description { get; set; } = "This grenade does 0 damage but teleport nearby players in a random place (does work in other dimension ;3 )";
- public override float Weight { get; set; } = 0.65f;
- public override float FuseTime { get; set; } = 1.5f;
- public override bool ExplodeOnCollision { get; set; } = true;
- public float DamageModifier { get; set; } = 0.05f;
- public override SpawnProperties SpawnProperties { get; set; } = new SpawnProperties()
- {
- Limit = 5,
- DynamicSpawnPoints = new List
- {
- new DynamicSpawnPoint()
- {
- Chance = 50,
- Location = SpawnLocationType.InsideLocker,
- },
- new DynamicSpawnPoint()
- {
- Chance = 50,
- Location = SpawnLocationType.InsideHczArmory,
- },
- new DynamicSpawnPoint()
- {
- Chance =2,
- Location = SpawnLocationType.Inside914,
- },
- new DynamicSpawnPoint()
- {
- Chance=50,
- Location = SpawnLocationType.Inside049Armory,
- },
- new DynamicSpawnPoint()
- {
- Chance=50,
- Location = SpawnLocationType.InsideLczArmory,
- }
- },
-
- };
-
- [Description("What roles will not be able to be affected by Implosion Grenades. Keeping SCP-173 on this list is highly recommended.")]
- public HashSet BlacklistedRoles { get; set; } = new HashSet() { RoleTypeId.Scp173, RoleTypeId.Scp106,RoleTypeId.Scp049, RoleTypeId.Scp096,RoleTypeId.Scp3114,RoleTypeId.Scp0492,RoleTypeId.Scp939 };
-
- protected override void OnExploding(ExplodingGrenadeEventArgs ev)
- {
- ev.IsAllowed = false;
- List copiedList = new List();
- foreach (Player player in ev.TargetsToAffect)
- {
- copiedList.Add(player);
- }
-
- ev.TargetsToAffect.Clear();
-
- effectedPlayers = ListPool.Pool.Get();
- foreach (Player player in copiedList)
- {
- if (BlacklistedRoles.Contains(player.Role))
- continue;
- try
- {
-
- bool line = Physics.Linecast(ev.Projectile.Transform.position, player.Position);
- if (line)
- {
- effectedPlayers.Add(player);
- player.Teleport(RandomRoom());
- }
- }
- catch (Exception exception)
- {
- Log.Error($"{nameof(OnExploding)} error: {exception}");
- }
- }
- }
-
-
-
- private Room RandomRoom()
- {
- Room room = Room.Random();
- if (Warhead.IsDetonated)
- {
- return Room.Random(ZoneType.Surface);
- }
-
- if(Map.IsLczDecontaminated)
- {
- float random = UnityEngine.Random.value;
- Log.Debug($"random={random}");
- if (random <= 0.33f)
- {
- return Room.Random(ZoneType.HeavyContainment);
- }
- if(random > 0.33f && random <= 0.66f)
- {
- return Room.Random(ZoneType.Entrance);
- }
- return Room.Random(ZoneType.Surface);
- }
- Log.Debug($"roomZone={room.Zone}");
- return room;
- }
- }
-
- //bonbon quand tu manges ça donne une armes random
- /*public class RandomWeaponCandy : CustomItem
- {
-
- }*/
-}
diff --git a/KruacentE.Items/MainPlugin.cs b/KruacentE.Items/MainPlugin.cs
deleted file mode 100644
index 42d98d4e..00000000
--- a/KruacentE.Items/MainPlugin.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-
-using Exiled.API.Features;
-using Exiled.CustomItems.API.Features;
-
-namespace ArmeKruacent
-{
- public class MainPlugin : Plugin
- {
-
- public override void OnEnabled()
- {
-
- CustomItem.RegisterItems();
-
- base.OnEnabled();
- }
-
- public override void OnDisabled()
- {
- CustomItem.UnregisterItems();
-
- base.OnDisabled();
- }
- }
-}
\ No newline at end of file
diff --git a/KruacentExiled.csproj b/KruacentExiled.csproj
deleted file mode 100644
index 6a605df2..00000000
--- a/KruacentExiled.csproj
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
- net48
-
-
-
-
-
-
-
-
- C:\Program Files (x86)\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Assembly-CSharp-firstpass.dll
-
-
- C:\Program Files (x86)\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.dll
-
-
- C:\Program Files (x86)\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.CoreModule.dll
-
-
-
-
diff --git a/KruacentE.Items/Config.cs b/KruacentExiled/IExiledTranslation.cs
similarity index 51%
rename from KruacentE.Items/Config.cs
rename to KruacentExiled/IExiledTranslation.cs
index c064de87..5edf884d 100644
--- a/KruacentE.Items/Config.cs
+++ b/KruacentExiled/IExiledTranslation.cs
@@ -5,11 +5,10 @@
using System.Text;
using System.Threading.Tasks;
-namespace ArmeKruacent
+namespace KruacentExiled
{
- public class Config : IConfig
+ internal interface IExiledTranslation
{
- public bool IsEnabled { get; set; } = true;
- public bool Debug { get; set; } = false;
+ public abstract ITranslation Translation { get; }
}
}
diff --git a/KruacentExiled/KE.CustomRoles/API/Core/Positions/UltraPosition.cs b/KruacentExiled/KE.CustomRoles/API/Core/Positions/UltraPosition.cs
new file mode 100644
index 00000000..4574a1f7
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/API/Core/Positions/UltraPosition.cs
@@ -0,0 +1,20 @@
+using HintServiceMeow.Core.Enum;
+using KE.Utils.API.Displays.DisplayMeow.Placements;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KE.CustomRoles.API.Core.Positions
+{
+ public class UltraPosition : HintPosition
+ {
+ public override float Xposition => 740;
+
+ public override float Yposition => 280;
+ public override string Name => "Ultra";
+
+ public override HintAlignment HintAlignment => HintAlignment.Right;
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/API/Features/Abilities/BaseCompAbility.cs b/KruacentExiled/KE.CustomRoles/API/Features/Abilities/BaseCompAbility.cs
new file mode 100644
index 00000000..c1bf3e46
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/API/Features/Abilities/BaseCompAbility.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace KE.CustomRoles.API.Features.Abilities
+{
+ public abstract class BaseCompAbility : MonoBehaviour
+ {
+
+ public abstract bool Active { get; }
+
+ public abstract void ToggleActive();
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/API/Features/Abilities/EmptyAbility.cs b/KruacentExiled/KE.CustomRoles/API/Features/Abilities/EmptyAbility.cs
new file mode 100644
index 00000000..e1a7f84d
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/API/Features/Abilities/EmptyAbility.cs
@@ -0,0 +1,25 @@
+using Exiled.API.Features;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KE.CustomRoles.API.Features.Abilities
+{
+ ///
+ /// an ability used to show text
+ ///
+ public abstract class EmptyAbility : KEAbilities
+ {
+
+ public sealed override float Cooldown => 1;
+ protected override void GuiReady(StringBuilder sb, Player player)
+ {
+
+ }
+
+
+
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/API/Features/Abilities/KEAbilityCost.cs b/KruacentExiled/KE.CustomRoles/API/Features/Abilities/KEAbilityCost.cs
new file mode 100644
index 00000000..9f9241aa
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/API/Features/Abilities/KEAbilityCost.cs
@@ -0,0 +1,45 @@
+using Exiled.API.Features;
+using KE.CustomRoles.Abilities.FireAbilities;
+using KE.Utils.API.CustomStats;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KE.CustomRoles.API.Features.Abilities
+{
+ public abstract class KEAbilityCost : KEAbilities
+ {
+
+ public abstract int Cost { get; }
+ public virtual string CostName { get; } = string.Empty;
+ protected sealed override bool AbilityUsed(Player player)
+ {
+ bool result = CanLaunchAbility(player);
+ if (result)
+ {
+ result = LaunchedAbility(player);
+ }
+ return result;
+ }
+
+ protected virtual bool LaunchedAbility(Player player)
+ {
+ return true;
+ }
+
+ public abstract bool CanLaunchAbility(Player player);
+
+
+ protected override void AbilityGui(StringBuilder sb,Player player)
+ {
+ base.AbilityGui(sb,player);
+ sb.Append("(");
+ sb.Append(Cost);
+ sb.Append(CostName);
+ sb.Append(")");
+ sb.Append(" ");
+ }
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/API/Features/Abilities/KEAbilityLimited.cs b/KruacentExiled/KE.CustomRoles/API/Features/Abilities/KEAbilityLimited.cs
new file mode 100644
index 00000000..f07f316f
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/API/Features/Abilities/KEAbilityLimited.cs
@@ -0,0 +1,74 @@
+using Exiled.API.Features;
+using KE.CustomRoles.Abilities.FireAbilities;
+using KE.Utils.API.CustomStats;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KE.CustomRoles.API.Features.Abilities
+{
+ public abstract class KEAbilityLimited : KEAbilities
+ {
+
+ public abstract int Uses { get; }
+
+ private Dictionary uses =new Dictionary();
+
+ public override void AddAbility(Player player)
+ {
+ uses[player] = Uses;
+ base.AddAbility(player);
+ }
+ protected sealed override bool AbilityUsed(Player player)
+ {
+ bool result = false;
+ int use = GetUse(player);
+ if (use > 0)
+ {
+ uses[player]--;
+ result = LaunchedAbility(player);
+ }
+ if(use <= 0)
+ {
+ RemoveAbility(player);
+ }
+
+ return result;
+ }
+
+ protected virtual bool LaunchedAbility(Player player)
+ {
+ return true;
+ }
+
+
+ public int GetUse(Player player)
+ {
+ if(!uses.TryGetValue(player,out int result))
+ {
+ return -1;
+ }
+
+ return result;
+ }
+
+
+
+ protected override void AbilityGui(StringBuilder sb,Player player)
+ {
+ base.AbilityGui(sb, player);
+ GuiUses(sb, player);
+ }
+
+
+ protected void GuiUses(StringBuilder sb, Player player)
+ {
+ sb.Append("(");
+ sb.Append(uses[player]);
+ sb.Append(" left)");
+ sb.Append(" ");
+ }
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/API/Features/Abilities/NeedCompAbility.cs b/KruacentExiled/KE.CustomRoles/API/Features/Abilities/NeedCompAbility.cs
new file mode 100644
index 00000000..b5b2ae9c
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/API/Features/Abilities/NeedCompAbility.cs
@@ -0,0 +1,47 @@
+using Exiled.API.Features;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace KE.CustomRoles.API.Features.Abilities
+{
+ public abstract class NeedCompAbility : KEAbilities where T : BaseCompAbility
+ {
+
+
+ protected virtual bool AddCompIfMissing { get; } = false;
+
+ protected sealed override bool AbilityUsed(Player player)
+ {
+ bool result = CanLaunchAbility(player,out T comp);
+ if (result)
+ {
+ result = LaunchedAbility(player,comp);
+ }
+ return result;
+ }
+
+ protected abstract bool LaunchedAbility(Player player, T component);
+
+ public virtual bool CanLaunchAbility(Player player, out T component)
+ {
+ if(!player.GameObject.TryGetComponent(out component))
+ {
+ if (AddCompIfMissing)
+ {
+ component = player.GameObject.AddComponent();
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ return component.Active;
+ }
+
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/API/Features/Abilities/ToggleableAbility.cs b/KruacentExiled/KE.CustomRoles/API/Features/Abilities/ToggleableAbility.cs
new file mode 100644
index 00000000..2032a441
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/API/Features/Abilities/ToggleableAbility.cs
@@ -0,0 +1,51 @@
+using Exiled.API.Features;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace KE.CustomRoles.API.Features.Abilities
+{
+ public abstract class ToggleableAbility : KEAbilities
+ {
+
+ public abstract Color ColorOn { get; }
+ private string colorOn = string.Empty;
+ public abstract Color ColorOff { get; }
+ private string colorOff = string.Empty;
+
+ public abstract bool GetState(Player player);
+
+ protected override void Gui(StringBuilder sb, Player player)
+ {
+ sb.Append("");
+ AbilityGui(sb, player);
+ GuiReady(sb, player);
+ GuiArrow(sb, player);
+ sb.Append("");
+ }
+
+
+
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/API/Features/CustomSCP.cs b/KruacentExiled/KE.CustomRoles/API/Features/CustomSCP.cs
new file mode 100644
index 00000000..09cf8420
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/API/Features/CustomSCP.cs
@@ -0,0 +1,79 @@
+using Exiled.API.Features;
+using Exiled.API.Features.Core.UserSettings;
+using KE.Utils.API.Features.SCPs;
+using KE.Utils.API.Settings;
+using KE.Utils.API.Settings.SettingsCategories;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace KE.CustomRoles.API.Features
+{
+ public abstract class CustomSCP : KECustomRole
+ {
+ public const int MinValue = -5;
+ public const int MaxValue = 5;
+ public const int DefaultValue = 0;
+
+ private SliderSetting sliderSetting;
+ public abstract bool IsSupport { get; }
+
+ protected abstract int SettingId { get; }
+ private static HeaderSetting header = null;
+ private static int HeaderId => MainPlugin.Configs.HeaderId;
+
+ private static SettingsCategory category;
+ public static IEnumerable All => Registered.Where(c => c is CustomSCP).Cast();
+ public override void Init()
+ {
+ base.Init();
+ if (SpawnChance <= 0)
+ {
+ return;
+ }
+
+
+ if (category is null)
+ {
+ header = new HeaderSetting(HeaderId, "SCP Spawn Preferences",string.Empty,true);
+ category = new SettingsCategory(header, 1001, new List());
+ }
+
+ sliderSetting= new SliderSetting(SettingId, GetTranslation("en", TranslationKeyName), MinValue, MaxValue, DefaultValue, true);
+
+ category.Settings.Add(sliderSetting);
+ }
+
+
+ public override void Destroy()
+ {
+ SettingBase.Unregister();
+ base.Destroy();
+ }
+
+ protected override void RoleAdded(Player player)
+ {
+ SCPTeam.AddSCP(player.ReferenceHub);
+ base.RoleAdded(player);
+ }
+
+
+ public int GetPreferences(Player player)
+ {
+ if(sliderSetting is null)
+ {
+ Log.Error("slider setting is null in custom scp");
+ return -6;
+ }
+
+
+ if (!SettingBase.TryGetSetting(player, sliderSetting.Id, out var setting)) return -6;
+ return (int) setting.SliderValue;
+ }
+
+ public override bool IsAvailable(Player player)
+ {
+ return false;
+ }
+
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/API/Features/GlobalCustomRole.cs b/KruacentExiled/KE.CustomRoles/API/Features/GlobalCustomRole.cs
new file mode 100644
index 00000000..32aba97f
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/API/Features/GlobalCustomRole.cs
@@ -0,0 +1,140 @@
+using Exiled.API.Enums;
+using Exiled.API.Extensions;
+using Exiled.API.Features;
+using Exiled.API.Features.Attributes;
+using Exiled.API.Features.Pools;
+using Exiled.CustomRoles.API.Features;
+using Exiled.Events.EventArgs.Player;
+using InventorySystem.Configs;
+using KE.CustomRoles.API.Interfaces;
+using KE.Utils.API.Displays.DisplayMeow;
+using LiteNetLib4Mirror.Open.Nat;
+using MEC;
+using PlayerRoles;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace KE.CustomRoles.API.Features
+{
+
+ public abstract class GlobalCustomRole : KECustomRole
+ {
+ public sealed override RoleTypeId Role { get; set; } = RoleTypeId.None;
+ public abstract SideEnum Side { get; set; }
+ public override bool KeepInventoryOnSpawn { get; set; } = true;
+
+ public override string Name
+ {
+ get
+ {
+ return Side.ToString().ToUpper() + "_" + InternalName.RemoveSpaces();
+ }
+ }
+
+ public override void AddRole(Player player)
+ {
+ SideEnum side = SideClass.Get(player.Role.Side);
+
+ if (side != Side)
+ {
+ Log.Error($"tried to give a global custom role to a player in the wrong side ({side} instead of {Side})");
+ return;
+ }
+
+
+ base.AddRole(player);
+ }
+
+ protected override void AttributeHealth(Player player)
+ {
+ player.MaxHealth *= MaxHealthMultiplicator;
+ player.Health = player.MaxHealth;
+ }
+
+
+
+
+ public sealed override int MaxHealth { get; set; }
+ public virtual float MaxHealthMultiplicator { get; set; } = 1;
+
+ protected override void ShowMessage(Player player)
+ {
+ StringBuilder sb = StringBuilderPool.Pool.Get();
+ sb.Append("");
+ IColor color = this as IColor;
+ if (color != null)
+ {
+ sb.Append("");
+ }
+
+
+ sb.Append(GetTranslation(player,TranslationKeyName));
+
+ if (color != null)
+ {
+ sb.Append("");
+ }
+
+ if (player.IsScp)
+ {
+ sb.Append(player.Role.Name);
+ }
+
+
+ sb.AppendLine("");
+
+ if (MainPlugin.SettingHandler.GetDescriptionsSettings(player))
+ {
+ sb.AppendLine(GetTranslation(player, TranslationKeyDesc));
+ }
+
+
+ float delay = MainPlugin.SettingHandler.GetTime(player);
+
+ DisplayHandler.Instance.AddHint(MainPlugin.CRHint, player, sb.ToString(), delay);
+ StringBuilderPool.Pool.Return(sb);
+ }
+
+
+
+ public override bool IsAvailable(Player player)
+ {
+ //if (player.Role == RoleTypeId.Scp106) return false;
+ if (CurrentNumberOfSpawn >= Limit) return false;
+ return SideClass.Get(player.Role.Side) == Side;
+ }
+
+ public override bool RoleCheck(RoleTypeId role)
+ {
+ return SideClass.Get(role.GetSide()) == Side;
+ }
+ }
+
+ public enum SideEnum
+ {
+ Human,
+ SCP,
+ None,
+ }
+
+ public static class SideClass
+ {
+ public static SideEnum Get(Side side)
+ {
+ return side switch
+ {
+ Side.Scp => SideEnum.SCP,
+ Side.Tutorial | Side.Mtf | Side.ChaosInsurgency => SideEnum.Human,
+ Side.None => SideEnum.None,
+ _ => SideEnum.None,
+ };
+ }
+ }
+
+}
diff --git a/KruacentExiled/KE.CustomRoles/API/Features/KEAbilities.cs b/KruacentExiled/KE.CustomRoles/API/Features/KEAbilities.cs
new file mode 100644
index 00000000..e72ed34d
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/API/Features/KEAbilities.cs
@@ -0,0 +1,769 @@
+using Exiled.API.Features;
+using Exiled.API.Features.Pools;
+using HintServiceMeow.Core.Enum;
+using HintServiceMeow.Core.Extension;
+using HintServiceMeow.Core.Models.Arguments;
+using HintServiceMeow.Core.Models.Hints;
+using KE.CustomRoles.Abilities.FireAbilities;
+using KE.CustomRoles.API.HintPositions;
+using KE.CustomRoles.API.Interfaces;
+using KE.CustomRoles.API.Interfaces.Ability;
+using KE.CustomRoles.Settings;
+using KE.Utils.API;
+using KE.Utils.API.Displays.DisplayMeow;
+using KE.Utils.API.Displays.DisplayMeow.Placements;
+using KE.Utils.API.Displays.Feeds;
+using KE.Utils.API.Features;
+using KE.Utils.API.Translations;
+using MEC;
+using PlayerRoles.FirstPersonControl.Thirdperson;
+using PlayerRoles.PlayableScps.HUDs;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using UnityEngine;
+
+namespace KE.CustomRoles.API.Features
+{
+ ///
+ /// Our version of the
+ ///
+ public abstract class KEAbilities
+ {
+ #region static stuff
+ private static HashSet registered = new HashSet();
+ public static HashSet Registered => registered;
+ #endregion
+
+ #region abstract stuff
+ public abstract string Name { get; }
+
+ public string TranslationKeyName => Name+"_"+ AbilityNameKey;
+ public string TranslationKeyDesc => Name+"_"+ AbilityDescriptionKey;
+
+ ///
+ /// in seconds
+ ///
+ public abstract float Cooldown { get; }
+ #endregion
+
+ private Dictionary LastUsed = new Dictionary();
+ private static Dictionary TypeToAbility { get; } = new Dictionary();
+ private static Dictionary NameToAbility { get; } = new Dictionary();
+ public HashSet Players { get; } = new HashSet();
+ public IReadOnlyCollection Selected => selected;
+ private HashSet selected = new HashSet();
+ private HashSet blockedPlayer = new HashSet();
+ private HashSet playerWithActiveAbility = new HashSet();
+
+
+
+ public static Dictionary> PlayersAbility { get;} = new Dictionary>();
+
+
+
+
+ protected const string AbilityNameKey = "Name";
+ protected const string AbilityDescriptionKey = "Desc";
+
+ public const string AbilityTranslationId = "Ability";
+ protected KEAbilities()
+ {
+
+ }
+
+
+ private bool activated = false;
+ private void OneTimeInit()
+ {
+ if (activated) return;
+ TranslationHub.Add(AbilityTranslationId, "en", "AbilityReady", "READY");
+ TranslationHub.Add(AbilityTranslationId, "fr", "AbilityReady", "PRÊT");
+
+
+ activated = true;
+ }
+
+ public virtual void Init()
+ {
+ if (NameToAbility.ContainsKey(Name))
+ {
+ KEAbilities other = NameToAbility[Name];
+ Log.Warn($"{GetType().FullName} have the same name as {other.GetType().FullName}. Skipping...");
+ return;
+ }
+
+
+ TypeToAbility.Add(GetType(), this);
+ NameToAbility.Add(Name, this);
+
+ OneTimeInit();
+ InternalSubscribeEvent();
+
+ var translate = SetTranslation();
+
+
+ TranslationHub.Add(AbilityTranslationId, translate);
+ }
+
+
+ protected abstract Dictionary> SetTranslation();
+
+ public static string GetTranslation(Player player, string key)
+ {
+ return TranslationHub.Get(player, AbilityTranslationId, key);
+ }
+
+
+ public static void ShowEffectHint(Player player,string translationKey)
+ {
+
+ string text = GetTranslation(player, translationKey);
+
+ float delay = MainPlugin.SettingHandler.GetTime(player);
+ DisplayHandler.Instance.AddHint(MainPlugin.CREffect, player, text, delay);
+ }
+
+ public static void ShowEffectHint(Player player, string text, float delay)
+ {
+ delay = MainPlugin.SettingHandler.GetTime(player);
+ DisplayHandler.Instance.AddHint(MainPlugin.CREffect, player, text, delay);
+ }
+
+
+ public static void TranslationFeed(Player player, string key)
+ {
+ HintFeed.AddFeed(player, GetTranslation(player, key));
+ }
+ public void Destroy()
+ {
+ InternalUnsubcribeEvent();
+ }
+
+ private void InternalSubscribeEvent()
+ {
+
+ SubscribeEvents();
+ }
+
+ private void InternalUnsubcribeEvent()
+ {
+ UnsubscribeEvents();
+ }
+
+
+
+
+
+ protected virtual void SubscribeEvents()
+ {
+
+ }
+
+ protected virtual void UnsubscribeEvents()
+ {
+
+ }
+
+ ///
+ ///
+ ///
+ ///
+ /// return true if the ability was used; returns false if the ability was not used and need to be refunded
+ protected virtual bool AbilityUsed(Player player)
+ {
+ return true;
+ }
+
+ protected virtual void AbilityAdded(Player player)
+ {
+
+ }
+ protected virtual void AbilityRemoved(Player player)
+ {
+
+ }
+
+
+ public void ShowAbility(Player player)
+ {
+ float time = MainPlugin.SettingHandler.GetAbilityTime(player);
+
+ StringBuilder sb = StringBuilderPool.Pool.Get();
+
+ sb.Append("");
+
+ if(this is IDynamicName dynamicName)
+ {
+ dynamicName.GetName(sb, player);
+ }
+ else
+ {
+ sb.Append(GetTranslation(player, TranslationKeyName));
+ }
+
+ sb.AppendLine("");
+
+
+ if(this is IDynamicDescription dynamicDescription)
+ {
+ dynamicDescription.GetDescription(sb, player);
+ }
+ else
+ {
+ sb.Append(GetTranslation(player, TranslationKeyDesc));
+ }
+
+ DisplayHandler.Instance.AddHint(MainPlugin.AbilitiesDesc, player, sb.ToString(), time);
+ StringBuilderPool.Pool.Return(sb);
+ }
+
+ public void SelectAbility(Player player, bool hide = false)
+ {
+ if (selected.Add(player))
+ {
+ Log.Debug($"player {player.Nickname} selected ability {this}");
+ foreach (KEAbilities abilities in Registered.Where(a => a != this && a.Players.Contains(player)))
+ {
+ abilities.UnselectAbility(player);
+ }
+ if (!hide)
+ {
+ ShowAbility(player);
+ }
+
+ }
+ }
+
+ public void UnselectAbility(Player player)
+ {
+ if (selected.Remove(player))
+ {
+ KELog.Debug($"player {player.Nickname} unselected ability {this}");
+ }
+
+ }
+
+ public void RemoveAbility(Player player)
+ {
+
+ if (Players.Contains(player))
+ {
+ KELog.Debug($"player {player.Nickname} lost {this}");
+ PlayersAbility[player].Remove(this);
+ Players.Remove(player);
+ UnselectAbility(player);
+ AbilityRemoved(player);
+ }
+ }
+ public virtual void AddAbility(Player player)
+ {
+
+ bool result = Players.Add(player);
+ Log.Debug($"player {player.Nickname} got {this} ({result})");
+ if (result)
+ {
+ if(!PlayersAbility.TryGetValue(player,out var _))
+ {
+ PlayersAbility.Add(player, new List());
+ }
+ PlayersAbility[player].Add(this);
+ InitHints(player);
+
+
+ AbilityAdded(player);
+ }
+
+ }
+ public void UseAbility(Player player)
+ {
+ if (AbilityUsed(player))
+ {
+ if (this is IDuration durationAbility)
+ {
+ this.playerWithActiveAbility.Add(player);
+ Timing.CallDelayed(durationAbility.Duration, () =>
+ {
+ durationAbility.ActionAfterAbility(player);
+ this.playerWithActiveAbility.Remove(player);
+ });
+ }
+
+ LastUsed[player] = DateTime.Now;
+ }
+ }
+
+ public bool IsAbilityActive(Player player)
+ {
+ return this.playerWithActiveAbility.Contains(player);
+ }
+
+ public virtual bool Check(Player player)
+ {
+ if(player != null)
+ {
+ return Players.Contains(player);
+ }
+ return false;
+ }
+
+ public bool CanUse(Player player, out string result)
+ {
+ if (player == null)
+ {
+ result = "player null";
+ return false;
+ }
+ if (!Players.Contains(player))
+ {
+ result = "cannot use this";
+ return false;
+ }
+ if (this.blockedPlayer.Contains(player))
+ {
+ result = "blocked";
+ return false;
+ }
+
+ if (!LastUsed.ContainsKey(player))
+ {
+ result = "never used";
+ return true;
+ }
+
+ DateTime dateTime = LastUsed[player] + TimeSpan.FromSeconds(Cooldown);
+ if (DateTime.Now > dateTime)
+ {
+ result = "ok";
+ return true;
+ }
+ result = "in cooldown";
+ return false;
+ }
+
+
+
+ public static void UseSelected(Player player)
+ {
+ if(TryGetSelected(player,out var ability))
+ {
+ Log.Debug("got selected " + ability.Name);
+ if (ability.CanUse(player, out string _))
+ {
+ Log.Debug("can use");
+ ability.UseAbility(player);
+ }
+ }
+ }
+
+ public static bool TryAddToPlayer(Type typeAbility,Player player)
+ {
+ if (!TypeToAbility.TryGetValue(typeAbility, out var ability)) return false;
+
+
+ ability.AddAbility(player);
+ return true;
+ }
+ public static bool TryAddToPlayer(string name, Player player)
+ {
+ if (!TryGet(name,out var ability)) return false;
+
+
+ ability.AddAbility(player);
+ return true;
+ }
+
+ public static void RemoveAllSelect(Player player)
+ {
+ if (!PlayersAbility.ContainsKey(player)) return;
+
+ foreach(KEAbilities ability in PlayersAbility[player])
+ {
+ ability.selected.Remove(player);
+ }
+
+ }
+
+ public static void SelectFirstAbility(Player player,bool hide = false)
+ {
+ if(PlayersAbility.TryGetValue(player,out var list))
+ {
+ list[0].SelectAbility(player);
+
+ }
+
+ }
+
+ public static void Remove(string name,Player player)
+ {
+ KEAbilities abilities = Get(name);
+
+ if(abilities != null)
+ {
+ abilities.RemoveAbility(player);
+ }
+
+ }
+
+ public static void TryRemoveFromPlayer(Player player)
+ {
+ RemoveAllSelect(player);
+ foreach(KEAbilities abilities in Registered)
+ {
+ if (abilities.Players.Contains(player))
+ {
+ abilities.RemoveAbility(player);
+ }
+ }
+ }
+
+ public static void TemporaryRemoveAbilities(Player player)
+ {
+ foreach(KEAbilities ability in PlayersAbility[player])
+ {
+ ability.blockedPlayer.Add(player);
+ }
+ }
+
+ public static void ReaffectRemovedAbilities(Player player)
+ {
+ foreach (KEAbilities ability in PlayersAbility[player])
+ {
+ ability.blockedPlayer.Remove(player);
+ }
+ }
+
+
+ public bool IsSelected(Player player)
+ {
+ return Selected.Contains(player);
+ }
+
+
+
+ #region register
+ private bool TryRegister()
+ {
+
+
+ if (!Registered.Contains(this))
+ {
+ Registered.Add(this);
+ Init();
+ Log.Debug("registered " + Name);
+ return true;
+ }
+
+
+
+ return false;
+ }
+
+
+ public static IEnumerable Register(Assembly assembly =null)
+ {
+ IEnumerable abilities = ReflectionHelper.GetObjects(assembly);
+ List result = abilities.ToList();
+
+ foreach(KEAbilities ability in abilities)
+ {
+
+ if (!ability.TryRegister())
+ {
+ Log.Warn("couldn't register KEability " + ability);
+ result.Remove(ability);
+ }
+
+ }
+ registered = result.ToHashSet();
+ return result;
+
+ }
+
+
+ private bool TryUnregister()
+ {
+ Destroy();
+ if (Registered.Remove(this))
+ {
+ Log.Debug("unregistered " + this);
+ return true;
+ }
+ Log.Warn(this + " was not registered");
+ return false;
+ }
+
+ public static IEnumerable Unregister()
+ {
+ List list = new List();
+ foreach (KEAbilities item in Registered)
+ {
+ item.TryUnregister();
+ list.Add(item);
+ }
+
+ return list;
+ }
+ #endregion
+
+ #region getters
+
+ public static KEAbilities Get(string name)
+ {
+ foreach(KEAbilities kEAbilities in Registered)
+ {
+ if (name == kEAbilities.Name)
+ {
+ return kEAbilities;
+ }
+ }
+
+ return null;
+ }
+
+ public static bool TryGet(string name, out KEAbilities ability)
+ {
+ ability = Get(name);
+ return ability != null;
+ }
+
+ public static KEAbilities GetSelected(Player player)
+ {
+ foreach(KEAbilities ability in Registered)
+ {
+ if (ability.IsSelected(player))
+ {
+ return ability;
+ }
+ }
+ return null;
+ }
+
+ public static bool TryGetSelected(Player player, out KEAbilities ability)
+ {
+ ability = GetSelected(player);
+ return ability != null;
+ }
+
+
+
+
+ #endregion
+
+ #region gui
+
+
+
+ protected virtual void GuiReady(StringBuilder sb, Player player)
+ {
+
+ if (CanUse(player, out var output))
+ {
+ sb.Append("[");
+ sb.Append(GetTranslation(player, "AbilityReady"));
+ sb.Append("]");
+ }
+ else
+ {
+ DateTime dateTime = LastUsed[player] + TimeSpan.FromSeconds(Cooldown);
+ sb.Append("[");
+ sb.Append(Math.Round((dateTime - DateTime.Now).TotalSeconds, 0));
+ sb.Append("s]");
+ }
+ }
+
+ protected void GuiArrow(StringBuilder sb, Player player)
+ {
+ if (IsSelected(player))
+ {
+ string arrow = SettingHandler.Instance.GetArrow(player);
+ if (string.IsNullOrEmpty(arrow))
+ {
+ arrow = SettingHandler.baseArrow;
+ }
+ sb.Append(arrow);
+ }
+ }
+
+ protected void GuiAbilityName(StringBuilder sb, Player player)
+ {
+ if(this is IDynamicName dynamic)
+ {
+ dynamic.GetName(sb, player);
+ }
+ else
+ {
+ sb.Append(GetTranslation(player, TranslationKeyName));
+ }
+
+
+ sb.Append(" ");
+ }
+
+ ///
+ /// The ability gui without the arrow or the ready
+ ///
+ protected virtual void AbilityGui(StringBuilder sb, Player player)
+ {
+ GuiAbilityName(sb, player);
+
+ }
+
+ ///
+ /// The full ability gui
+ ///
+ protected virtual void Gui(StringBuilder sb,Player player)
+ {
+ IConditional conditional = this as IConditional;
+
+ if(conditional != null)
+ {
+ if (conditional.CheckCondition(player))
+ {
+ sb.Append("");
+ }
+ else
+ {
+ sb.Append("");
+ }
+ }
+ else
+ {
+ sb.Append("");
+ }
+
+ AbilityGui(sb, player);
+ GuiReady(sb, player);
+ GuiArrow(sb, player);
+
+ sb.Append("");
+ }
+
+
+ public static Dictionary> PlayersHints { get; } = new Dictionary>();
+ public static Dictionary AddonHints { get; } = new Dictionary();
+ public const int InitialAbilitySlot = 5;
+ private void InitHints(Player player)
+ {
+
+
+ if (!PlayersHints.TryGetValue(player, out var _))
+ {
+ PlayersHints.Add(player, new List());
+ for (int i = 0; i < InitialAbilitySlot; i++)
+ {
+ AbilitiesPosition position = AbilitiesPosition.GetIndex(i);
+ AbstractHint hint = DisplayHandler.Instance.CreateAuto(player, arg => UpdateHint(arg), position.HintPlacement);
+ PlayersHints[player].Add(hint);
+ AddonHints.Add(hint, DisplayHandler.Instance.CreateAuto(player, arg => UpdateAddon(arg,hint), AddonAbilitiesPosition.Get(position).HintPlacement,HintSyncSpeed.Slow));
+ }
+
+
+
+ }
+
+ }
+
+ private static HashSet addonAbilitiesexception = new HashSet();
+
+ ///
+ ///
+ ///
+ ///
+ /// the other hint
+ ///
+ private static string UpdateAddon(AutoContentUpdateArg arg,AbstractHint hint)
+ {
+ Player player = Player.Get(arg.PlayerDisplay.ReferenceHub);
+ int index = PlayersHints[player].FindIndex(h => h == hint);
+
+ if (PlayersAbility[player].TryGet(index, out KEAbilities ability))
+ {
+ if (ability is ICustomIcon icon && !addonAbilitiesexception.Contains(ability))
+ {
+ StringBuilder sb = StringBuilderPool.Pool.Get();
+ try
+ {
+ sb.Append(icon.IconName.RawString);
+ }
+ catch(KeyNotFoundException)
+ {
+ addonAbilitiesexception.Add(ability);
+ Log.Error("Icon for ability "+ ability.Name+ " is missing");
+ return " ";
+ }
+ return StringBuilderPool.Pool.ToStringReturn(sb);
+ }
+ }
+ return " ";
+ }
+
+
+
+
+
+ private static string UpdateHint(AutoContentUpdateArg arg)
+ {
+ Player player = Player.Get(arg.PlayerDisplay.ReferenceHub);
+ int index = PlayersHints[player].FindIndex(h => h == arg.Hint);
+
+
+
+ if (PlayersAbility[player].TryGet(index,out KEAbilities ability))
+ {
+
+ StringBuilder sb = StringBuilderPool.Pool.Get();
+ ability.Gui(sb,player);
+ return StringBuilderPool.Pool.ToStringReturn(sb);
+ }
+
+
+
+ return " ";
+ }
+
+
+ public static string UpdateGUI(Player player)
+ {
+ StringBuilder builder = StringBuilderPool.Pool.Get();
+
+
+ List allAbilities = PlayersAbility[player];
+
+ for (int i = 0; i < allAbilities.Count; i++)
+ {
+
+
+ KEAbilities ability = allAbilities[i];
+ ability.Gui(builder,player);
+
+
+ }
+
+ string msg = builder.ToString();
+ StringBuilderPool.Pool.Return(builder);
+ return msg;
+ }
+
+
+
+
+
+ public static void ShowAbilityHint(Player player, string text)
+ {
+ float delay = MainPlugin.SettingHandler.GetTime(player);
+ DisplayHandler.Instance.AddHint(MainPlugin.AbilitiesDesc, player, text, delay);
+ }
+
+
+ #endregion
+
+ public override string ToString()
+ {
+ return Name;
+ }
+
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/API/Features/KECustomRole.cs b/KruacentExiled/KE.CustomRoles/API/Features/KECustomRole.cs
new file mode 100644
index 00000000..55778ccc
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/API/Features/KECustomRole.cs
@@ -0,0 +1,805 @@
+using CustomPlayerEffects;
+using Exiled.API.Enums;
+using Exiled.API.Extensions;
+using Exiled.API.Features;
+using Exiled.API.Features.Items;
+using Exiled.API.Features.Pools;
+using Exiled.CustomItems.API.Features;
+using Exiled.CustomRoles;
+using Exiled.CustomRoles.API;
+using Exiled.CustomRoles.API.Features;
+using Exiled.Events.EventArgs.Player;
+using Exiled.Events.EventArgs.Server;
+using HintServiceMeow.Core.Models.Hints;
+using InventorySystem.Configs;
+using KE.CustomRoles.API.HintPositions;
+using KE.CustomRoles.API.Interfaces;
+using KE.CustomRoles.Events.EventArgs;
+using KE.Utils.API.Displays.DisplayMeow;
+using KE.Utils.API.Displays.DisplayMeow.Placements;
+using KE.Utils.API.Translations;
+using MEC;
+using PlayerRoles;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Sockets;
+using System.Reflection;
+using System.Text;
+using UnityEngine;
+using YamlDotNet.Core.Tokens;
+
+namespace KE.CustomRoles.API.Features
+{
+ public abstract class KECustomRole : CustomRole
+ {
+ public const float TimeAttributingInventory = .25f;
+ public sealed override uint Id { get; set; } = 0;
+ public override string Name
+ {
+ get
+ {
+ return Role.ToString().ToUpper() + "_" + InternalName.RemoveSpaces();
+ }
+ set
+ {
+
+ }
+ }
+
+ public sealed override bool RemovalKillsPlayer
+ {
+ get
+ {
+ return false;
+ }
+ set
+ {
+
+ }
+ }
+
+ public virtual string InternalName => GetType().Name;
+
+ public sealed override string Description { get; set; } = string.Empty;
+
+ public static new HashSet Registered { get; } = new HashSet();
+
+ public sealed override string CustomInfo { get; set; }
+
+ ///
+ /// the max number of people who can have this role in a round
+ ///
+ public virtual int Limit { get; set; } = 1;
+ ///
+ /// the number of people who had this role in a round
+ ///
+ public int CurrentNumberOfSpawn { get; set; } = 0;
+
+ public static void ResetNumberOfSpawn()
+ {
+
+ foreach(KECustomRole cr in Registered)
+ {
+ cr.CurrentNumberOfSpawn = 0;
+ }
+
+ }
+
+
+ protected abstract Dictionary> SetTranslation();
+
+
+
+ public abstract override RoleTypeId Role { get; }
+
+ ///
+ ///
+ ///
+ public virtual HashSet Abilities { get; }
+
+ public sealed override bool IgnoreSpawnSystem { get; set; } = true;
+ public sealed override List CustomAbilities { get; set; } = null;
+ protected override void ShowMessage(Player player)
+ {
+ //string msg = MainPlugin.Translations.GettingNewRole;
+ //msg = msg.Replace("%Name%", PublicName).Replace("%Desc%",Description);
+ StringBuilder sb =StringBuilderPool.Pool.Get();
+ sb.Append("");
+ IColor color = this as IColor;
+ if (color != null)
+ {
+ sb.Append("");
+ }
+
+ sb.Append(GetTranslation(player,TranslationKeyName));
+
+ if (color != null)
+ {
+ sb.Append("");
+ }
+ sb.AppendLine("");
+
+ if (MainPlugin.SettingHandler.GetDescriptionsSettings(player))
+ {
+ sb.AppendLine(GetTranslation(player, TranslationKeyDesc));
+
+ if(this is IHealable heal)
+ {
+ sb.AppendLine(GetTranslation(player, TranslationHealable));
+ }
+
+ }
+
+
+ float delay = MainPlugin.SettingHandler.GetTime(player);
+
+ DisplayHandler.Instance.AddHint(MainPlugin.CRHint, player, sb.ToString(), delay);
+ StringBuilderPool.Pool.Return(sb);
+ }
+
+ public void Show(Player player)
+ {
+ ShowMessage(player);
+ }
+
+
+ private static Dictionary typeLookupTable = new Dictionary();
+ private static Dictionary stringLookupTable = new Dictionary();
+
+ protected const string CustomRoleNameKey = "Name";
+ protected const string CustomRoleDescriptionKey = "Desc";
+ public string TranslationKeyName => Name + "_" + CustomRoleNameKey;
+ public string TranslationKeyDesc => Name + "_" + CustomRoleDescriptionKey;
+
+ public const string CustomRoleTranslationId = "CustomRole";
+ public override void Init()
+ {
+ typeLookupTable.Add(GetType(), this);
+ stringLookupTable.Add(Name, this);
+ OneTimeInit();
+ InternalSubscribeEvents();
+ SubscribeEvents();
+
+ var translate = SetTranslation();
+
+
+ TranslationHub.Add(CustomRoleTranslationId, translate);
+
+ Log.Debug("adding keys to "+Name);
+ }
+
+ private bool activated = false;
+
+ private const string TranslationGUI = "CustomRoleGUI";
+ private const string TranslationHealable = "CustomRoleHealable";
+ private static Dictionary> SetStaticTranslation()
+ {
+ return new Dictionary>()
+ {
+ ["en"] = new Dictionary()
+ {
+ [TranslationGUI] = "Current Role",
+ [TranslationHealable] = "This Custom Role is healable!",
+ },
+ ["fr"] = new Dictionary()
+ {
+ [TranslationGUI] = "Role",
+ [TranslationHealable] = "Ce custom role peut être enlevé avec un object!",
+ },
+ };
+ }
+
+ private void OneTimeInit()
+ {
+ if (activated) return;
+ TranslationHub.Add(CustomRoleTranslationId, SetStaticTranslation());
+
+
+ activated = true;
+ }
+ public static string GetTranslation(Player player, string key)
+ {
+ return TranslationHub.Get(player, CustomRoleTranslationId, key);
+ }
+ public static string GetTranslation(string lang, string key)
+ {
+ return TranslationHub.Get(lang, CustomRoleTranslationId, key);
+ }
+
+ public override void Destroy()
+ {
+ typeLookupTable.Remove(GetType());
+ stringLookupTable.Remove(Name);
+ InternalUnsubscribeEvents();
+ UnsubscribeEvents();
+ }
+
+ protected virtual void InternalSubscribeEvents()
+ {
+ if(this is IHealable)
+ {
+ Exiled.Events.Handlers.Player.UsedItem += OnUsedItem;
+ }
+
+ if(this is IEffectImmunity)
+ {
+ Exiled.Events.Handlers.Player.ReceivingEffect += OnReceivingEffect;
+ }
+
+ }
+
+
+ protected virtual void InternalUnsubscribeEvents()
+ {
+ if (this is IHealable)
+ {
+ Exiled.Events.Handlers.Player.UsedItem -= OnUsedItem;
+ }
+ if (this is IEffectImmunity)
+ {
+ Exiled.Events.Handlers.Player.ReceivingEffect -= OnReceivingEffect;
+ }
+ }
+
+
+ private void OnUsedItem(UsedItemEventArgs ev)
+ {
+ if (!Check(ev.Player)) return;
+ IHealable healable = this as IHealable;
+ if(healable.HealItem is null || healable.HealItem.Count == 0)
+ {
+ Log.Warn("no healable item found for" + Name);
+ return;
+ }
+
+
+ if (healable.HealItem.Contains(ev.Item.Type))
+ {
+ RemoveRole(ev.Player);
+ }
+ }
+
+
+ private void OnReceivingEffect(ReceivingEffectEventArgs ev)
+ {
+ if (!Check(ev.Player)) return;
+ if (!ev.IsAllowed) return;
+
+ IEffectImmunity effectImmunity = this as IEffectImmunity;
+
+ if (effectImmunity.ImmuneEffects is null || effectImmunity.ImmuneEffects.Count == 0)
+ {
+ Log.Warn("no healable item found for" + Name);
+ return;
+ }
+ if (effectImmunity.ImmuneEffects.Contains(ev.Effect.GetEffectType()))
+ {
+ ev.IsAllowed = false;
+ }
+
+ }
+
+
+ public static void SpawnStartRound(Misc.Features.Spawn.SpawnedEventArgs ev)
+ {
+ GiveRandomRole(Player.List.Except(ev.CustomRoles));
+ }
+
+ public static void RespawnCustomRole(RespawnedTeamEventArgs ev)
+ {
+ GiveRandomRole(ev.Players);
+ }
+
+ public static void ShowCustomRole(VerifiedEventArgs ev)
+ {
+ Timing.CallDelayed(1, delegate
+ {
+ Player player = ev.Player;
+ DisplayHandler.Instance.CreateAuto(player, arg => CurrentRole(player), CurrentCustomRolePosition.HintPlacement);
+ });
+
+
+ }
+
+
+
+
+ public static string CurrentRole(Player player)
+ {
+ StringBuilder sb = StringBuilderPool.Pool.Get();
+
+ if (HasCustomRole(player))
+ {
+ KECustomRole kECustomRole = Get(player).First();
+
+ sb.Append(GetTranslation(player, TranslationGUI));
+ sb.AppendLine(" : ");
+ sb.AppendLine();
+ sb.Append("");
+ sb.Append(GetTranslation(player,kECustomRole.TranslationKeyName));
+ sb.Append("");
+ }
+ else if (player.IsDead)
+ {
+ Player spectating = GetSpectatingPlayer(player);
+ KECustomRole customRole = null;
+ if (spectating != null)
+ {
+ customRole = Get(spectating).FirstOrDefault();
+ }
+
+
+ if(customRole != null)
+ {
+ sb.Append(GetTranslation(player, TranslationGUI));
+ sb.AppendLine(" : ");
+ sb.AppendLine();
+ sb.Append("");
+ sb.Append(GetTranslation(player,customRole.TranslationKeyName));
+ sb.Append("");
+ }
+ }
+
+ string result = StringBuilderPool.Pool.ToStringReturn(sb);
+
+ if (string.IsNullOrEmpty(result))
+ {
+ result = " ";
+ }
+
+
+ return result;
+ }
+
+ private static Player GetSpectatingPlayer(Player spectator)
+ {
+
+ foreach(Player player in Player.Enumerable)
+ {
+ if (player.CurrentSpectatingPlayers.Contains(spectator))
+ {
+ return player;
+ }
+ }
+
+ return null;
+
+
+ }
+
+
+ protected void ShowEffectHint(Player player, string text)
+ {
+ float delay = MainPlugin.SettingHandler.GetTime(player); ;
+ DisplayHandler.Instance.AddHint(MainPlugin.CREffect, player, text, delay);
+ }
+ protected void ShowEffectHint(Player player, string text, float delay)
+ {
+ DisplayHandler.Instance.AddHint(MainPlugin.CREffect, player, text, delay);
+ }
+
+
+
+
+
+ #region addrole
+ public override void AddRole(Player player)
+ {
+ Player player2 = player;
+ Log.Debug(Name + ": Adding role to " + player2.Nickname + ".");
+ Log.Debug(Name + ": old role type " + player2.Role.Type + ".");
+ Log.Debug(Name + ": new role type " + Role + ".");
+
+
+ ReceivingCustomRoleEventArgs ev1 = new ReceivingCustomRoleEventArgs(player2,this);
+
+ Events.Handlers.KECustomRole.OnReceivingCustomRole(ev1);
+
+ if (!ev1.IsAllowed)
+ {
+ Log.Debug(Name + ": role cancelled by plugin");
+ return;
+ }
+
+ foreach(KECustomRole cr in Get(player))
+ {
+ cr.RemoveRole(player);
+ }
+
+
+
+ CurrentNumberOfSpawn++;
+
+
+
+
+ if (Role != RoleTypeId.None)
+ {
+ Log.Debug("new role exist");
+ if (KeepPositionOnSpawn)
+ {
+ if (KeepInventoryOnSpawn)
+ {
+ player2.Role.Set(Role, SpawnReason.ForceClass, RoleSpawnFlags.None);
+ }
+ else
+ {
+ player2.Role.Set(Role, SpawnReason.ForceClass, RoleSpawnFlags.AssignInventory);
+ }
+ }
+ else if (KeepInventoryOnSpawn && player2.IsAlive)
+ {
+ player2.Role.Set(Role, SpawnReason.ForceClass, RoleSpawnFlags.UseSpawnpoint);
+ }
+ else
+ {
+ player2.Role.Set(Role, SpawnReason.ForceClass, RoleSpawnFlags.AssignInventory);
+ }
+ }
+
+
+
+ TrackedPlayers.Add(player2);
+
+ Timing.CallDelayed(TimeAttributingInventory, delegate
+ {
+ ClearInventory(player2);
+ GiveInventory(player2);
+ GiveAmmo(player2);
+ });
+
+
+ AttributeHealth(player2);
+ AttributeScale(player2);
+
+ SetCustomInfo(player2);
+
+ SetAbilities(player2);
+
+ SetSpawn(player2);
+
+
+ ShowMessage(player2);
+
+ RoleAdded(player2);
+ player2.UniqueRole = Name;
+ player2.TryAddCustomRoleFriendlyFire(Name, CustomRoleFFMultiplier);
+ ReceivedCustomRoleEventArgs ev2 = new ReceivedCustomRoleEventArgs(player2, this);
+
+ Events.Handlers.KECustomRole.OnReceivedCustomRole(ev2);
+
+ }
+
+
+ public static readonly HintPosition CurrentCustomRolePosition = new CurrentCustomRolePosition();
+
+
+
+ protected virtual void ClearInventory(Player player)
+ {
+ if (!KeepInventoryOnSpawn)
+ {
+ player.ClearInventory();
+ }
+ }
+
+ protected virtual void GiveInventory(Player player)
+ {
+
+ for (int i = 0; i < Inventory.Count; i++)
+ {
+ string item = Inventory[i];
+ Log.Debug(Name + ": Adding " + item + " to inventory.");
+ if (TryAddItem(player, item) && CustomItem.TryGet(item, out CustomItem customItem) && customItem is CustomWeapon customWeapon && player.CurrentItem is Firearm firearm && !customWeapon.Attachments.IsEmpty())
+ {
+ firearm.AddAttachment(customWeapon.Attachments);
+ Log.Debug(Name + ": Applied attachments to " + item + ".");
+ }
+ }
+ }
+
+ protected virtual void GiveAmmo(Player player)
+ {
+ if (Ammo.Count > 0)
+ {
+ AmmoType[] values = EnumUtils.Values;
+ foreach (AmmoType ammoType in values)
+ {
+ if (ammoType != 0)
+ {
+ player.SetAmmo(ammoType, (ushort)(Ammo.ContainsKey(ammoType) ? Ammo[ammoType] == ushort.MaxValue ? InventoryLimits.GetAmmoLimit(ammoType.GetItemType(), player.ReferenceHub) : Ammo[ammoType] : 0));
+ }
+ }
+ }
+ }
+
+ protected virtual void AttributeHealth(Player player)
+ {
+ player.Health = MaxHealth;
+ player.MaxHealth = MaxHealth;
+ }
+
+ protected virtual void AttributeScale(Player player)
+ {
+ player.Scale = Scale;
+ }
+
+ protected virtual void SetSpawn(Player player)
+ {
+ Vector3 spawnPosition = GetSpawnPosition();
+ if (spawnPosition != Vector3.zero)
+ {
+ player.Position = spawnPosition;
+ }
+ }
+
+ protected virtual void SetCustomInfo(Player player)
+ {
+ //MirrorExtensions.SetPlayerInfoForTargetOnly()
+
+ string name = GetTranslation("legacy", TranslationKeyName);
+
+ player.CustomInfo = player.CustomName;
+ if (!string.IsNullOrEmpty(name))
+ {
+ player.CustomInfo += "\n" + name;
+ }
+ player.InfoArea &= ~(PlayerInfoArea.Nickname | PlayerInfoArea.Role);
+ }
+
+ protected virtual void SetAbilities(Player player)
+ {
+ KEAbilities.TryRemoveFromPlayer(player);
+
+ if (Abilities != null)
+ {
+ foreach (string name in Abilities)
+ {
+ if(!KEAbilities.TryAddToPlayer(name, player))
+ {
+ Log.Error("couldn't find ability : " + name);
+ }
+
+ }
+ KEAbilities.SelectFirstAbility(player,true);
+ }
+ }
+
+
+#endregion
+
+
+ public override void RemoveRole(Player player)
+ {
+ base.RemoveRole(player);
+ if (Abilities != null)
+ {
+ KEAbilities.TryRemoveFromPlayer(player);
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ /// true if the player can have this CR ; false otherwise
+ public virtual bool IsAvailable(Player player)
+ {
+ if (CurrentNumberOfSpawn >= Limit) return false;
+ return RoleCheck(player.Role);
+ }
+
+
+ public virtual bool RoleCheck(RoleTypeId role)
+ {
+ return Role == role;
+ }
+
+
+ ///
+ /// The chance of having this role, NOT the chance to have a role
+ ///
+ public new virtual float SpawnChance { get; set; } = 100;
+
+ public static new KECustomRole Get(string fullinternalname)
+ {
+ return stringLookupTable[fullinternalname];
+ }
+
+
+ public static bool TryGet(string fullinternalname,out KECustomRole customrole)
+ {
+ customrole = Get(fullinternalname);
+ return customrole != null;
+ }
+
+ public static KECustomRole Get(RoleTypeId roleid,string name)
+ {
+ return Get(roleid.ToString().ToUpper() + "_" + name);
+ }
+ public static bool Get(RoleTypeId roleid, string name, out KECustomRole customrole)
+ {
+ customrole = Get(roleid, name);
+ return customrole != null;
+ }
+
+
+ public static bool HasCustomRole(Player player)
+ {
+ if (player is null) return false;
+
+
+ foreach(KECustomRole customRole in Registered)
+ {
+ if (customRole.Check(player))
+ {
+ return true;
+ }
+ }
+ return false;
+
+ }
+
+ public static IEnumerable Get(Player player)
+ {
+ List cr = new List();
+ foreach(KECustomRole ke in Registered)
+ {
+ if (ke.Check(player))
+ {
+ cr.Add(ke);
+ }
+ }
+ return cr;
+ }
+
+ #region Spawn
+
+ ///
+ /// The chance to get a at the start or a respawn
+ ///
+ public static float Chance
+ {
+ get
+ {
+ return chance;
+ }
+ set
+ {
+ chance = Mathf.Clamp(value, 0f, 100f);
+ }
+ }
+
+ private static float chance = 100;
+
+ private static KECustomRole AssignRole(Dictionary roleChances)
+ {
+ float totalWeight = roleChances.Values.Sum();
+ float randomValue = UnityEngine.Random.Range(0f, totalWeight);
+
+ foreach (var role in roleChances)
+ {
+ randomValue -= role.Value;
+ if (randomValue <= 0)
+ return role.Key;
+ }
+
+ return null;
+ }
+
+ public static Dictionary GetAvailableCustomRole(Player player)
+ {
+ return Registered.Where(cr => cr.IsAvailable(player)).ToDictionary(c => c, c => c.SpawnChance);
+ }
+
+ public static void GiveRandomRole(Player player)
+ {
+ if (player == null)
+ return;
+ if (UnityEngine.Random.Range(0f, 100f) > Chance)
+ {
+ Log.Debug("no luck");
+ return;
+ }
+
+ if(HasCustomRole(player))
+ {
+ Log.Debug("already got a cr");
+ return;
+ }
+
+
+ KECustomRole cr = AssignRole(GetAvailableCustomRole(player));
+ Log.Debug($"{player.Id} : {cr?.Name}");
+
+ cr?.AddRole(player);
+ }
+
+
+ public static void GiveRandomRole(IEnumerable players)
+ {
+ foreach (Player p in players)
+ {
+ GiveRandomRole(p);
+ }
+ }
+ #endregion
+
+
+
+
+
+ #region Register
+
+ public bool TryRegister()
+ {
+
+ if (!Registered.Contains(this))
+ {
+ if (Registered.Any((KECustomRole r) => r.Name == Name))
+ {
+ Log.Warn($"{Name} has tried to register with the same Name as another role: {Name}. It will not be registered!");
+ return false;
+ }
+
+ Registered.Add(this);
+ Init();
+ return true;
+ }
+
+
+ return false;
+ }
+ public bool TryUnregister()
+ {
+ Destroy();
+ if (!Registered.Remove(this))
+ {
+ Log.Warn($"Cannot unregister {Name} [{Role}], it hasn't been registered yet.");
+ return false;
+ }
+
+ return true;
+ }
+
+ public static IEnumerable Register()
+ {
+ List list = new List();
+ Type[] types = Assembly.GetCallingAssembly().GetTypes();
+ foreach (Type type in types)
+ {
+ if (!type.IsAbstract && type.IsSubclassOf(typeof(KECustomRole)))
+ {
+ KECustomRole customRole = (KECustomRole)Activator.CreateInstance(type);
+ if (customRole.TryRegister())
+ {
+ list.Add(customRole);
+ }
+ }
+ }
+ return list;
+ }
+
+
+ public static IEnumerable Unregister()
+ {
+ List list = new List();
+ foreach (KECustomRole item in Registered)
+ {
+ item.TryUnregister();
+ list.Add(item);
+ }
+
+ return list;
+ }
+
+ #endregion
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/API/Features/KECustomRoleMultipleRole.cs b/KruacentExiled/KE.CustomRoles/API/Features/KECustomRoleMultipleRole.cs
new file mode 100644
index 00000000..ccbb24ed
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/API/Features/KECustomRoleMultipleRole.cs
@@ -0,0 +1,44 @@
+using Exiled.API.Enums;
+using Exiled.API.Extensions;
+using Exiled.API.Features;
+using PlayerRoles;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KE.CustomRoles.API.Features
+{
+ public abstract class KECustomRoleMultipleRole : KECustomRole
+ {
+
+ public override string Name
+ {
+ get
+ {
+ return "MULTIPLE_" + InternalName.RemoveSpaces();
+ }
+ }
+ public override sealed RoleTypeId Role => RoleTypeId.None;
+ public override sealed int MaxHealth { get; set; }= -1;
+
+ public abstract HashSet Roles { get; }
+
+
+
+
+
+ protected override void AttributeHealth(Player player)
+ {
+
+ }
+
+
+ public override bool RoleCheck(RoleTypeId role)
+ {
+ return Roles.Contains(role);
+ }
+
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/API/Features/RecyclableSettingId.cs b/KruacentExiled/KE.CustomRoles/API/Features/RecyclableSettingId.cs
new file mode 100644
index 00000000..322c6907
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/API/Features/RecyclableSettingId.cs
@@ -0,0 +1,78 @@
+using Exiled.API.Features;
+using Exiled.API.Features.Core.UserSettings;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KE.CustomRoles.API.Features
+{
+ public struct RecyclableSettingId : IEquatable
+ {
+ public const int MinThreshold = 16;
+ public readonly int Value;
+
+ private static Queue FreeIds = new Queue();
+ private static int _autoIncrement;
+
+ private static int Min => MainPlugin.Configs.CustomScpSliderRangeMin;
+ private static int Max => MainPlugin.Configs.CustomScpSliderRangeMax;
+
+ public RecyclableSettingId(SettingBase setting)
+ {
+ Value = setting.Id;
+ }
+
+ /*public RecyclableSettingId()
+ {
+ int num = MinThreshold;
+ int value;
+ if (FreeIds.Count >= num)
+ {
+ value = FreeIds.Dequeue();
+ }
+ else
+ {
+ value = _autoIncrement++ + Min;
+ if(value > Max)
+ {
+ throw new ArgumentOutOfRangeException("ID out of range, please increase the CustomScpSliderRangeMax in the plugins settings");
+ }
+ }
+ Log.Debug("creating id =" + value);
+ Value = value;
+
+ }*/
+
+
+ public void Destroy()
+ {
+ if(Value != 0)
+ {
+ FreeIds.Enqueue(Value);
+ }
+ }
+
+
+
+ public bool Equals(RecyclableSettingId other)
+ {
+ return other.Value == this.Value;
+ }
+ public override bool Equals(object obj)
+ {
+ if (obj is RecyclableSettingId other)
+ {
+ return Equals(other);
+ }
+ return false;
+ }
+
+ public override int GetHashCode()
+ {
+ return Value;
+ }
+
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/API/HintPositions/AbilitiesPosition.cs b/KruacentExiled/KE.CustomRoles/API/HintPositions/AbilitiesPosition.cs
new file mode 100644
index 00000000..bbcfdb8e
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/API/HintPositions/AbilitiesPosition.cs
@@ -0,0 +1,47 @@
+using Exiled.API.Enums;
+using Exiled.API.Features;
+using HintServiceMeow.Core.Enum;
+using KE.CustomRoles.API.Features;
+using KE.Utils.API.Displays.DisplayMeow.Placements;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KE.CustomRoles.API.HintPositions
+{
+ public class AbilitiesPosition : HintPosition
+ {
+
+
+ public const float BaseYPosition = 900;
+ public const float Increments = -50;
+ private float yposition = BaseYPosition;
+ public override float Xposition => -300;
+
+ public override float Yposition => yposition;
+
+ private int index;
+ public int Index => index;
+
+ private static List nonalloc = new List(KEAbilities.InitialAbilitySlot);
+
+
+ public static AbilitiesPosition GetIndex(int index)
+ {
+ if (!nonalloc.TryGet(index, out AbilitiesPosition position))
+ {
+ position = new AbilitiesPosition()
+ {
+ yposition = BaseYPosition - index * 50,
+ index = index
+ };
+ }
+ Log.Debug($"get index {index} y pos=" + position.Yposition);
+ return position;
+ }
+
+ public override HintAlignment HintAlignment => HintAlignment.Left;
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/API/HintPositions/AddonAbilitiesPosition.cs b/KruacentExiled/KE.CustomRoles/API/HintPositions/AddonAbilitiesPosition.cs
new file mode 100644
index 00000000..0e2f40d2
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/API/HintPositions/AddonAbilitiesPosition.cs
@@ -0,0 +1,45 @@
+using Exiled.API.Features;
+using HintServiceMeow.Core.Enum;
+using KE.CustomRoles.API.Features;
+using KE.Utils.API.Displays.DisplayMeow.Placements;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KE.CustomRoles.API.HintPositions
+{
+ public class AddonAbilitiesPosition : HintPosition
+ {
+
+
+ private float yposition = AbilitiesPosition.BaseYPosition;
+ public override float Xposition => -350;
+
+ public override float Yposition => yposition;
+
+
+ private static List nonalloc = new List(KEAbilities.InitialAbilitySlot);
+
+
+ public static AddonAbilitiesPosition Get(AbilitiesPosition abilitiesPosition)
+ {
+ if (abilitiesPosition is null) throw new ArgumentNullException();
+ int index = abilitiesPosition.Index;
+
+ if (!nonalloc.TryGet(index, out AddonAbilitiesPosition position))
+ {
+ position = new AddonAbilitiesPosition()
+ {
+ yposition = abilitiesPosition.Yposition+5
+ };
+ }
+ Log.Debug($"addon get {index} y pos=" + position.Yposition);
+ return position;
+
+ }
+
+ public override HintAlignment HintAlignment => HintAlignment.Left;
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/API/HintPositions/CurrentCustomRolePosition.cs b/KruacentExiled/KE.CustomRoles/API/HintPositions/CurrentCustomRolePosition.cs
new file mode 100644
index 00000000..e818cbbe
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/API/HintPositions/CurrentCustomRolePosition.cs
@@ -0,0 +1,21 @@
+using HintServiceMeow.Core.Enum;
+using KE.Utils.API.Displays.DisplayMeow.Placements;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KE.CustomRoles.API.HintPositions
+{
+ public class CurrentCustomRolePosition : HintPosition
+ {
+ public override float Xposition => 600;
+
+ public override float Yposition => 1030;
+
+ public override HintAlignment HintAlignment => HintAlignment.Center;
+
+ public override string Name => "CurrentRole";
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/API/Interfaces/Ability/IConditional.cs b/KruacentExiled/KE.CustomRoles/API/Interfaces/Ability/IConditional.cs
new file mode 100644
index 00000000..682e4ba1
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/API/Interfaces/Ability/IConditional.cs
@@ -0,0 +1,15 @@
+using Exiled.API.Features;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KE.CustomRoles.API.Interfaces.Ability
+{
+ public interface IConditional
+ {
+
+ bool CheckCondition(Player player);
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/API/Interfaces/Ability/IDuration.cs b/KruacentExiled/KE.CustomRoles/API/Interfaces/Ability/IDuration.cs
new file mode 100644
index 00000000..e68a58c5
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/API/Interfaces/Ability/IDuration.cs
@@ -0,0 +1,15 @@
+using Exiled.API.Features;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KE.CustomRoles.API.Interfaces.Ability
+{
+ internal interface IDuration
+ {
+ float Duration { get; }
+ void ActionAfterAbility(Player player);
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/API/Interfaces/Ability/IDynamicDescription.cs b/KruacentExiled/KE.CustomRoles/API/Interfaces/Ability/IDynamicDescription.cs
new file mode 100644
index 00000000..8914727f
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/API/Interfaces/Ability/IDynamicDescription.cs
@@ -0,0 +1,17 @@
+using Exiled.API.Features;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KE.CustomRoles.API.Interfaces.Ability
+{
+ public interface IDynamicDescription
+ {
+
+
+ void GetDescription(StringBuilder sb, Player player);
+
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/API/Interfaces/Ability/IDynamicName.cs b/KruacentExiled/KE.CustomRoles/API/Interfaces/Ability/IDynamicName.cs
new file mode 100644
index 00000000..1b4c31df
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/API/Interfaces/Ability/IDynamicName.cs
@@ -0,0 +1,15 @@
+using Exiled.API.Features;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KE.CustomRoles.API.Interfaces.Ability
+{
+ public interface IDynamicName
+ {
+
+ void GetName(StringBuilder sb, Player player);
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/API/Interfaces/IColor.cs b/KruacentExiled/KE.CustomRoles/API/Interfaces/IColor.cs
new file mode 100644
index 00000000..18493e58
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/API/Interfaces/IColor.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+
+namespace KE.CustomRoles.API.Interfaces
+{
+ public interface IColor
+ {
+
+ Color32 Color { get; }
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/API/Interfaces/ICustomIcon.cs b/KruacentExiled/KE.CustomRoles/API/Interfaces/ICustomIcon.cs
new file mode 100644
index 00000000..189f0071
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/API/Interfaces/ICustomIcon.cs
@@ -0,0 +1,17 @@
+using KE.Utils.API.GifAnimator;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KE.CustomRoles.API.Interfaces
+{
+ public interface ICustomIcon
+ {
+
+ public abstract TextImage IconName { get; }
+
+
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/API/Interfaces/IEffectImmunity.cs b/KruacentExiled/KE.CustomRoles/API/Interfaces/IEffectImmunity.cs
new file mode 100644
index 00000000..58786df8
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/API/Interfaces/IEffectImmunity.cs
@@ -0,0 +1,15 @@
+using Exiled.API.Enums;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KE.CustomRoles.API.Interfaces
+{
+ public interface IEffectImmunity
+ {
+
+ public abstract HashSet ImmuneEffects { get; }
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/API/Interfaces/IHealable.cs b/KruacentExiled/KE.CustomRoles/API/Interfaces/IHealable.cs
new file mode 100644
index 00000000..d7c207e9
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/API/Interfaces/IHealable.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KE.CustomRoles.API.Interfaces
+{
+ public interface IHealable
+ {
+
+ public abstract HashSet HealItem { get; }
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/API/Interfaces/ISCPPreferences.cs b/KruacentExiled/KE.CustomRoles/API/Interfaces/ISCPPreferences.cs
new file mode 100644
index 00000000..a3d74e4e
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/API/Interfaces/ISCPPreferences.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KE.CustomRoles.API.Interfaces
+{
+
+ ///
+ /// SCP custom role implementing this interface will allow them to have a preference slider like the vanilla scps
+ ///
+ public interface ISCPPreferences
+ {
+
+
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/Abilities/Airstrike.cs b/KruacentExiled/KE.CustomRoles/Abilities/Airstrike.cs
new file mode 100644
index 00000000..7114e33a
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/Abilities/Airstrike.cs
@@ -0,0 +1,106 @@
+using Exiled.API.Extensions;
+using Exiled.API.Features;
+using Exiled.API.Features.Attributes;
+using Exiled.API.Features.Items;
+using Exiled.API.Features.Pickups.Projectiles;
+using Exiled.API.Features.Toys;
+using Exiled.CustomRoles.API.Features;
+using KE.CustomRoles.API.Features;
+using KE.CustomRoles.API.Interfaces;
+using KE.CustomRoles.API.Interfaces.Ability;
+using MapGeneration;
+using MEC;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+using Light = Exiled.API.Features.Toys.Light;
+
+namespace KE.CustomRoles.Abilities
+{
+ public class Airstrike : KEAbilities, ICustomIcon, IConditional
+ {
+ public override string Name { get; } = "AirStrike";
+
+ public const string TranslationSomethingHere = "AirStrikeSomethingHere";
+ protected override Dictionary> SetTranslation()
+ {
+ return new Dictionary>()
+ {
+ ["en"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Airstrike",
+ [TranslationKeyDesc] = "Don't overuse it or your co-op will not be happy",
+ [TranslationSomethingHere] = "Something is in the way",
+ },
+ ["fr"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Bombardement",
+ [TranslationKeyDesc] = "Ne l'utilise pas trop sinon ton coop sera pas content",
+ [TranslationSomethingHere] = "Quelque chose gêne",
+ }
+ };
+ }
+ public override float Cooldown { get; } = 60f;
+
+ public float height = 1;
+ public Utils.API.GifAnimator.TextImage IconName => MainPlugin.Instance.icons["Airstrike"];
+
+
+ protected override bool AbilityUsed(Player player)
+ {
+ if (CheckValid(player, true))
+ {
+ return false;
+ }
+ SetPosition.TryGetTarget(player, out Vector3 target);
+ var l = Light.Create(target, null, null, true, Color.red);
+
+ ExplosiveGrenade grenade = (ExplosiveGrenade)Item.Create(ItemType.GrenadeHE, player);
+ grenade.ScpDamageMultiplier = 1;
+ grenade.FuseTime = 10;
+ Timing.CallDelayed(1.5f, () =>
+ {
+ Projectile gre = grenade.SpawnActive(target + (height - .5f) * Vector3.up);
+ //explode on collision
+ gre.GameObject.AddComponent().Init(player.GameObject, gre.Base);
+ l.Destroy();
+ });
+
+ return base.AbilityUsed(player);
+ }
+
+
+
+ private bool CheckValid(Player player, bool showMessage)
+ {
+ if (!SetPosition.TryGetTarget(player, out Vector3 target))
+ {
+ if (showMessage)
+ {
+ ShowEffectHint(player, SetPosition.TranslationNoTarget);
+ }
+ return false;
+ }
+
+ Physics.Linecast(target, target + height * Vector3.up, out RaycastHit hit);
+ if (hit.collider != null)
+ {
+ if (showMessage)
+ {
+ ShowEffectHint(player, TranslationSomethingHere);
+ }
+ return false;
+ }
+
+ return true;
+ }
+
+ public bool CheckCondition(Player player)
+ {
+ return CheckValid(player, false);
+ }
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/Abilities/Convert.cs b/KruacentExiled/KE.CustomRoles/Abilities/Convert.cs
new file mode 100644
index 00000000..84cea815
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/Abilities/Convert.cs
@@ -0,0 +1,105 @@
+/*using DrawableLine;
+using Exiled.API.Features;
+using Exiled.API.Features.Attributes;
+using Exiled.API.Features.Toys;
+using Exiled.CustomRoles.API.Features;
+using KE.CustomRoles.API.Features;
+using KE.CustomRoles.API.Interfaces;
+using MEC;
+using PlayerRoles;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace KE.CustomRoles.Abilities
+{
+ public class Convert : KEAbilities, ICustomIcon
+ {
+ public override string Name { get; } = "Convert";
+
+ protected override Dictionary> SetTranslation()
+ {
+ return new()
+ {
+ ["en"] = new()
+ {
+ [TranslationKeyName] = "Convert",
+ [TranslationKeyDesc] = "Convert a zombie to your team",
+ ["ConvertNobody"] = "But nobody's here",
+ ["ConvertSameTeam"] = "I know you don't like them, but they're in your team",
+ ["ConvertNonZombie"] = "That ain't a zombie",
+ ["ConvertSuccess"] = "New friend acquired!",
+ },
+ ["fr"] = new()
+ {
+ [TranslationKeyName] = "Convert",
+ [TranslationKeyDesc] = "Converti un zombie à la bonne foi",
+ ["ConvertNobody"] = "Mais personne n'est venu",
+ ["ConvertSameTeam"] = "Je sais que tu l'aimes pas mais il est bien avec toi",
+ ["ConvertNonZombie"] = "C'est pas un zombie ça",
+ ["ConvertSuccess"] = "Ami obtenu!",
+ }
+ };
+ }
+
+ public override float Cooldown { get; } = 10*60f;
+
+ public float MaxDistance { get; set; } = 15f;
+
+ public Utils.API.GifAnimator.TextImage IconName => MainPlugin.Instance.icons["Convert"];
+ protected override bool AbilityUsed(Player player)
+ {
+ Vector3 start = player.CameraTransform.position+ player.CameraTransform.forward*.2f;
+ Vector3 end = start + player.CameraTransform.forward * 5f;
+
+ Vector3 basePosition = player.Position + player.CameraTransform.rotation * Vector3.forward;
+ DrawableLines.IsDebugModeEnabled = MainPlugin.Instance.Config.Debug;
+ DrawableLines.ServerGenerateLine(10f,null,start, end);
+
+
+
+
+ if (!Physics.Linecast(start, end, out RaycastHit hit)) return false;
+
+
+ Player playerHit = Player.Get(hit.collider);
+
+ if (playerHit == null || playerHit == player)
+ {
+ MainPlugin.ShowEffectHint(player, "But nobody's here");
+ return false;
+ }
+
+
+ if (playerHit.Role.Side == player.Role.Side)
+ {
+ MainPlugin.ShowEffectHint(player, "I know you don't like them but they're in your team");
+ return false;
+ }
+
+ if (playerHit.IsScp && playerHit.Role != RoleTypeId.Scp0492)
+ {
+ MainPlugin.ShowEffectHint(player, "That ain't a zombie");
+ return false;
+ }
+
+
+ if (playerHit.IsScp)
+ {
+ playerHit.Role.Set(player.Role, RoleSpawnFlags.AssignInventory);
+ }
+ else
+ {
+ playerHit.Role.Set(player.Role, RoleSpawnFlags.None);
+ }
+
+ MainPlugin.ShowEffectHint(player, "New friend acquired!");
+ return base.AbilityUsed(player);
+ }
+
+ }
+}
+*/
\ No newline at end of file
diff --git a/KruacentExiled/KE.CustomRoles/Abilities/EmptyAbilities/NumberCheckpointEmptyAbility.cs b/KruacentExiled/KE.CustomRoles/Abilities/EmptyAbilities/NumberCheckpointEmptyAbility.cs
new file mode 100644
index 00000000..13424b4e
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/Abilities/EmptyAbilities/NumberCheckpointEmptyAbility.cs
@@ -0,0 +1,58 @@
+using Exiled.API.Features;
+using KE.CustomRoles.API.Features.Abilities;
+using KE.CustomRoles.API.Interfaces.Ability;
+using KE.CustomRoles.CR.Scientist;
+using KE.Utils.API.Features;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KE.CustomRoles.Abilities.EmptyAbilities
+{
+ public class NumberCheckpointEmptyAbility : EmptyAbility, IDynamicName
+ {
+ public override string Name { get; } = "NumberCheckpoints";
+
+ protected override Dictionary> SetTranslation()
+ {
+ return new Dictionary>()
+ {
+ ["en"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Remaining Checkpoint%S% : %remain%/%total%",
+ [TranslationKeyDesc] = "",
+ },
+ ["fr"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Checkpoint%S% restant : %remain%/%total% ",
+ [TranslationKeyDesc] = "",
+ }
+ };
+ }
+
+ public void GetName(StringBuilder sb, Player player)
+ {
+ int remaining = ZoneManager.GetNumberCheckpoints(player);
+ int total = ZoneManager.DoorToOpen.Count;
+ KELog.Debug("remainig"+remaining);
+
+ if(remaining > 0)
+ {
+ string n = GetTranslation(player, TranslationKeyName).Replace("%remain%", remaining.ToString()).Replace("%total%", total.ToString());
+ if(remaining > 1)
+ {
+ n = n.Replace("%S%", "s");
+ }
+ else
+ {
+ n = n.Replace("%S%", string.Empty);
+ }
+
+ sb.Append(n);
+ }
+ }
+
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/Abilities/Explode.cs b/KruacentExiled/KE.CustomRoles/Abilities/Explode.cs
new file mode 100644
index 00000000..768a9473
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/Abilities/Explode.cs
@@ -0,0 +1,97 @@
+using Exiled.API.Features;
+using Exiled.API.Features.Attributes;
+using Exiled.API.Features.Items;
+using Exiled.API.Features.Pools;
+using Exiled.CustomRoles.API.Features;
+using InventorySystem.Items.ThrowableProjectiles;
+using KE.CustomRoles.API.Features;
+using KE.CustomRoles.API.Interfaces;
+using KE.Utils.API.Features;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace KE.CustomRoles.Abilities
+{
+ public class Explode : KEAbilities, ICustomIcon
+ {
+ public override string Name { get; } = "Explode";
+
+ protected override Dictionary> SetTranslation()
+ {
+ return new Dictionary>()
+ {
+ ["en"] = new Dictionary ()
+ {
+ [TranslationKeyName] = "Explode",
+ [TranslationKeyDesc] = "You got an explosive belt",
+ },
+ ["fr"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Explosion",
+ [TranslationKeyDesc] = "Tu as une ceinture d'explosif autour de toi",
+ }
+ };
+ }
+
+
+ public override float Cooldown { get; } = 4*60f;
+
+ public Utils.API.GifAnimator.TextImage IconName => MainPlugin.Instance.icons["Explode"];
+
+ private HashSet GrenadesSerials = new HashSet();
+ protected override void SubscribeEvents()
+ {
+ GrenadesSerials = HashSetPool.Pool.Get();
+ Items.API.Events.ExplodeEvent.ExplodeDestructible += ExplodeEvent_ExplodeDestructible;
+
+ base.SubscribeEvents();
+ }
+
+ protected override void UnsubscribeEvents()
+ {
+
+ Items.API.Events.ExplodeEvent.ExplodeDestructible -= ExplodeEvent_ExplodeDestructible;
+ HashSetPool.Pool.Return(GrenadesSerials);
+ base.UnsubscribeEvents();
+ }
+
+ protected override bool AbilityUsed(Player player)
+ {
+ ExplosiveGrenade grenade = (ExplosiveGrenade)Item.Create(ItemType.GrenadeHE);
+
+
+ grenade.FuseTime = 0.2f;
+ grenade.SpawnActive(player.Position);
+
+
+ //idk why it's the next one but it is
+ ushort serial = (ushort) (grenade.Serial + 1);
+
+ GrenadesSerials.Add(serial);
+
+ return base.AbilityUsed(player);
+ }
+
+ private void ExplodeEvent_ExplodeDestructible(Items.API.Events.OnExplodeDestructibleEventsArgs obj)
+ {
+
+ ushort serial = obj.ExplosionGrenade.Info.Serial;
+ KELog.Debug("explode serial " + serial);
+
+
+
+ if (!GrenadesSerials.Contains(serial)) return;
+
+ obj.Damage /=3f;
+
+ GrenadesSerials.Remove(serial);
+
+ KELog.Debug("explode with "+ obj.Damage);
+
+ }
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/Abilities/FireAbilities/BlindingFlash.cs b/KruacentExiled/KE.CustomRoles/Abilities/FireAbilities/BlindingFlash.cs
new file mode 100644
index 00000000..c56e151a
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/Abilities/FireAbilities/BlindingFlash.cs
@@ -0,0 +1,47 @@
+using Exiled.API.Features;
+using Exiled.API.Features.Items;
+using Exiled.API.Features.Pickups.Projectiles;
+using System.Collections.Generic;
+
+namespace KE.CustomRoles.Abilities.FireAbilities
+{
+ public class BlindingFlash : FireAbilityBase
+ {
+ public override string Name { get; } = "BlindingFlash";
+
+ protected override Dictionary> SetTranslation()
+ {
+ return new Dictionary>()
+ {
+ ["en"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Blinding Flash",
+ [TranslationKeyDesc] = "Spawns an active flashbang at your feet",
+ },
+ ["fr"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Le soleil",
+ [TranslationKeyDesc] = "IS THAT A MOTHERFUCKER RED FLOOD REFERENCE??????????????",
+ }
+ };
+ }
+ public override float Cooldown { get; } = 30f;
+
+ public override int Cost => 50;
+
+
+
+ protected override bool LaunchedAbility(Player player)
+ {
+ FlashGrenade flashbangProjectile = Item.Create(ItemType.GrenadeFlash);
+
+ flashbangProjectile.FuseTime = .1f;
+ flashbangProjectile.SpawnActive(player.Position, player);
+ return true;
+ }
+
+
+
+ }
+
+}
diff --git a/KruacentExiled/KE.CustomRoles/Abilities/FireAbilities/FireAbilityBase.cs b/KruacentExiled/KE.CustomRoles/Abilities/FireAbilities/FireAbilityBase.cs
new file mode 100644
index 00000000..673728bb
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/Abilities/FireAbilities/FireAbilityBase.cs
@@ -0,0 +1,47 @@
+using Exiled.API.Extensions;
+using Exiled.API.Features;
+using KE.CustomRoles.API.Features.Abilities;
+using KE.Utils.API.CustomStats;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KE.CustomRoles.Abilities.FireAbilities
+{
+ public abstract class FireAbilityBase : KEAbilityCost
+ {
+ public override bool CanLaunchAbility(Player player)
+ {
+
+ if(player.GameObject.TryGetComponent(out var stat))
+ {
+ if (stat.TryGetModule(out var firestat))
+ {
+ Log.Debug($"cur = {firestat.CurValue} / {Cost}");
+ if (firestat.CurValue > Cost)
+ {
+ Log.Debug("enough");
+ firestat.AddAmount(-Cost);
+ return true;
+ }
+ else
+ {
+ Log.Debug("not enough");
+ return false;
+ }
+ }
+
+ }
+
+
+ Log.Debug("no fire stat");
+ return true;
+
+ }
+
+
+
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/Abilities/FireAbilities/FireStat.cs b/KruacentExiled/KE.CustomRoles/Abilities/FireAbilities/FireStat.cs
new file mode 100644
index 00000000..be0781ed
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/Abilities/FireAbilities/FireStat.cs
@@ -0,0 +1,88 @@
+using Exiled.API.Features;
+using KE.CustomRoles.API.Features;
+using KE.Utils.API.CustomStats;
+using KE.Utils.API.CustomStats.GUI;
+using System.Linq;
+using UnityEngine;
+
+namespace KE.CustomRoles.Abilities.FireAbilities
+{
+ public class FireStat : CustomStatBar, ICustomRoleStat
+ {
+
+ public float BaseCapacity => MaxValue;
+ public override float CurValue { get; set; }
+
+ public override float MinValue => 0;
+
+ private float maxvalue = 100;
+ public override float MaxValue
+ {
+ get
+ {
+ return maxvalue;
+ }
+ set
+ {
+ maxvalue = value;
+ }
+ }
+
+ public float FireRegen { get; set; } = 3f;
+
+ public override Color ColorBar => new Color32(252, 186, 3,byte.MaxValue);
+ public override Color ColorText => new Color32(255, 255, 255, byte.MaxValue);
+
+ public string CustomRole => "SCP106_SCP457";
+
+ public override int Width => 80;
+
+ public override char Segment => '|';
+
+
+
+
+ public override void ClassChanged()
+ {
+ maxvalue = 100f;
+ CurValue = 0f;
+ }
+
+ public override void Init(ReferenceHub ply)
+ {
+ base.Init(ply);
+ StatBarPosition = new Utils.API.Displays.DisplayMeow.Placements.SCP106StatBarPosition();
+
+ }
+
+ public override bool Check()
+ {
+ return false;
+ Player player = Player.Get(Hub);
+ if (!KECustomRole.Get(player).Any(role => KECustomRole.Get(CustomRole) == role))
+ {
+ return false;
+ }
+
+ return base.Check();
+ }
+
+ public override void Update()
+ {
+ float num = FireRegen * Time.deltaTime;
+ if (num > 0f)
+ {
+ if (CurValue < MaxValue)
+ {
+ CurValue = Mathf.MoveTowards(CurValue, MaxValue, num);
+ }
+ }
+ else if (CurValue > 0f)
+ {
+ CurValue += num;
+ }
+ base.Update();
+ }
+
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/Abilities/FireAbilities/Fireball.cs b/KruacentExiled/KE.CustomRoles/Abilities/FireAbilities/Fireball.cs
new file mode 100644
index 00000000..f026511c
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/Abilities/FireAbilities/Fireball.cs
@@ -0,0 +1,168 @@
+using Discord;
+using Exiled.API.Features;
+using Exiled.API.Features.Doors;
+using Exiled.API.Features.Roles;
+using Exiled.API.Features.Toys;
+using Exiled.API.Interfaces;
+using Exiled.Events.EventArgs.Player;
+using Exiled.Events.Patches.Events.Player;
+using KE.CustomRoles.API.Features;
+using MEC;
+using PlayerStatsSystem;
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+using Light = Exiled.API.Features.Toys.Light;
+
+namespace KE.CustomRoles.Abilities.FireAbilities
+{
+ public class Fireball : FireAbilityBase
+ {
+ public override string Name { get; } = "Fireball";
+ protected override Dictionary> SetTranslation()
+ {
+ return new Dictionary>()
+ {
+ ["en"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Fireball",
+ [TranslationKeyDesc] = "I cast Fireball",
+ },
+ ["fr"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Boule de feur",
+ [TranslationKeyDesc] = "Attends j'ai fait une faute là non?",
+ }
+ };
+ }
+ public override int Cost => 10;
+
+ public override float Cooldown { get; } = 0f;
+
+
+ public static readonly CustomReasonDamageHandler BallDamage = new CustomReasonDamageHandler("Burned to death", 25, string.Empty);
+ public const int MAX_BALLS = 5;
+ private static readonly Color ballColor = new Color(2, 1.08f, 0, .75f);
+ private Dictionary _activeBalls = new Dictionary();
+
+ protected override bool LaunchedAbility(Player player)
+ {
+ if (!_activeBalls.ContainsKey(player))
+ {
+ _activeBalls.Add(player, 0);
+ }
+
+
+ if (_activeBalls[player] >= MAX_BALLS)
+ {
+ ShowEffectHint(player, "too much balls");
+ return true;
+ }
+
+
+ _activeBalls[player]++;
+ Timing.RunCoroutine(LaunchingAttack(player));
+ return true;
+
+ }
+
+
+ private float smooth = .01f;
+
+ private IEnumerator LaunchingAttack(Player player)
+ {
+ Vector3 initpos = player.Position;
+ Quaternion direction = player.ReferenceHub.PlayerCameraReference.rotation;
+
+
+ Log.Debug(direction.eulerAngles);
+ bool attackTouchedSomething = false;
+
+ Light light = Light.Create(initpos, direction.eulerAngles, null, false);
+ light.Color = ballColor;
+ light.Intensity = 1f;
+ Primitive primitive = Primitive.Create(initpos, direction.eulerAngles, null, false);
+ primitive.Collidable = false;
+ primitive.Color = ballColor;
+ primitive.Spawn();
+ light.Spawn();
+ Vector3 nextPos;
+
+ int fallback = Mathf.CeilToInt(100/ smooth);
+ Log.Debug("fallback=" + fallback);
+ while (!attackTouchedSomething && fallback > 0)
+ {
+ nextPos = primitive.Position + primitive.Rotation * new Vector3(0, 0, 10 * smooth);
+
+
+ if (Physics.Linecast(primitive.Position, nextPos, out RaycastHit hit))
+ {
+ //spawn mtf looking at central gate
+ if (hit.collider.gameObject.name != "VolumeOverrideTunnel")
+ {
+ attackTouchedSomething = true;
+ }
+
+ }
+
+ Collider[] colliders = Physics.OverlapSphere(primitive.Position, .5f);
+
+
+
+ foreach (Collider collider in colliders)
+ {
+
+ attackTouchedSomething = attackTouchedSomething || ProcessHit(player, collider);
+
+ }
+
+
+
+
+
+
+
+ Log.Debug($"current pos = {primitive.Position} next pos = {nextPos}");
+ yield return Timing.WaitForSeconds(smooth);
+ primitive.Position = nextPos;
+ light.Position = nextPos;
+ fallback--;
+ }
+ _activeBalls[player]--;
+ primitive.Destroy();
+ light.Destroy();
+
+
+ }
+
+
+ private bool ProcessHit(Player attacker,Collider collider)
+ {
+ bool result = false;
+ Player playerhit = Player.Get(collider);
+ if (playerhit != null && playerhit.Role.Side != attacker.Role.Side)
+ {
+ playerhit.Hurt(BallDamage);
+ attacker.ShowHitMarker();
+ result = true;
+
+ }
+
+ Door doorhit = Door.Get(collider.gameObject);
+ if (doorhit != null && doorhit is IDamageableDoor damageable && !damageable.IsDestroyed)
+ {
+ damageable.Break();
+ attacker.ShowHitMarker();
+ result = true;
+ }
+
+ return result;
+ }
+
+
+
+
+
+ }
+
+}
diff --git a/KruacentExiled/KE.CustomRoles/Abilities/ForceOpen.cs b/KruacentExiled/KE.CustomRoles/Abilities/ForceOpen.cs
new file mode 100644
index 00000000..7ee97b0c
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/Abilities/ForceOpen.cs
@@ -0,0 +1,128 @@
+using Exiled.API.Enums;
+using Exiled.API.Extensions;
+using Exiled.API.Features;
+using Exiled.API.Features.Attributes;
+using Exiled.API.Features.Doors;
+using Exiled.CustomRoles.API.Features;
+using Exiled.Events.EventArgs.Player;
+using KE.CustomRoles.API.Features;
+using KE.CustomRoles.API.Features.Abilities;
+using KE.CustomRoles.API.Interfaces;
+using PlayerRoles;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace KE.CustomRoles.Abilities
+{
+ public class ForceOpen : KEAbilityLimited, ICustomIcon
+ {
+ public override string Name { get; } = "ForceOpen";
+ protected override Dictionary> SetTranslation()
+ {
+ return new Dictionary>()
+ {
+ ["en"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Force open",
+ [TranslationKeyDesc] = "Force open a door",
+ ["ForceOpenFail"] = "You failed opening the door and lost %HP% HP!",
+ },
+ ["fr"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Forcer une porte",
+ [TranslationKeyDesc] = "Force une porte (grosse description)",
+ ["ForceOpenFail"] = "Tu as raté d'ouvrir la porte et a perdu %HP% HP!",
+ }
+ };
+ }
+ public Utils.API.GifAnimator.TextImage IconName => MainPlugin.Instance.icons["ForceOpen"];
+ public override float Cooldown { get; } = 30;
+ public override int Uses { get; } = 5;
+
+ private Dictionary abilityActivated = new Dictionary();
+ public static readonly TimeSpan MaxTime = new TimeSpan(0, 0, 30);
+
+ protected override bool LaunchedAbility(Player player)
+ {
+
+ abilityActivated[player] = DateTime.Now;
+ return base.LaunchedAbility(player);
+ }
+
+ private void InteractingDoor(InteractingDoorEventArgs ev)
+ {
+ Player player = ev.Player;
+ if (ev.IsAllowed) return;
+ if (!abilityActivated.ContainsKey(player)) return;
+ if (DateTime.Now > abilityActivated[player] + MaxTime) return;
+
+ if (ev.Door.IsOpen) return;
+ if (ev.Door.DoorLockType >= DoorLockType.Lockdown079) return;
+
+
+ int successRate;
+ int damage;
+
+ if (ev.Door is Gate)
+ {
+ successRate = 100;
+ damage = 20;
+ }
+ else if (ev.Door.Type.IsCheckpoint())
+ {
+ successRate = 50;
+ damage = 10;
+ }
+ else
+ {
+ successRate = 50;
+ damage = 5;
+ }
+
+
+ int proba = UnityEngine.Random.Range(0, 101);
+
+ if (proba <= successRate)
+ {
+ if(ev.Door is Gate gate)
+ {
+ gate.TryPry(player);
+ }
+ else
+ {
+ ev.IsAllowed = true;
+ }
+ }
+ else
+ {
+
+ string text = GetTranslation(player, "ForceOpenFail").Replace("%HP%",damage.ToString());
+ ShowEffectHint(player, text,0f);
+ player.Hurt(damage, "Door too stronk");
+ }
+
+
+ abilityActivated.Remove(player);
+
+
+ }
+
+
+ protected override void SubscribeEvents()
+ {
+ Exiled.Events.Handlers.Player.InteractingDoor += InteractingDoor;
+ base.SubscribeEvents();
+ }
+
+ protected override void UnsubscribeEvents()
+ {
+ Exiled.Events.Handlers.Player.InteractingDoor -= InteractingDoor;
+ base.UnsubscribeEvents();
+ }
+
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/Abilities/RedMist/EgoAbility.cs b/KruacentExiled/KE.CustomRoles/Abilities/RedMist/EgoAbility.cs
new file mode 100644
index 00000000..d25606f5
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/Abilities/RedMist/EgoAbility.cs
@@ -0,0 +1,67 @@
+using Exiled.API.Features;
+using KE.CustomRoles.API.Features;
+using KE.CustomRoles.API.Features.Abilities;
+using KE.CustomRoles.API.Interfaces.Ability;
+using KE.CustomRoles.CR.MTF.RedMist;
+using KE.Utils.API.Features;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace KE.CustomRoles.Abilities.RedMist
+{
+ public abstract class EgoAbility : NeedCompAbility, IConditional
+ {
+
+
+ protected sealed override bool AddCompIfMissing => false;
+ protected abstract NeedActive NeedEGOActive { get; }
+
+ public override bool CanLaunchAbility(Player player, out EGO comp)
+ {
+
+
+
+ bool active = base.CanLaunchAbility(player,out comp);
+
+ if (CheckItem(player))
+ {
+ return false;
+ }
+
+
+ if (NeedEGOActive == NeedActive.NeedActive)
+ {
+ return active;
+ }
+
+ if (NeedEGOActive == NeedActive.NeedNotActive)
+ {
+ return !active;
+ }
+
+ return true;
+ }
+
+ protected static bool CheckItem(Player player)
+ {
+ return player.CurrentItem is null || player.CurrentItem.Type != ItemType.SCP1509;
+ }
+
+ public bool CheckCondition(Player player)
+ {
+ return CanLaunchAbility(player, out _);
+ }
+
+ public enum NeedActive
+ {
+ NeedActive,
+ NeedNotActive,
+ Either,
+ }
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/Abilities/RedMist/GreaterSplitHorizontal/AttackGreaterSplitComp.cs b/KruacentExiled/KE.CustomRoles/Abilities/RedMist/GreaterSplitHorizontal/AttackGreaterSplitComp.cs
new file mode 100644
index 00000000..922defbf
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/Abilities/RedMist/GreaterSplitHorizontal/AttackGreaterSplitComp.cs
@@ -0,0 +1,294 @@
+using CustomPlayerEffects;
+using DrawableLine;
+using Exiled.API.Enums;
+using Exiled.API.Features;
+using HintServiceMeow.Core.Enum;
+using KE.Utils.API.Displays.DisplayMeow;
+using KE.Utils.API.Displays.DisplayMeow.Placements;
+using KE.Utils.API.Features;
+using MEC;
+using PlayerRoles;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using TMPro;
+using UnityEngine;
+
+namespace KE.CustomRoles.Abilities.RedMist.GreaterSplitHorizontal
+{
+ public class AttackGreaterSplitComp : MonoBehaviour
+ {
+
+ private static bool Debug => MainPlugin.Instance.Config.Debug;
+ private Player player;
+ private bool currentUsing;
+ private bool attacking;
+ private GreaterSplitHorizontal ability;
+ public const float TimeAttack = 3;
+ private readonly float StartTimeAttack;
+ private readonly float EndTimeAttack;
+
+ private const float HighPowerRequirement = .1f;
+ private const float MedPowerRequirement = .3f;
+
+
+ public AttackGreaterSplitComp()
+ {
+
+ StartTimeAttack = Mathf.Abs(TimeAttack) / 2f;
+ EndTimeAttack = -StartTimeAttack;
+
+ }
+ private static HintPosition Position = new DebugPosition();
+ public void Init(Player player, GreaterSplitHorizontal ability)
+ {
+ this.player = player;
+ this.ability = ability;
+ currentUsing = false;
+ attacking = false;
+
+ if (Debug)
+ {
+ DisplayHandler.Instance.CreateAuto(player, (args) => GetDebug(), Position.HintPlacement, HintSyncSpeed.Fastest);
+ }
+ }
+
+ private void OnDestroy()
+ {
+
+ if (player != null)
+ {
+ DisplayHandler.Instance.RemoveHint(player, Position.HintPlacement);
+ }
+
+ }
+
+
+ private string GetDebug()
+ {
+ float power = GetPower(time);
+ return "time = " + time + "\npower = " + power + '(' + GetPowerDebug(power) + ')';
+
+ }
+
+ private string GetPowerDebug(float power)
+ {
+ if (power <= StartTimeAttack * HighPowerRequirement)
+ {
+ return "high";
+ }
+ else if (power <= StartTimeAttack * MedPowerRequirement)
+ {
+ return "medium";
+ }
+ else
+ {
+ return "low";
+ }
+ }
+
+
+ private float time;
+ public void StartAttack()
+ {
+ currentUsing = true;
+ time = StartTimeAttack;
+ }
+
+
+ public bool OnTriggerAttack()
+ {
+ if (currentUsing)
+ {
+ attacking = true;
+
+ }
+ return attacking;
+ }
+
+
+ private void Update()
+ {
+ try
+ {
+ UpdateAttack();
+ }
+ catch (Exception e)
+ {
+ Log.Error(e);
+ }
+ }
+
+ private void UpdateAttack()
+ {
+ if (!currentUsing) return;
+
+
+ time -= Timing.DeltaTime;
+
+
+
+ if (time <= EndTimeAttack || attacking)
+ {
+ if (ability.Check(player))
+ {
+ float power = GetPower(time);
+
+ LaunchedAttack(power);
+ }
+ attacking = false;
+ currentUsing = false;
+ }
+ }
+
+
+ public float GetPower(float time)
+ {
+ return Mathf.Abs(time);
+ }
+
+ private void LaunchedAttack(float power)
+ {
+
+ if (player == null || player.GameObject == null) return;
+ KELog.Debug($"power (0~{StartTimeAttack})= {power}");
+
+ float angle = 1f;
+ float range = 1f;
+ bool checkBack = false;
+
+
+ if (power <= StartTimeAttack * HighPowerRequirement)
+ {
+ angle = 60f;
+ range = 7f;
+ checkBack = true;
+ KELog.Debug("high power");
+ }
+ else if (power <= StartTimeAttack * MedPowerRequirement)
+ {
+ angle = 60f;
+ range = 7f;
+ KELog.Debug("med power");
+ }
+ else
+ {
+ angle = 30f;
+ range = 5f;
+ KELog.Debug("low power");
+ }
+
+
+
+
+
+
+ Vector3 direction = player.Transform.forward.NormalizeIgnoreY();
+ Vector3 directionBack = -player.Transform.forward.NormalizeIgnoreY();
+
+
+
+ Vector3 position = player.Position;
+
+ if (Debug)
+ {
+ CheckPoint(position, position, direction, range, angle);
+ if (checkBack)
+ {
+ CheckPoint(position, position, directionBack, range, angle);
+ }
+ }
+
+
+ foreach (Player target in Player.List)
+ {
+
+ Vector3 targetPosition = target.Position;
+ KELog.Debug("fornt");
+
+
+
+ if (!CheckPoint(targetPosition, position, direction, range, angle) & !(checkBack && CheckPoint(targetPosition, position, directionBack, range, angle)))
+ {
+ continue;
+ }
+ KELog.Debug("player " + target.Nickname);
+ if (player == target)
+ {
+ continue;
+ }
+
+
+ KELog.Debug("linecast");
+ if (Physics.Linecast(position, targetPosition, out var hitInfo, PlayerRolesUtils.AttackMask))
+ {
+ continue;
+ }
+
+ KELog.Debug("add damage");
+
+
+ target.Hurt(GreaterSplitHorizontal.Damage, DamageType.Scp1509);
+
+ }
+
+ }
+
+ public const float height = 1f;
+ private bool CheckPoint(Vector3 point, Vector3 center, Vector3 direction, float size, float halfAngle)
+ {
+ Vector2 position = new Vector2(point.x - center.x, point.z - center.z);
+ Vector3 playerPosition = player.Position;
+ float sqrMag = position.sqrMagnitude;
+
+ float rad = halfAngle * Mathf.Deg2Rad;
+
+ Vector2 dir = new Vector2(direction.x, direction.z);
+
+ dir /= Mathf.Sqrt(dir.sqrMagnitude);
+ Vector2 leftDir = new Vector2(dir.x * Mathf.Cos(rad) - dir.y * Mathf.Sin(rad), dir.x * Mathf.Sin(rad) + dir.y * Mathf.Cos(rad));
+ Vector2 rightDir = new Vector2(dir.x * Mathf.Cos(-rad) - dir.y * Mathf.Sin(-rad), dir.x * Mathf.Sin(-rad) + dir.y * Mathf.Cos(-rad));
+
+ Vector3 leftPoint = center + new Vector3(leftDir.x, 0, leftDir.y) * size;
+ Vector3 rightPoint = center + new Vector3(rightDir.x, 0, rightDir.y) * size;
+
+ Vector3 frontPoint = playerPosition + direction * size;
+ if (Debug)
+ {
+ DrawableLines.IsDebugModeEnabled = true;
+ DrawableLines.GenerateLine(10, Color.yellow, playerPosition, leftPoint, frontPoint, rightPoint, player.Position);
+ }
+
+ if (sqrMag <= 0.0001f)
+ return false;
+
+ float radius = Mathf.Sqrt(sqrMag);
+ if (radius > size)
+ return false;
+
+
+
+ Vector2 pointDir = position / radius;
+
+ float cosThreshold = Mathf.Cos(halfAngle * Mathf.Deg2Rad);
+
+ float dot = Vector2.Dot(dir, pointDir);
+
+
+
+
+
+ KELog.Debug("center =" + center);
+ KELog.Debug("left =" + leftPoint);
+ KELog.Debug("rightPoint =" + rightPoint);
+
+
+
+ return dot >= cosThreshold && point.y < center.y + height && point.y > center.y - height;
+ }
+
+ }
+
+}
diff --git a/KruacentExiled/KE.CustomRoles/Abilities/RedMist/GreaterSplitHorizontal/DebugPosition.cs b/KruacentExiled/KE.CustomRoles/Abilities/RedMist/GreaterSplitHorizontal/DebugPosition.cs
new file mode 100644
index 00000000..3a2091a6
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/Abilities/RedMist/GreaterSplitHorizontal/DebugPosition.cs
@@ -0,0 +1,18 @@
+using HintServiceMeow.Core.Enum;
+using KE.Utils.API.Displays.DisplayMeow.Placements;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KE.CustomRoles.Abilities.RedMist.GreaterSplitHorizontal
+{
+ public class DebugPosition : HintPosition
+ {
+ public override float Xposition => 800;
+ public override float Yposition => 400;
+ public override HintAlignment HintAlignment => HintAlignment.Center;
+ public override string Name => "DebugGreaterSplit";
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/Abilities/RedMist/GreaterSplitHorizontal/GreaterSplitHorizontal.cs b/KruacentExiled/KE.CustomRoles/Abilities/RedMist/GreaterSplitHorizontal/GreaterSplitHorizontal.cs
new file mode 100644
index 00000000..7ff68b7c
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/Abilities/RedMist/GreaterSplitHorizontal/GreaterSplitHorizontal.cs
@@ -0,0 +1,135 @@
+using DrawableLine;
+using Exiled.API.Enums;
+using Exiled.API.Features;
+using Exiled.CreditTags.Features;
+using Exiled.Events.EventArgs.Scp1509;
+using HintServiceMeow.Core.Enum;
+using InventorySystem.Items.MicroHID.Modules;
+using KE.CustomRoles.CR.MTF.RedMist;
+using KE.CustomRoles.CR.SCP;
+using KE.Utils.API.Displays.DisplayMeow;
+using KE.Utils.API.Displays.DisplayMeow.Placements;
+using KE.Utils.API.Features;
+using MEC;
+using PlayerRoles;
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+namespace KE.CustomRoles.Abilities.RedMist.GreaterSplitHorizontal
+{
+ public class GreaterSplitHorizontal : EgoAbility
+ {
+ public const string FailEgo = "GreaterSplitFailEGO";
+ public const string FailWeapon = "GreaterSplitFailWeapon";
+
+
+ public override string Name { get; } = "GreaterSplitHorizontal";
+ protected override Dictionary> SetTranslation()
+ {
+ return new Dictionary>()
+ {
+ ["en"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Greater Split : Horizontal",
+ [TranslationKeyDesc] = "Manifest your E.G.O, gain powerful buff but rapid health drain.\nCan be deactivated anytime",
+ [FailEgo] = "You need to manifest your E.G.O. first",
+ [FailWeapon] = "You need your weapon",
+ },
+ ["fr"] = new Dictionary()
+ {
+ [TranslationKeyName] = "todo",
+ [TranslationKeyDesc] = "todo",
+ [FailEgo] = "todo",
+ [FailWeapon] = "todo",
+ }
+ };
+ }
+ public override float Cooldown { get; } = 0f;
+
+ protected override NeedActive NeedEGOActive => NeedActive.NeedActive;
+
+ public const float Damage = 200;
+
+
+ public static readonly LayerMasks Mask = LayerMasks.Scp173Teleport | LayerMasks.Glass;
+ protected override void AbilityAdded(Player player)
+ {
+
+ if (!player.GameObject.TryGetComponent(out var comp))
+ {
+ comp = player.GameObject.AddComponent();
+ }
+ comp.Init(player, this);
+
+
+ base.AbilityAdded(player);
+ }
+
+ protected override void AbilityRemoved(Player player)
+ {
+ if (player.GameObject.TryGetComponent(out var comp))
+ {
+ UnityEngine.Object.Destroy(comp);
+ }
+ base.AbilityRemoved(player);
+ }
+
+
+ protected override void SubscribeEvents()
+ {
+ Exiled.Events.Handlers.Scp1509.TriggeringAttack += OnTriggeringAttack;
+
+ base.SubscribeEvents();
+ }
+
+ protected override void UnsubscribeEvents()
+ {
+ Exiled.Events.Handlers.Scp1509.TriggeringAttack -= OnTriggeringAttack;
+ base.UnsubscribeEvents();
+ }
+
+
+
+
+
+ private void OnTriggeringAttack(TriggeringAttackEventArgs ev)
+ {
+ Player player = ev.Player;
+ if (!Check(player)) return;
+ if (!ev.IsAllowed) return;
+ if (player.GameObject.TryGetComponent(out var comp))
+ {
+ ev.IsAllowed = !comp.OnTriggerAttack();
+
+ }
+ ev.Scp1509.NextResurrectTime = float.MaxValue;
+ }
+
+
+
+
+
+ protected override bool LaunchedAbility(Player player,EGO ego)
+ {
+ KELog.Debug("check weapopgn");
+ if(player.CurrentItem is null || player.CurrentItem.Type != ItemType.SCP1509)
+ {
+ ShowEffectHint(player, FailWeapon);
+ return false;
+ }
+
+
+ if (player.GameObject.TryGetComponent(out var comp))
+ {
+ comp.StartAttack();
+ }
+
+
+ return true;
+ }
+
+
+
+
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/Abilities/RedMist/OnRush.cs b/KruacentExiled/KE.CustomRoles/Abilities/RedMist/OnRush.cs
new file mode 100644
index 00000000..e258ae51
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/Abilities/RedMist/OnRush.cs
@@ -0,0 +1,178 @@
+using Exiled.API.Enums;
+using Exiled.API.Features;
+using Exiled.API.Features.DamageHandlers;
+using InventorySystem.Items.MicroHID.Modules;
+using KE.CustomRoles.CR.MTF.RedMist;
+using KE.Utils.API.Features;
+using PlayerRoles;
+using System.Collections.Generic;
+using UnityEngine;
+namespace KE.CustomRoles.Abilities.RedMist
+{
+ public class OnRush : EgoAbility
+ {
+ public override string Name { get; } = "OnRush";
+ protected override Dictionary> SetTranslation()
+ {
+ return new Dictionary>()
+ {
+ ["en"] = new Dictionary()
+ {
+ [TranslationKeyName] = "On Rush",
+ [TranslationKeyDesc] = "Manifest your E.G.O, gain powerful buff but rapid health drain.\nCan be deactivated anytime",
+ ["OnRushFailEGO"] = "You need to manifest your E.G.O. first",
+ ["OnRushFailWeapon"] = "You need your weapon",
+ },
+ ["fr"] = new Dictionary()
+ {
+ [TranslationKeyName] = "todo",
+ [TranslationKeyDesc] = "todo",
+ ["OnRushFailEGO"] = "todo",
+ ["OnRushFailWeapon"] = "todo",
+ }
+ };
+ }
+ public override float Cooldown { get; } = 120f;
+
+ protected override NeedActive NeedEGOActive => NeedActive.Either;
+
+ public const float Damage = 100;
+
+ public const float MaxDistance = 5;
+
+
+ private Collider[] NonAlloc = new Collider[64];
+
+ public static readonly LayerMasks Mask = LayerMasks.Scp173Teleport | LayerMasks.Glass;
+ protected override bool LaunchedAbility(Player player,EGO ego)
+ {
+
+
+ KELog.Debug("check weapopgn");
+
+
+
+ if(player.CurrentItem is null || player.CurrentItem.Type != ItemType.SCP1509)
+ {
+ //show OnRushFailWeapon
+ return false;
+ }
+
+
+ float size = 1;
+
+ Vector3 feetposition = player.Position- (Vector3.up*player.Scale.y)/2 + player.CameraTransform.forward*.1f;
+ Vector3 position = player.Position + player.CameraTransform.forward*.1f;
+
+
+ DrawSphere(position, size, Color.green);
+ DrawSphere(feetposition, size, Color.green);
+ Vector3 direction = player.CameraTransform.forward;
+
+ direction = direction.NormalizeIgnoreY();
+
+
+ Vector3 end = position + direction * MaxDistance;
+ Vector3 feetend = feetposition + direction * MaxDistance;
+ DrawSphere(end, size, Color.yellow);
+ DrawSphere(feetend, size, Color.yellow);
+ Vector3 teleport = end;
+
+ Vector3 wallPoint = end;
+
+
+
+
+ if (Physics.Raycast(position, direction,out RaycastHit wallHit, MaxDistance, (int)Mask)
+ | Physics.Raycast(feetposition, direction, out RaycastHit wallHitFeet, MaxDistance, (int)Mask))
+ {
+ Draw.Sphere(wallHit.point, Quaternion.identity, Vector3.one / 8f, Color.blue, 10, Player.Enumerable);
+ Draw.Sphere(wallHitFeet.point, Quaternion.identity, Vector3.one / 8f, Color.blue, 10, Player.Enumerable);
+
+ if(Vector3.Distance(wallHit.point,position) <= Vector3.Distance(wallHitFeet.point, feetposition))
+ {
+ teleport = wallHit.point + direction * (-1f);
+ }
+ else
+ {
+ teleport = wallHitFeet.point + direction * (-1f);
+ }
+ Vector3 directionToPoint = teleport - player.Position;
+ bool behind = Vector3.Dot(direction, directionToPoint) < 0f;
+
+
+ wallPoint = teleport;
+ if (behind)
+ {
+ teleport = Vector3.zero;
+ }
+
+
+
+ }
+
+ int detect = Physics.OverlapCapsuleNonAlloc(position, wallPoint, size, NonAlloc, HitregUtils.DetectionMask);
+
+ for (int i = 0;i < detect;i++)
+ {
+ Collider collider = NonAlloc[i];
+
+ //KELog.Debug("hit collider ="+collider);
+
+ if (collider.TryGetComponent(out var destructible) &&
+ (!Linecast(position, destructible?.CenterOfMass ?? Vector3.zero, out var hitInfo, PlayerRolesUtils.AttackMask)
+ || collider == hitInfo.collider))
+ {
+ Player target = Player.Get(collider);
+
+ if(target is null || target != player)
+ {
+ DrawSphere(collider.transform.position,.2f, Color.red);
+ }
+
+
+ if (destructible != null)
+ {
+ PlayerStatsSystem.DamageHandlerBase handler = new GenericDamageHandler(target, player, Damage, DamageType.Scp1509, new DamageHandlerBase.CassieAnnouncement("")).Base;
+ destructible.Damage(Damage, handler, destructible.CenterOfMass);
+ }
+ }
+ }
+
+ DrawSphere(teleport,.25f, Color.magenta);
+
+ if(teleport != Vector3.zero)
+ {
+ player.Teleport(teleport);
+ }
+
+ return true;
+ }
+
+
+ private static bool Debug => MainPlugin.Configs.Debug;
+ public static bool Linecast(Vector3 start,Vector3 end,out RaycastHit hit,int layerMask)
+ {
+ if (Debug)
+ {
+ //Draw.Line(start, end, Color.red, 10, Player.Enumerable);
+ }
+
+
+
+ return Physics.Linecast(start, end, out hit, layerMask);
+
+ }
+
+ public static void DrawSphere(Vector3 position, float size,Color color)
+ {
+ if (Debug)
+ {
+ Draw.Sphere(position, Quaternion.identity, Vector3.one * size, color, 10, Player.Enumerable);
+ }
+
+ }
+
+
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/Abilities/RedMist/Spear/Spear.cs b/KruacentExiled/KE.CustomRoles/Abilities/RedMist/Spear/Spear.cs
new file mode 100644
index 00000000..950447ff
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/Abilities/RedMist/Spear/Spear.cs
@@ -0,0 +1,65 @@
+using DrawableLine;
+using Exiled.API.Enums;
+using Exiled.API.Features;
+using Exiled.API.Features.DamageHandlers;
+using InventorySystem.Items.MicroHID.Modules;
+using KE.CustomRoles.CR.MTF.RedMist;
+using KE.Utils.API.Features;
+using PlayerRoles;
+using System.Collections.Generic;
+using UnityEngine;
+using static PlayerRoles.PlayableScps.VisionInformation;
+
+namespace KE.CustomRoles.Abilities.RedMist.Spear
+{
+ public class Spear : EgoAbility
+ {
+ public const string FailWeapon = "SpearFailWeapon";
+ public override string Name { get; } = "Spear";
+ protected override Dictionary> SetTranslation()
+ {
+ return new Dictionary>()
+ {
+ ["en"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Spear",
+ [TranslationKeyDesc] = "Manifest your E.G.O, gain powerful buff but rapid health drain.\nCan be deactivated anytime",
+ [FailWeapon] = "You need your weapon",
+ },
+ ["fr"] = new Dictionary()
+ {
+ [TranslationKeyName] = "todo",
+ [TranslationKeyDesc] = "todo",
+ [FailWeapon] = "todo",
+ }
+ };
+ }
+ public override float Cooldown { get; } = 0f;
+
+ protected override NeedActive NeedEGOActive => NeedActive.Either;
+
+ public const float Damage = 200;
+
+
+
+
+ protected override bool LaunchedAbility(Player player, EGO ego)
+ {
+ KELog.Debug("check weapopgn");
+ if (player.CurrentItem is null || player.CurrentItem.Type != ItemType.SCP1509)
+ {
+ ShowEffectHint(player, FailWeapon);
+ return false;
+ }
+
+ SpearProjectile.Create(player, player.CameraTransform.forward);
+
+
+ return true;
+ }
+
+
+
+
+ }
+}
\ No newline at end of file
diff --git a/KruacentExiled/KE.CustomRoles/Abilities/RedMist/Spear/SpearComp.cs b/KruacentExiled/KE.CustomRoles/Abilities/RedMist/Spear/SpearComp.cs
new file mode 100644
index 00000000..05786b39
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/Abilities/RedMist/Spear/SpearComp.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace KE.CustomRoles.Abilities.RedMist.Spear
+{
+ public class SpearComp : MonoBehaviour
+ {
+
+
+
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/Abilities/RedMist/Spear/SpearProjectile.cs b/KruacentExiled/KE.CustomRoles/Abilities/RedMist/Spear/SpearProjectile.cs
new file mode 100644
index 00000000..1899f355
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/Abilities/RedMist/Spear/SpearProjectile.cs
@@ -0,0 +1,153 @@
+using DrawableLine;
+using Exiled.API.Enums;
+using Exiled.API.Features;
+using Exiled.API.Features.Toys;
+using KE.Utils.API.Features;
+using PlayerRoles;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using TMPro;
+using UnityEngine;
+using Utils;
+
+namespace KE.CustomRoles.Abilities.RedMist.Spear
+{
+ public class SpearProjectile : MonoBehaviour
+ {
+ public static float Damage => Spear.Damage;
+ private Primitive _primitive;
+ private Vector3 _direction;
+ private Player _player;
+
+ public const float Speed = 30;
+
+ public const float TimeBeforeMoving = 2f;
+ private float timeBeforeMove = 0f;
+
+ public void Init(Primitive primitive,Player player, Vector3 direction)
+ {
+ _player = player;
+ _direction = direction;
+ _primitive = primitive;
+ DrawableLines.IsDebugModeEnabled = true;
+
+ }
+
+ public const byte Fallback = 200;
+ private byte fallback = 0;
+ private const float SpeedRotation = 90f;
+
+ private static readonly Vector3 SizeProjectile = new Vector3(1,.2f,.2f);
+ public static SpearProjectile Create(Player player, Vector3 direction)
+ {
+ var prim = Primitive.Create(player.Position, null, SizeProjectile, false);
+ prim.MovementSmoothing = 0;
+ prim.Collidable = false;
+ prim.Spawn();
+
+ SpearProjectile spear = prim.GameObject.AddComponent();
+
+ spear.Init(prim,player, direction);
+
+ return spear;
+
+ }
+
+
+ private Collider[] NonAlloc = new Collider[64];
+ private void Update()
+ {
+ if(fallback >= Fallback || _player == null || _player.GameObject == null)
+ {
+ Destroy();
+ return;
+ }
+
+ if (timeBeforeMove <= TimeBeforeMoving)
+ {
+ timeBeforeMove += Time.deltaTime;
+ _primitive.Rotation = _primitive.Rotation * Quaternion.Euler(0, SpeedRotation* Time.deltaTime, 0);
+ return;
+ }
+
+ _primitive.Rotation.SetLookRotation(_direction);
+
+
+ UpdateDetect();
+ UpdateMovement();
+
+ }
+
+ private void UpdateMovement()
+ {
+
+ Vector3 position = base.transform.position;
+ float checkDistance = base.transform.localScale.z;
+
+ DrawableLines.GenerateLine(.1f,Color.blue,position, position + _direction * checkDistance);
+
+ if (Physics.Raycast(position, _direction, checkDistance, (int)LayerMasks.AttackMask))
+ {
+ KELog.Debug("hit wall");
+
+ Destroy();
+ return;
+
+ }
+
+ _primitive.Position = _primitive.Position + _direction * Speed * Time.deltaTime;
+ fallback++;
+ }
+
+
+ private void UpdateDetect()
+ {
+ Vector3 position = base.transform.position;
+ float radius = _primitive.Scale.x /2f;
+ int detect = Physics.OverlapSphereNonAlloc(position, radius, NonAlloc, (int)LayerMasks.Hitbox);
+
+
+
+ DrawableLines.GenerateSphere(position, radius, duration:.1f);
+
+ KELog.Debug("detect=" + detect);
+
+ for (int i = 0; i < detect; i++)
+ {
+ Collider collider = NonAlloc[i];
+
+ if (Player.TryGet(collider, out Player target))
+ {
+ KELog.Debug("collider layer=" + collider.gameObject.layer);
+
+ if (target == _player)
+ {
+ continue;
+ }
+
+ if (HitboxIdentity.IsDamageable(_player.ReferenceHub, target.ReferenceHub))
+ {
+ KELog.Debug("hurt "+ target.Nickname);
+ target.Hurt(_player, Damage, Exiled.API.Enums.DamageType.Scp1509);
+ Destroy();
+ }
+
+
+ }
+ }
+ }
+
+
+ public void Destroy()
+ {
+ KELog.Debug($"destroyed ({fallback}/{Fallback})");
+
+ _primitive?.Destroy();
+ Destroy(this);
+ }
+
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/Abilities/RedMist/ToggleEGO.cs b/KruacentExiled/KE.CustomRoles/Abilities/RedMist/ToggleEGO.cs
new file mode 100644
index 00000000..8bad74a5
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/Abilities/RedMist/ToggleEGO.cs
@@ -0,0 +1,74 @@
+using Exiled.API.Features;
+using KE.CustomRoles.API.Features;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using KE.CustomRoles.CR.MTF.RedMist;
+using KE.CustomRoles.API.Features.Abilities;
+using UnityEngine;
+namespace KE.CustomRoles.Abilities.RedMist
+{
+ public class ToggleEGO : ToggleableAbility
+ {
+
+ public override Color ColorOn => Color.red;
+ public override Color ColorOff => Color.white;
+
+ public override string Name { get; } = "ToggleEGO";
+ protected override Dictionary> SetTranslation()
+ {
+ return new Dictionary>()
+ {
+ ["en"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Toggle E.G.O.",
+ [TranslationKeyDesc] = "Manifest your E.G.O, gain powerful buff but rapid health drain.\nCan be deactivated anytime",
+ },
+ ["fr"] = new Dictionary()
+ {
+ [TranslationKeyName] = "todo",
+ [TranslationKeyDesc] = "todo",
+ }
+ };
+ }
+ public override float Cooldown { get; } = 0f;
+
+ public override bool GetState(Player player)
+ {
+ if (!player.ReferenceHub.gameObject.TryGetComponent(out var ego))
+ {
+ return false;
+ }
+
+ return ego.Active;
+ }
+ protected override bool AbilityUsed(Player player)
+ {
+ if(!player.ReferenceHub.gameObject.TryGetComponent(out var ego))
+ {
+ return false;
+ }
+
+ ego.ToggleActive();
+
+
+
+ return base.AbilityUsed(player);
+ }
+
+
+ protected override void SubscribeEvents()
+ {
+ base.SubscribeEvents();
+ }
+
+ protected override void UnsubscribeEvents()
+ {
+ base.UnsubscribeEvents();
+ }
+
+
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/Abilities/SCP049C/Small.cs b/KruacentExiled/KE.CustomRoles/Abilities/SCP049C/Small.cs
new file mode 100644
index 00000000..62265928
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/Abilities/SCP049C/Small.cs
@@ -0,0 +1,52 @@
+using Exiled.API.Features;
+using KE.CustomRoles.API.Features;
+using KE.CustomRoles.API.Interfaces;
+using MEC;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace KE.CustomRoles.Abilities.SCP049C
+{
+ public class Small : KEAbilities
+ {
+ public override string Name { get; } = "Small";
+
+
+ protected override Dictionary> SetTranslation()
+ {
+ return new Dictionary>()
+ {
+ ["en"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Small",
+ [TranslationKeyDesc] = "Get small for 30 seconds",
+ },
+ ["fr"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Potit",
+ [TranslationKeyDesc] = "Devient plus petit pendant 30 secondes",
+ }
+ };
+ }
+
+ public override float Cooldown { get; } = 120f;
+
+ protected override bool AbilityUsed(Player player)
+ {
+
+ Vector3 oldScale = player.Scale;
+ player.Scale = new Vector3(oldScale.x, 0.8f, oldScale.z);
+
+ Timing.CallDelayed(30, () =>
+ {
+ player.Scale = oldScale;
+ });
+
+ return base.AbilityUsed(player);
+ }
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/Abilities/SCP049C/ToggleHighJump.cs b/KruacentExiled/KE.CustomRoles/Abilities/SCP049C/ToggleHighJump.cs
new file mode 100644
index 00000000..dc1d8d63
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/Abilities/SCP049C/ToggleHighJump.cs
@@ -0,0 +1,68 @@
+using Exiled.API.Features;
+using HintServiceMeow.UI.Utilities;
+using KE.CustomRoles.API.Features;
+using KE.CustomRoles.API.Features.Abilities;
+using KE.CustomRoles.CR.CustomSCPs.SCP049C.UnlockableAbilities.Tier1;
+using MEC;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace KE.CustomRoles.Abilities.SCP049C
+{
+ public class ToggleHighJump : ToggleableAbility
+ {
+ public override string Name { get; } = "ToggleHighJump";
+
+
+ protected override Dictionary> SetTranslation()
+ {
+ return new Dictionary>()
+ {
+ ["en"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Toggle Big Jump",
+ [TranslationKeyDesc] = "",
+ },
+ ["fr"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Active/désactive grand saut",
+ [TranslationKeyDesc] = "",
+ }
+ };
+ }
+
+ public override float Cooldown { get; } = 2f;
+
+ public override Color ColorOn => Color.green;
+
+ public override Color ColorOff => Color.red;
+
+
+
+ protected override bool AbilityUsed(Player player)
+ {
+ if (!player.GameObject.TryGetComponent(out var comp))
+ {
+ player.GameObject.AddComponent();
+ return true;
+ }
+ comp.IsActive = !comp.IsActive;
+
+ return base.AbilityUsed(player);
+ }
+
+ public override bool GetState(Player player)
+ {
+ if (!player.GameObject.TryGetComponent(out var comp))
+ {
+ return false;
+ }
+
+ return comp.IsActive;
+ }
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/Abilities/SetPosition.cs b/KruacentExiled/KE.CustomRoles/Abilities/SetPosition.cs
new file mode 100644
index 00000000..b4cfffbc
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/Abilities/SetPosition.cs
@@ -0,0 +1,179 @@
+using Exiled.API.Features;
+using KE.CustomRoles.API.Features;
+using KE.CustomRoles.API.Interfaces;
+using KE.Utils.API.Features;
+using KE.Utils.API.KETextToy;
+using MEC;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace KE.CustomRoles.Abilities
+{
+ public class SetPosition : KEAbilities, ICustomIcon
+ {
+ public override string Name { get; } = "SetPosition";
+
+ public const string TranslationNoTarget = "SetPositionNoTarget";
+ public const string TranslationTooFar = "SetPositionTooFar";
+ protected override Dictionary> SetTranslation()
+ {
+ return new Dictionary>()
+ {
+ ["en"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Set Position",
+ [TranslationKeyDesc] = "Select the current position for another ability",
+ [TranslationNoTarget] = "No target set",
+ [TranslationTooFar] = "Position destroyed : too far away",
+ },
+ ["fr"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Selection de position",
+ [TranslationKeyDesc] = "Selectionne la position pour une autre abilité",
+ [TranslationNoTarget] = "Pas de position mise",
+ [TranslationTooFar] = "Position détruite : trop loin",
+ }
+ };
+ }
+
+ public override float Cooldown { get; } = 5f;
+ public Utils.API.GifAnimator.TextImage IconName => MainPlugin.Instance.icons[Name];
+
+
+
+ protected override bool AbilityUsed(Player player)
+ {
+
+ Vector3 position = player.Position;
+
+ if(SetPositionPosition.TryGet(player,out var setPosition))
+ {
+ setPosition.Destroy();
+ }
+
+
+
+
+
+ FollowingTextToy followingTextToy = new FollowingTextToy(new List() { player }, position, Quaternion.identity, Vector3.one);
+
+ new SetPositionPosition(player,position, followingTextToy);
+
+
+
+ followingTextToy.OnlyMoveY = true;
+ followingTextToy.Text = "↓";
+
+ Log.Debug("set position at " + position);
+ return base.AbilityUsed(player);
+ }
+
+
+
+
+
+ protected override void AbilityRemoved(Player player)
+ {
+ if(SetPositionPosition.TryGet(player,out var position))
+ {
+ position.Destroy();
+ }
+ base.AbilityRemoved(player);
+ }
+
+ public static bool TryGetTarget(Player p, out Vector3 target)
+ {
+ return SetPositionPosition.TryGetTarget(p, out target);
+ }
+
+ private class SetPositionPosition
+ {
+ public const float RefreshRate = 1f;
+ public const float MaxDistance = 25;
+ private static Dictionary SelectedTarget = new Dictionary();
+ public Player Player { get; private set; }
+ public Vector3 Position { get; }
+ public FollowingTextToy Follow { get; }
+ private readonly CoroutineHandle handle;
+
+ public SetPositionPosition(Player player,Vector3 position, FollowingTextToy follow)
+ {
+ Position = position;
+ Follow = follow;
+ Player = player;
+ handle = Timing.RunCoroutine(CheckPosition());
+ SelectedTarget.Add(Player, this);
+ }
+
+ public static void TryDestroyFollowingTextToy(Player player)
+ {
+ if (SelectedTarget.ContainsKey(player))
+ {
+ SetPositionPosition setposition = SelectedTarget[player];
+
+ if (setposition.Follow != null)
+ {
+ setposition.Follow.Destroy();
+ }
+ }
+ }
+
+ public static bool TryGet(Player player, out SetPositionPosition position)
+ {
+ return SelectedTarget.TryGetValue(player, out position);
+ }
+
+
+ public void Destroy()
+ {
+ Follow.Destroy();
+ SelectedTarget.Remove(Player);
+ Timing.KillCoroutines(handle);
+ Player = null;
+ }
+
+ private bool ToDestroy = false;
+ private IEnumerator CheckPosition()
+ {
+ while(Player != null && !ToDestroy)
+ {
+ float distance = (Position - Player.Position).sqrMagnitude;
+
+ yield return Timing.WaitForSeconds(RefreshRate);
+
+ if (distance >= MaxDistance * MaxDistance)
+ {
+ KELog.Debug("SetPosition to destroy "+ Position);
+ ToDestroy = true;
+ }
+
+
+ }
+
+ if (ToDestroy)
+ {
+
+ TranslationFeed(Player, SetPosition.TranslationTooFar);
+ KELog.Debug($"SetPosition ({Position}) destroyed : too far");
+ Destroy();
+ }
+ }
+
+
+ public static bool TryGetTarget(Player player, out Vector3 target)
+ {
+ target = Vector3.zero;
+ bool result = SelectedTarget.TryGetValue(player, out SetPositionPosition position);
+ if (result)
+ {
+ target = position.Position;
+ }
+
+ return result;
+ }
+ }
+
+
+
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/Abilities/SimulateDeath.cs b/KruacentExiled/KE.CustomRoles/Abilities/SimulateDeath.cs
new file mode 100644
index 00000000..ce284a85
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/Abilities/SimulateDeath.cs
@@ -0,0 +1,67 @@
+using Exiled.API.Enums;
+using Exiled.API.Extensions;
+using Exiled.API.Features;
+using KE.CustomRoles.API.Features;
+using KE.CustomRoles.API.Interfaces;
+using KE.CustomRoles.API.Interfaces.Ability;
+using MEC;
+using PlayerRoles.FirstPersonControl.Thirdperson.Subcontrollers;
+using PlayerStatsSystem;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace KE.CustomRoles.Abilities
+{
+ public class SimulateDeath : KEAbilities, ICustomIcon, IDuration
+ {
+ public override string Name { get; } = "SimulateDeath";
+
+ protected override Dictionary> SetTranslation()
+ {
+ return new Dictionary>()
+ {
+ ["en"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Play dead",
+ [TranslationKeyDesc] = "Simulate your own death",
+ },
+ ["fr"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Faire le mort",
+ [TranslationKeyDesc] = "T'es talent de mime te permettent de simuler la mort",
+ }
+ };
+ }
+
+ public Utils.API.GifAnimator.TextImage IconName => MainPlugin.Instance.icons[Name];
+ public override float Cooldown => 60f;
+ public float Duration => 10f;
+
+ private Ragdoll ragdoll;
+ private Vector3 pScale;
+ private Vector3 pPos;
+
+ protected override bool AbilityUsed(Player player)
+ {
+ Dictionary deathTranslation = DeathTranslations.TranslationsById;
+
+ this.pScale = player.Scale;
+ this.pPos = player.Position;
+ this.ragdoll = Ragdoll.CreateAndSpawn(player.Role, player.DisplayNickname, deathTranslation[(byte)UnityEngine.Random.Range(0, deathTranslation.Count)].DeathscreenTranslation, player.Position, player.ReferenceHub.PlayerCameraReference.rotation, player);
+
+ player.EnableEffect(EffectType.Invisible, this.Duration);
+ player.EnableEffect(EffectType.Ensnared, this.Duration);
+ player.EnableEffect(EffectType.AmnesiaItems, this.Duration);
+ player.Scale = new Vector3(0.1f, 0.1f, 0.1f);
+
+ return base.AbilityUsed(player);
+ }
+
+ public void ActionAfterAbility(Player player)
+ {
+ this.ragdoll.Destroy();
+ player.Scale = this.pScale;
+ player.Position = this.pPos;
+ }
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/Abilities/Teleportation.cs b/KruacentExiled/KE.CustomRoles/Abilities/Teleportation.cs
new file mode 100644
index 00000000..58e1ad0f
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/Abilities/Teleportation.cs
@@ -0,0 +1,127 @@
+using Exiled.API.Extensions;
+using Exiled.API.Features;
+using KE.CustomRoles.API.Features;
+using KE.CustomRoles.API.Interfaces;
+using KE.CustomRoles.API.Interfaces.Ability;
+using MapGeneration;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+
+namespace KE.CustomRoles.Abilities
+{
+ public class Teleportation : KEAbilities, ICustomIcon, IConditional
+ {
+ public override string Name { get; } = "Teleportation";
+
+ public const string TranslationLift = "TeleportationLift";
+ public const string TranslationLcz = "TeleportationLcz";
+ public const string TranslationDifferentZone = "TeleportationDifferentZone";
+
+
+ protected override Dictionary> SetTranslation()
+ {
+ return new Dictionary>()
+ {
+ ["en"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Teleportation",
+ [TranslationKeyDesc] = $"You lose {Damage} HP per teleportation, can't be used in lifts",
+ [TranslationLift] = "can't teleport in lifts",
+ [TranslationLcz] = "The target is inaccessible",
+ [TranslationDifferentZone] = "The target is inaccessible",
+ },
+ ["fr"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Téléportation",
+ [TranslationKeyDesc] = $"Tu perds {Damage} HP/téléportation, ne peux pas être utilisé dans les ascenseurs",
+ [TranslationLift] = "Impossible de se téléporter dans un ascenseur",
+ [TranslationLcz] = "Position inaccessible",
+ [TranslationDifferentZone] = "Position inaccessible",
+ }
+ };
+ }
+
+ public Utils.API.GifAnimator.TextImage IconName => MainPlugin.Instance.icons[Name];
+ public override float Cooldown { get; } = 130f;
+ public static float Damage { get; set; } = 60;
+
+ protected override bool AbilityUsed(Player player)
+ {
+
+ if (!CheckValid(player, true))
+ {
+ return false;
+ }
+ SetPosition.TryGetTarget(player, out Vector3 target);
+ player.Hurt(Damage, Exiled.API.Enums.DamageType.Asphyxiation);
+
+ if(UnityEngine.Random.Range(1f, 100f) < 5)
+ {
+ if (Player.Enumerable.Count() > 1)
+ {
+ player.Teleport(Player.Enumerable.GetRandomValue(p => p != player));
+ }
+ }
+ else
+ {
+ player.Position = target;
+ }
+ return base.AbilityUsed(player);
+
+ }
+
+
+ private bool CheckValid(Player player,bool showMessage)
+ {
+ if (!SetPosition.TryGetTarget(player, out Vector3 target))
+ {
+ if (showMessage)
+ {
+ ShowEffectHint(player, SetPosition.TranslationNoTarget);
+ }
+
+ return false;
+ }
+
+
+
+ if (Lift.Get(target) != null)
+ {
+ if (showMessage)
+ {
+ ShowEffectHint(player, TranslationLift);
+ }
+
+ return false;
+ }
+
+
+ if (target.GetZone() == FacilityZone.LightContainment && Exiled.API.Features.Map.IsLczDecontaminated)
+ {
+ if (showMessage)
+ {
+ ShowEffectHint(player, TranslationLcz);
+ }
+
+
+ return false;
+ }
+
+ if (target.GetZone() != player.Zone.GetZone())
+ {
+ if (showMessage)
+ {
+ ShowEffectHint(player, TranslationDifferentZone);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ public bool CheckCondition(Player player)
+ {
+ return CheckValid(player, false);
+ }
+ }
+}
\ No newline at end of file
diff --git a/KruacentExiled/KE.CustomRoles/Abilities/Thief.cs b/KruacentExiled/KE.CustomRoles/Abilities/Thief.cs
new file mode 100644
index 00000000..d306c4fa
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/Abilities/Thief.cs
@@ -0,0 +1,133 @@
+using Exiled.API.Extensions;
+using Exiled.API.Features;
+using Exiled.API.Features.Items;
+using Exiled.CustomItems.API.Features;
+using KE.CustomRoles.API.Features;
+using KE.CustomRoles.API.Interfaces;
+using KE.Items.API.Features;
+using KE.Utils.API.Displays.DisplayMeow;
+using KE.Utils.API.Features;
+using KE.Utils.API.Features.SCPs;
+using KE.Utils.API.GifAnimator;
+using LiteNetLib4Mirror.Open.Nat;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+
+namespace KE.CustomRoles.Abilities
+{
+ public class Thief : KEAbilities, ICustomIcon
+ {
+ public override string Name { get; } = "Thief";
+
+ public const string NoPlayer = "ThiefNoPlayer";
+ public const string Fail = "ThiefFail";
+ protected override Dictionary> SetTranslation()
+ {
+ return new Dictionary>()
+ {
+ ["en"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Steal",
+ [TranslationKeyDesc] = "Steal a random item from a player in the same room",
+ [NoPlayer] = "No player to steal from",
+ [Fail] = "I think this is a skill issue! Congrats!",
+ },
+ ["fr"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Voler",
+ [TranslationKeyDesc] = "Vole un object aléatoire à un joueur dans la même pièce",
+ [NoPlayer] = "Personne à qui voler",
+ [Fail] = "Wow faut get good là",
+ }
+ };
+ }
+
+ public override float Cooldown { get; } = 60f;
+
+ public TextImage IconName => MainPlugin.Instance.icons[Name];
+
+ protected override bool AbilityUsed(Player player)
+ {
+
+ IEnumerable sameRoom = Player.Enumerable.Where(p => p.CurrentRoom == player.CurrentRoom && !p.IsInventoryEmpty && p != player);
+
+
+
+ KELog.Debug("scp");
+ Player scp = GetClosest(sameRoom.Where(p => SCPTeam.IsSCP(p.ReferenceHub)), player.Position);
+
+
+ if(scp != null)
+ {
+ KELog.Debug("steal scp");
+ Steal(player, scp);
+ return true;
+ }
+
+ KELog.Debug("enemy");
+ Player enemy = GetClosest(sameRoom.Where(p => HitboxIdentity.IsEnemy(player.ReferenceHub, p.ReferenceHub)),player.Position);
+
+
+ if(enemy != null)
+ {
+ KELog.Debug("steal enemy");
+ Steal(player, enemy);
+ return true;
+ }
+
+ KELog.Debug("other");
+ Player ally = GetClosest(sameRoom, player.Position);
+
+
+ if (ally != null)
+ {
+ KELog.Debug("steal ally");
+ Steal(player, ally);
+ return true;
+ }
+
+ MainPlugin.ShowEffectHint(player, "no player to steal from");
+ return false;
+ }
+
+ private void Steal(Player player, Player thiefed)
+ {
+
+ Item item;
+
+ if(player.CurrentItem is null || UnityEngine.Random.Range(0f,100f) < 50f)
+ {
+ item = thiefed.Items.GetRandomValue();
+ }
+ else
+ {
+ item = player.CurrentItem;
+ }
+
+
+ if(KECustomItem.TryGet(item,out CustomItem ci))
+ {
+ ci.Give(player);
+ }
+ else
+ {
+ Item newitem = item.Clone();
+ newitem.Give(player);
+ }
+
+ thiefed.RemoveItem(item);
+ }
+
+
+
+
+ private Player GetClosest(IEnumerable players,Vector3 center)
+ {
+ return players.OrderBy(p => Vector3.Distance(center, p.Position)).FirstOrDefault();
+ }
+
+
+
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/Abilities/Trade.cs b/KruacentExiled/KE.CustomRoles/Abilities/Trade.cs
new file mode 100644
index 00000000..b0a7acc3
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/Abilities/Trade.cs
@@ -0,0 +1,72 @@
+using Exiled.API.Features;
+using Exiled.API.Features.Attributes;
+using Exiled.CustomRoles.API.Features;
+using KE.CustomRoles.API.Features;
+using KE.CustomRoles.API.Interfaces;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace KE.CustomRoles.Abilities
+{
+ public class Trade : KEAbilities, ICustomIcon
+ {
+ public override string Name { get; } = "Trade";
+ public Utils.API.GifAnimator.TextImage IconName => MainPlugin.Instance.icons[Name];
+ public override float Cooldown { get; } = 1f;
+
+ public static float MaxHealthPercent = .1f;
+
+ protected override Dictionary> SetTranslation()
+ {
+ return new Dictionary>()
+ {
+ ["en"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Trade",
+ [TranslationKeyDesc] = "Your link with casinos permit your to gain more coins in exchange of items",
+ ["TradeNoItem"] = "No item?",
+ },
+ ["fr"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Trade",
+ [TranslationKeyDesc] = "T'es lien avec le casino t'as permis d'avoir un peu plus de pièce en échange d'un objet",
+ ["TradeNoItem"] = "Pas d'objet",
+ }
+ };
+ }
+
+ protected override bool AbilityUsed(Player player)
+ {
+ if (player.CurrentItem != null)
+ {
+ player.RemoveItem(player.CurrentItem);
+ }
+ else
+ {
+
+
+ ShowEffectHint(player, "no items?");
+ return false;
+ /*
+ float newHealth = player.MaxHealth - player.MaxHealth * MaxHealthPercent;
+ if (newHealth > 0)
+ {
+ player.Health = Mathf.Min(player.Health, newHealth);
+ }
+ else
+ {
+ player.Kill("The casino always win");
+ return base.AbilityUsed(player);
+ }*/
+ }
+
+ player.AddItem(ItemType.Coin);
+ return base.AbilityUsed(player);
+ }
+
+ }
+}
diff --git a/KruacentExiled/KE.CustomRoles/CR/ChaosInsurgency/LeRusse.cs b/KruacentExiled/KE.CustomRoles/CR/ChaosInsurgency/LeRusse.cs
new file mode 100644
index 00000000..ba62946f
--- /dev/null
+++ b/KruacentExiled/KE.CustomRoles/CR/ChaosInsurgency/LeRusse.cs
@@ -0,0 +1,183 @@
+using AdminToys;
+using Exiled.API.Enums;
+using Exiled.API.Features;
+using Exiled.API.Features.Items;
+using Exiled.API.Features.Toys;
+using Exiled.CustomItems.API.Features;
+using Exiled.Events.EventArgs.Player;
+using KE.CustomRoles.API.Features;
+using MEC;
+using Mirror;
+using PlayerRoles;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+using VoiceChat.Codec;
+using VoiceChat.Networking;
+
+namespace KE.CustomRoles.CR.ChaosInsurgency
+{
+ public class Russe : KECustomRole
+ {
+ protected override Dictionary> SetTranslation()
+ {
+ return new Dictionary>()
+ {
+ ["en"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Russian",
+ [TranslationKeyDesc] = "RUSH B or A, i dont rember sooo good luck",
+ },
+ ["fr"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Russe",
+ [TranslationKeyDesc] = "RUSH B or A, i dont rember sooo good luck",
+ },
+ ["legacy"] = new Dictionary()
+ {
+ [TranslationKeyName] = "Russe",
+ [TranslationKeyDesc] = "RUSH B or A, i dont rember sooo good luck",
+ }
+ };
+ }
+
+ public override int MaxHealth { get; set; } = 100;
+ public override RoleTypeId Role { get; set; } = RoleTypeId.ChaosRifleman;
+
+ public float DamageToLootbox { get; set; } = 500f;
+
+ private readonly Dictionary _playerDamage = new Dictionary();
+
+ private static object[] _lootPool = null;
+
+ public override List Inventory { get; set; } = new List()
+ {
+ $"{ItemType.GunRevolver}", $"{ItemType.GunA7}", $"{ItemType.ArmorHeavy}",
+ $"{ItemType.GrenadeHE}", $"{ItemType.GrenadeFlash}", $"{ItemType.Radio}",
+ $"{ItemType.KeycardChaosInsurgency}"
+ };
+
+ public override Dictionary Ammo { get; set; } = new Dictionary()
+ {
+ { AmmoType.Ammo44Cal, 19 }, { AmmoType.Nato762, 60 }
+ };
+
+
+ protected override void SubscribeEvents()
+ {
+ Exiled.Events.Handlers.Player.Hurting += OnDealingDamage;
+ base.SubscribeEvents();
+ }
+
+ protected override void UnsubscribeEvents()
+ {
+ Exiled.Events.Handlers.Player.Hurting -= OnDealingDamage;
+ base.UnsubscribeEvents();
+ }
+
+
+ protected override void RoleAdded(Player player)
+ {
+ _playerDamage[player] = 0f;
+
+ }
+
+ protected override void RoleRemoved(Player player)
+ {
+
+
+ _playerDamage.Remove(player);
+ }
+
+ private void OnDealingDamage(HurtingEventArgs ev)
+ {
+ if (ev.Attacker == null || !Check(ev.Attacker) || ev.Player == ev.Attacker) return;
+
+ if (!_playerDamage.ContainsKey(ev.Attacker)) _playerDamage[ev.Attacker] = 0f;
+ _playerDamage[ev.Attacker] += ev.Amount;
+
+ if (_playerDamage[ev.Attacker] >= DamageToLootbox)
+ {
+ _playerDamage[ev.Attacker] = 0f;
+ SpawnLootBox(ev.Player.Position, ev.Attacker);
+ }
+ }
+
+ private void SpawnLootBox(Vector3 pos, Player owner)
+ {
+ Primitive box = Primitive.Create(PrimitiveType.Cube, pos + Vector3.up * 0.5f, Vector3.zero, new Vector3(0.5f, 0.5f, 0.5f), true);
+ box.Color = Color.black;
+ Timing.RunCoroutine(LootBoxAnimation(box, owner));
+ }
+
+ private IEnumerator LootBoxAnimation(Primitive box, Player p)
+ {
+ Exiled.API.Features.Toys.Light boxLight = Exiled.API.Features.Toys.Light.Create(box.Position + Vector3.up * 0.5f, Vector3.zero, Vector3.one, true);
+ boxLight.Intensity = 40f;
+ boxLight.Range = 8f;
+
+ Color[] csRarities = { Color.blue, Color.magenta, Color.red };
+
+ try
+ {
+ for (int i = 0; i < 10; i++)
+ {
+ if (box == null) yield break;
+
+ Color color = csRarities[Random.Range(0, csRarities.Length)];
+ box.Color = color;
+ boxLight.Color = color;
+
+ yield return Timing.WaitForSeconds(0.2f);
+ }
+
+ object reward = GetRandomGambleReward();
+
+ if (reward is CustomItem custom)
+ {
+ custom.Spawn(box.Position);
+ }
+ else if (reward is ItemType baseItem)
+ {
+ Item.Create(baseItem).CreatePickup(box.Position);
+ }
+ }
+ finally
+ {
+ if (box != null) box.Destroy();
+ Timing.CallDelayed(0.5f, () => { if (boxLight != null) boxLight.Destroy(); });
+ }
+ }
+
+
+ private object GetRandomGambleReward()
+ {
+ if (_lootPool == null)
+ {
+ List