C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
// Requires: EMInterface
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Game.Rust.Cui;
using Oxide.Core.Plugins;
using UnityEngine;
using System.Linq;
using Network;
using Facepunch;
using UI = Oxide.Plugins.EMInterface.UI;
using UI4 = Oxide.Plugins.EMInterface.UI4;
namespace Oxide.Plugins
{
using EventManagerEx;
using System.Collections;
using System.Text.RegularExpressions;
[Info("EventManager", "k1lly0u", "4.0.0")]
[Description("The core mechanics for arena combat games")]
public class EventManager : RustPlugin
{
#region Fields
private DynamicConfigFile restorationData, eventData;
[PluginReference]
private Plugin Economics, Kits, NoEscape, ServerRewards, Spawns, ZoneManager;
private RewardType rewardType;
private int scrapItemId;
private static Regex hexFilter;
public Hash<string, IEventPlugin> EventModes { get; set; } = new Hash<string, IEventPlugin>();
public EventData Events { get; private set; }
private RestoreData Restore { get; set; }
public static EventManager Instance { get; private set; }
public static BaseEventGame BaseManager { get; internal set; }
public static ConfigData Configuration { get; set; }
public static EventResults LastEventResult { get; private set; }
public static bool IsUnloading { get; private set; }
internal const string ADMIN_PERMISSION = "eventmanager.admin";
#endregion
#region Oxide Hooks
private void Loaded()
{
restorationData = Interface.Oxide.DataFileSystem.GetFile("EventManager/restoration_data");
eventData = Interface.Oxide.DataFileSystem.GetFile("EventManager/event_data");
permission.RegisterPermission(ADMIN_PERMISSION, this);
Instance = this;
IsUnloading = false;
LastEventResult = new EventResults();
LoadData();
}
protected override void LoadDefaultMessages() => lang.RegisterMessages(Messages, this);
private void OnServerInitialized()
{
if (!CheckDependencies())
return;
rewardType = ParseType<RewardType>(Configuration.Reward.Type);
scrapItemId = ItemManager.FindItemDefinition("scrap")?.itemid ?? 0;
hexFilter = new Regex("^([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$");
UnsubscribeAll();
foreach (BasePlayer player in BasePlayer.activePlayerList)
OnPlayerConnected(player);
}
private void Unload()
{
IsUnloading = true;
SaveRestoreData();
BaseEventPlayer[] eventPlayers = UnityEngine.Object.FindObjectsOfType<BaseEventPlayer>();
for (int i = 0; i < eventPlayers?.Length; i++)
UnityEngine.Object.DestroyImmediate(eventPlayers[i]);
if (BaseManager != null)
UnityEngine.Object.DestroyImmediate(BaseManager.gameObject);
hexFilter = null;
LastEventResult = null;
BaseManager = null;
Configuration = null;
Instance = null;
}
private void OnServerSave() => SaveRestoreData();
private void OnPlayerConnected(BasePlayer player)
{
if (player.IsSleeping() || player.HasPlayerFlag(BasePlayer.PlayerFlags.ReceivingSnapshot))
{
timer.Once(1f, () => OnPlayerConnected(player));
return;
}
UnlockInventory(player);
if (Restore.HasRestoreData(player.userID))
Restore.RestorePlayer(player);
}
private void OnPlayerDisconnected(BasePlayer player)
{
BaseEventPlayer eventPlayer = GetUser(player);
if (eventPlayer != null)
{
if (BaseManager != null)
BaseManager.LeaveEvent(player);
else UnityEngine.Object.DestroyImmediate(eventPlayer);
if (player.IsAlive())
player.DieInstantly();
}
}
private void OnEntityTakeDamage(BaseEntity entity, HitInfo hitInfo)
{
if (entity == null || hitInfo == null)
return;
BasePlayer player = entity.ToPlayer();
if (player != null)
{
BaseEventPlayer eventPlayer = GetUser(player);
if (eventPlayer != null)
{
if (BaseManager == null)
return;
BaseManager.OnPlayerTakeDamage(eventPlayer, hitInfo);
}
}
else
{
BaseEventPlayer attacker = GetUser(hitInfo.InitiatorPlayer);
if (attacker != null)
{
if (BaseManager != null)
{
if (BaseManager.CanDealEntityDamage(attacker, entity, hitInfo))
return;
}
ClearDamage(hitInfo);
}
}
}
private object CanBeWounded(BasePlayer player, HitInfo hitInfo)
{
BaseEventPlayer eventPlayer = GetUser(player);
if (eventPlayer != null && BaseManager != null)
return false;
return null;
}
private object OnPlayerDeath(BasePlayer player, HitInfo hitInfo)
{
if (player != null)
{
BaseEventPlayer eventPlayer = GetUser(player);
if (eventPlayer != null && BaseManager != null)
{
if (!eventPlayer.IsDead)
BaseManager.PrePlayerDeath(eventPlayer, hitInfo);
return false;
}
}
return null;
}
private object CanSpectateTarget(BasePlayer player, string name)
{
BaseEventPlayer eventPlayer = player.GetComponent<BaseEventPlayer>();
if (eventPlayer != null && eventPlayer.Player.IsSpectating())
{
eventPlayer.UpdateSpectateTarget();
return false;
}
return null;
}
private void OnEntityBuilt(Planner planner, GameObject gameObject)
{
BasePlayer player = planner?.GetOwnerPlayer();
if (player == null)
return;
BaseCombatEntity baseCombatEntity = gameObject?.ToBaseEntity() as BaseCombatEntity;
if (baseCombatEntity == null)
return;
BaseEventPlayer eventPlayer = GetUser(player);
if (eventPlayer != null && BaseManager != null)
BaseManager.OnEntityDeployed(baseCombatEntity);
}
private void OnItemDeployed(Deployer deployer, BaseCombatEntity baseCombatEntity)
{
BasePlayer player = deployer.GetOwnerPlayer();
if (player == null)
return;
BaseEventPlayer eventPlayer = GetUser(player);
if (eventPlayer != null && BaseManager != null)
BaseManager.OnEntityDeployed(baseCombatEntity);
}
private object OnCreateWorldProjectile(HitInfo hitInfo, Item item)
{
if (hitInfo == null)
return null;
if (hitInfo.InitiatorPlayer != null)
{
BaseEventPlayer eventPlayer = GetUser(hitInfo.InitiatorPlayer);
if (eventPlayer != null)
return false;
}
if (hitInfo.HitEntity?.ToPlayer() != null)
{
BaseEventPlayer eventPlayer = GetUser(hitInfo.HitEntity.ToPlayer());
if (eventPlayer != null)
return false;
}
return null;
}
private object CanDropActiveItem(BasePlayer player)
{
BaseEventPlayer eventPlayer = GetUser(player);
if (eventPlayer != null)
return false;
return null;
}
private object OnPlayerCommand(BasePlayer player, string command, string[] args)
{
BaseEventPlayer eventPlayer = GetUser(player);
if (player == null || player.IsAdmin || eventPlayer == null)
return null;
if (Configuration.Event.CommandBlacklist.Any(x => x.StartsWith("/") ? x.Substring(1).ToLower() == command : x.ToLower() == command))
{
SendReply(player, Message("Error.CommandBlacklisted", player.userID));
return false;
}
return null;
}
private object OnServerCommand(ConsoleSystem.Arg arg)
{
BasePlayer player = arg.Player();
BaseEventPlayer eventPlayer = GetUser(player);
if (player == null || player.IsAdmin || eventPlayer == null || arg.Args == null)
return null;
if (Configuration.Event.CommandBlacklist.Any(x => arg.cmd.FullName.Contains(x.ToLower())))
{
SendReply(player, Message("Error.CommandBlacklisted", player.userID));
return false;
}
return null;
}
#endregion
#region Event Construction
public static void RegisterEvent(string eventName, IEventPlugin plugin) => Instance.EventModes[eventName] = plugin;
public static void UnregisterEvent(string eventName) => Instance.EventModes.Remove(eventName);
public object OpenEvent(string eventName)
{
EventConfig eventConfig;
if (Events.events.TryGetValue(eventName, out eventConfig))
{
IEventPlugin plugin;
if (!EventModes.TryGetValue(eventConfig.EventType, out plugin))
return $"Unable to find event plugin for game mode: {eventConfig.EventType}";
if (plugin == null)
return $"Unable to initialize event plugin: {eventConfig.EventType}.Plugin is either unloaded or the class does not derive from IEventGame";
object success = ValidateEventConfig(eventConfig);
if (success is string)
return $"Failed to open event : {(string)success}";
if (!plugin.InitializeEvent(eventConfig))
return $"There was a error initializing the event : {eventConfig.EventType}";
return null;
}
else return "Failed to find a event with the specified name";
}
public static bool InitializeEvent<T>(IEventPlugin plugin, EventConfig config) where T : BaseEventGame
{
if (BaseManager != null)
return false;
BaseManager = new GameObject(config.EventName).AddComponent<T>();
BaseManager.InitializeEvent(plugin, config);
return true;
}
#endregion
#region Functions
public IEventPlugin GetPlugin(string name)
{
IEventPlugin eventPlugin;
if (EventModes.TryGetValue(name, out eventPlugin))
return eventPlugin;
return null;
}
private bool CheckDependencies()
{
if (!Spawns)
{
PrintError("Unable to load EventManager - Spawns database not found.Please download Spawns database to continue");
rust.RunServerCommand("oxide.unload", "EventManager");
return false;
}
if (!ZoneManager)
PrintError("ZoneManager is not installed! Unable to restrict event players to zones");
if (!Kits)
PrintError("Kits is not installed! Unable to issue any weapon kits");
return true;
}
private void UnsubscribeAll()
{
Unsubscribe(nameof(OnEntityTakeDamage));
Unsubscribe(nameof(CanBeWounded));
Unsubscribe(nameof(OnPlayerDeath));
Unsubscribe(nameof(OnEntityBuilt));
Unsubscribe(nameof(OnItemDeployed));
Unsubscribe(nameof(OnCreateWorldProjectile));
Unsubscribe(nameof(CanDropActiveItem));
Unsubscribe(nameof(OnPlayerCommand));
Unsubscribe(nameof(OnServerCommand));
}
private void SubscribeAll()
{
Subscribe(nameof(OnEntityTakeDamage));
Subscribe(nameof(CanBeWounded));
Subscribe(nameof(OnPlayerDeath));
Subscribe(nameof(OnEntityBuilt));
Subscribe(nameof(OnItemDeployed));
Subscribe(nameof(OnCreateWorldProjectile));
Subscribe(nameof(CanDropActiveItem));
Subscribe(nameof(OnPlayerCommand));
Subscribe(nameof(OnServerCommand));
}
private static void Broadcast(string key, params object[] args)
{
foreach (BasePlayer player in BasePlayer.activePlayerList)
player.ChatMessage(string.Format(Message(key, player.userID), args));
}
internal static bool IsValidHex(string s) => hexFilter.IsMatch(s);
#endregion
#region Classes and Components
public class BaseEventGame : MonoBehaviour
{
internal IEventPlugin Plugin { get; private set; }
internal EventConfig Config { get; private set; }
public EventStatus Status { get; protected set; }
protected GameTimer Timer { get; set; }
internal SpawnSelector _spawnSelectorA;
internal SpawnSelector _spawnSelectorB;
protected CuiElementContainer scoreContainer = null;
internal List<BasePlayer> joiningPlayers = Pool.GetList<BasePlayer>();
internal List<BaseEventPlayer> eventPlayers = Pool.GetList<BaseEventPlayer>();
internal List<ScoreEntry> scoreData = Pool.GetList<ScoreEntry>();
private List<BaseCombatEntity> _deployedObjects = Pool.GetList<BaseCombatEntity>();
private bool _isClosed = false;
internal string TeamAColor { get; set; }
internal string TeamBColor { get; set; }
internal string TeamAClothing { get; set; }
internal string TeamBClothing { get; set; }
public bool GodmodeEnabled { get; protected set; } = true;
internal string EventInformation
{
get
{
string str = string.Format(Message("Info.Event.Current"), Config.EventName, Config.EventType);
str += string.Format(Message("Info.Event.Player"), eventPlayers.Count, Config.MaximumPlayers);
return str;
}
}
internal string EventStatus => string.Format(Message("Info.Event.Status"), Status);
#region Initialization and Destruction
/// <summary>
/// Called when the event GameObject is destroyed
/// </summary>
protected virtual void OnDestroy()
{
CleanupEntities();
for (int i = eventPlayers.Count - 1; i >= 0; i--)
{
BaseEventPlayer eventPlayer = eventPlayers[i];
if (eventPlayer.IsDead)
ResetPlayer(eventPlayer.Player);
LeaveEvent(eventPlayer);
}
Pool.FreeList(ref scoreData);
Pool.FreeList(ref joiningPlayers);
Pool.FreeList(ref _deployedObjects);
Pool.FreeList(ref eventPlayers);
_spawnSelectorA?.Destroy();
_spawnSelectorB?.Destroy();
Timer?.StopTimer();
Instance?.UnsubscribeAll();
Destroy(gameObject);
}
/// <summary>
/// The first function called when an event is being opened
/// </summary>
/// <param name="plugin">The plugin the event game belongs to</param>
/// <param name="config">The event config</param>
internal virtual void InitializeEvent(IEventPlugin plugin, EventConfig config)
{
this.Plugin = plugin;
this.Config = config;
_spawnSelectorA = new SpawnSelector(config.EventName, config.TeamConfigA.Spawnfile);
if (plugin.IsTeamEvent)
{
TeamAColor = config.TeamConfigA.Color;
TeamBColor = config.TeamConfigB.Color;
if (string.IsNullOrEmpty(TeamAColor) || TeamAColor.Length < 6 || TeamAColor.Length > 6 || !hexFilter.IsMatch(TeamAColor))
TeamAColor = "#EA3232";
else TeamAColor = "#" + TeamAColor;
if (string.IsNullOrEmpty(TeamBColor) || TeamBColor.Length < 6 || TeamBColor.Length > 6 || !hexFilter.IsMatch(TeamBColor))
TeamBColor = "#3232EA";
else TeamBColor = "#" + TeamBColor;
_spawnSelectorB = new SpawnSelector(config.EventName, config.TeamConfigB.Spawnfile);
}
Timer = new GameTimer(this);
GodmodeEnabled = true;
OpenEvent();
}
#endregion
#region Event Management
/// <summary>
/// Opens the event for players to join
/// </summary>
internal virtual void OpenEvent()
{
_isClosed = false;
Status = EventManager.EventStatus.Open;
Broadcast("Notification.EventOpen", Config.EventName, Config.EventType, Configuration.Timer.Start);
InvokeHandler.Invoke(this, PrestartEvent, Configuration.Timer.Start);
}
/// <summary>
/// Closes the event and prevent's more players from joining
/// </summary>
internal virtual void CloseEvent()
{
_isClosed = true;
Broadcast("Notification.EventClosed");
}
/// <summary>
/// The event prestart where players are created and sent to the arena
/// </summary>
internal virtual void PrestartEvent()
{
if (!HasMinimumRequiredPlayers())
{
Broadcast("Notification.NotEnoughToStart");
EndEvent();
return;
}
Instance.SubscribeAll();
Status = EventManager.EventStatus.Prestarting;
StartCoroutine(CreateEventPlayers());
}
/// <summary>
/// Start's the event
/// </summary>
protected virtual void StartEvent()
{
InvokeHandler.CancelInvoke(this, PrestartEvent);
if (!HasMinimumRequiredPlayers())
{
Broadcast("Notification.NotEnoughToStart");
EndEvent();
return;
}
Timer.StopTimer();
Status = EventManager.EventStatus.Started;
if (Config.TimeLimit > 0)
Timer.StartTimer(Config.TimeLimit, string.Empty, EndEvent);
GodmodeEnabled = false;
eventPlayers.ForEach((BaseEventPlayer eventPlayer) =>
{
if (eventPlayer?.Player == null)
return;
if (eventPlayer.IsDead)
RespawnPlayer(eventPlayer);
else
{
ResetPlayer(eventPlayer.Player);
OnPlayerRespawn(eventPlayer);
}
});
}
/// <summary>
/// End's the event and restore's all player's back to the state they were in prior to the event starting
/// </summary>
internal virtual void EndEvent()
{
InvokeHandler.CancelInvoke(this, PrestartEvent);
Timer.StopTimer();
Status = EventManager.EventStatus.Finished;
GodmodeEnabled = true;
LastEventResult.UpdateFromEvent(this);
ProcessWinners();
eventPlayers.ForEach((BaseEventPlayer eventPlayer) =>
{
if (eventPlayer?.Player == null)
return;
if (eventPlayer.IsDead)
ResetPlayer(eventPlayer.Player);
EventStatistics.Data.OnGamePlayed(eventPlayer.Player, Config.EventType);
});
EventStatistics.Data.OnGamePlayed(Config.EventType);
EjectAllPlayers();
DestroyImmediate(this);
}
#endregion
#region Player Management
internal bool IsOpen()
{
if (_isClosed || Status == EventManager.EventStatus.Finished)
return false;
if (((int)Status < 2 && joiningPlayers.Count >= Config.MaximumPlayers) || eventPlayers.Count >= Config.MaximumPlayers)
return false;
if (!string.IsNullOrEmpty(CanJoinEvent()))
return false;
return true;
}
internal bool CanJoinEvent(BasePlayer player)
{
if (_isClosed)
{
player.ChatMessage(Message("Notification.EventClosed", player.userID));
return false;
}
if (Status == EventManager.EventStatus.Finished)
{
player.ChatMessage(Message("Notification.EventFinished", player.userID));
return false;
}
if (((int)Status < 2 && joiningPlayers.Count >= Config.MaximumPlayers) || eventPlayers.Count >= Config.MaximumPlayers)
{
player.ChatMessage(Message("Notification.MaximumPlayers", player.userID));
return false;
}
string str = CanJoinEvent();
if (!string.IsNullOrEmpty(str))
{
player.ChatMessage(str);
return false;
}
return true;
}
/// <summary>
/// Allow or disallow players to join the event
/// </summary>
/// <returns>Supply a (string) reason to disallow, or a empty string to allow</returns>
protected virtual string CanJoinEvent()
{
return string.Empty;
}
/// <summary>
/// Override to perform additional logic when a player joins an event
/// </summary>
/// <param name="player">The BasePlayer object of the player joining the event</param>
/// <param name="team">The team the player should be placed in</param>
internal virtual void JoinEvent(BasePlayer player, Team team = Team.None)
{
if (Status == EventManager.EventStatus.Started)
CreateEventPlayer(player, team);
else joiningPlayers.Add(player);
if (Configuration.Message.BroadcastJoiners)
Broadcast("Notification.PlayerJoined", player.displayName, Config.EventName);
}
/// <summary>
/// Override to perform additional logic when a player leaves an event.This is called when the player uses the leave chat command prior to destroying the BaseEventPlayer
/// </summary>
/// <param name="player">The BasePlayer object of the player leaving the event</param>
internal virtual void LeaveEvent(BasePlayer player)
{
if (joiningPlayers.Contains(player))
{
joiningPlayers.Remove(player);
if (Configuration.Message.BroadcastLeavers)
Broadcast("Notification.PlayerLeft", player.displayName, Config.EventName);
}
BaseEventPlayer eventPlayer = GetUser(player);
if (eventPlayer == null)
return;
LeaveEvent(eventPlayer);
}
/// <summary>
/// Override to perform additional logic when a event player leaves an event
/// </summary>
/// <param name="eventPlayer">The BaseEventPlayer object of the player leaving the event</param>
internal virtual void LeaveEvent(BaseEventPlayer eventPlayer)
{
if (!string.IsNullOrEmpty(Config.ZoneID))
Instance.ZoneManager?.Call("RemovePlayerFromZoneWhitelist", Config.ZoneID, eventPlayer.Player);
eventPlayers.Remove(eventPlayer);
if (!eventPlayer.Player.IsConnected || eventPlayer.Player.IsSleeping() || IsUnloading)
eventPlayer.Player.Die();
else Instance.Restore.RestorePlayer(eventPlayer.Player);
DestroyImmediate(eventPlayer);
if (Status != EventManager.EventStatus.Finished && !HasMinimumRequiredPlayers())
{
BroadcastToPlayers("Notification.NotEnoughToContinue");
EndEvent();
}
}
private IEnumerator CreateEventPlayers()
{
for (int i = joiningPlayers.Count - 1; i >= 0; i--)
{
BasePlayer joiner = joiningPlayers[i];
EMInterface.DestroyAllUI(joiner);
CreateEventPlayer(joiner, GetPlayerTeam(joiner));
yield return CoroutineEx.waitForEndOfFrame;
yield return CoroutineEx.waitForEndOfFrame;
}
UpdateScoreboard();
Timer.StartTimer(Configuration.Timer.Prestart, Message("Notification.RoundStartsIn"), StartEvent);
}
/// <summary>
/// Override to perform additional logic when initializing the BaseEventPlayer component
/// </summary>
/// <param name="player">The BasePlayer object of the player joining the event</param>
/// <param name="team">The team this player is on</param>
protected virtual void CreateEventPlayer(BasePlayer player, Team team = Team.None)
{
if (player == null)
return;
joiningPlayers.Remove(player);
BaseEventPlayer eventPlayer = AddPlayerComponent(player);
eventPlayer.ResetPlayer();
eventPlayer.Event = this;
eventPlayer.Team = team;
eventPlayers.Add(eventPlayer);
if (!Config.AllowClassSelection || GetAvailableKits(eventPlayer.Team).Count <= 1)
eventPlayer.Kit = GetAvailableKits(team).First();
SpawnPlayer(eventPlayer, Status == EventManager.EventStatus.Started, true);
if (!string.IsNullOrEmpty(Config.ZoneID))
Instance.ZoneManager?.Call("AddPlayerToZoneWhitelist", Config.ZoneID, player);
}
/// <summary>
/// Override to assign players to teams
/// </summary>
/// <param name="player"></param>
/// <returns>The team the player will be assigned to</returns>
protected virtual Team GetPlayerTeam(BasePlayer player) => Team.None;
/// <summary>
/// Add's the BaseEventPlayer component to the player.Override with your own component if you want to extend the BaseEventPlayer class
/// </summary>
/// <param name="player"></param>
/// <param name="team"></param>
/// <returns>The BaseEventPlayer component</returns>
protected virtual BaseEventPlayer AddPlayerComponent(BasePlayer player) => player.gameObject.GetComponent<BaseEventPlayer>() ?? player.gameObject.AddComponent<BaseEventPlayer>();
/// <summary>
/// Called prior to a event player respawning
/// </summary>
/// <param name="baseEventPlayer"></param>
internal virtual void OnPlayerRespawn(BaseEventPlayer baseEventPlayer)
{
SpawnPlayer(baseEventPlayer, Status == EventManager.EventStatus.Started);
}
/// <summary>
/// Spawn's the specified player
/// </summary>
/// <param name="eventPlayer"></param>
/// <param name="giveKit">Should this player recieve a kit?</param>
/// <param name="sleep">Should this player be put to sleep before teleporting?</param>
internal void SpawnPlayer(BaseEventPlayer eventPlayer, bool giveKit = true, bool sleep = false)
{
BasePlayer player = eventPlayer?.Player;
if (player == null)
return;
eventPlayer.Player?.GetMounted()?.AttemptDismount(eventPlayer.Player);
StripInventory(player);
ResetMetabolism(player);
MovePosition(player, eventPlayer.Team == Team.B ? _spawnSelectorB.GetSpawnPoint() : _spawnSelectorA.GetSpawnPoint(), sleep);
if (string.IsNullOrEmpty(eventPlayer.Kit))
{
eventPlayer.ForceSelectClass();
EMInterface.DisplayDeathScreen(eventPlayer, Message("UI.SelectClass", eventPlayer.Player.userID), true);
return;
}
UpdateScoreboard(eventPlayer);
if (giveKit)
{
Instance.NextTick(() =>
{
if (!CanGiveKit(eventPlayer))
return;
GiveKit(player, eventPlayer.Kit);
OnKitGiven(eventPlayer);
});
}
eventPlayer.ApplyInvincibility();
OnPlayerSpawned(eventPlayer);
}
/// <summary>
/// Called after a player has spawned/respawned
/// </summary>
/// <param name="eventPlayer">The player that has spawned</param>
protected virtual void OnPlayerSpawned(BaseEventPlayer eventPlayer) { }
/// <summary>
/// Kicks all players out of the event
/// </summary>
protected void EjectAllPlayers()
{
for (int i = eventPlayers.Count - 1; i >= 0; i--)
LeaveEvent(eventPlayers[i].Player);
eventPlayers.Clear();
}
/// <summary>
/// Reset's all players that are currently dead and respawn's them
/// </summary>
protected void RespawnAllPlayers()
{
for (int i = eventPlayers.Count - 1; i >= 0; i--)
RespawnPlayer(eventPlayers[i]);
}
private bool HasMinimumRequiredPlayers()
{
if (Status == EventManager.EventStatus.Open)
return joiningPlayers.Count >= Config.MinimumPlayers;
else return eventPlayers.Count >= Config.MinimumPlayers;
}
#endregion
#region Damage and Death
/// <summary>
/// Called when a player deals damage to a entity that is not another event player
/// </summary>
/// <param name="attacker">The player dealing the damage</param>
/// <param name="entity">The entity that was hit</param>
/// <param name="hitInfo">The HitInfo</param>
/// <returns>True allows damage, false prevents damage</returns>
internal virtual bool CanDealEntityDamage(BaseEventPlayer attacker, BaseEntity entity, HitInfo hitInfo)
{
return false;
}
/// <summary>
/// Scale's player-to-player damage
/// </summary>
/// <param name="eventPlayer">The player that is attacking</param>
/// <returns>1.0f is normal damage</returns>
protected virtual float GetDamageModifier(BaseEventPlayer eventPlayer) => 1f;
/// <summary>
/// Calculates and applies damage to the player
/// </summary>
/// <param name="eventPlayer"></param>
/// <param name="hitInfo"></param>
internal virtual void OnPlayerTakeDamage(BaseEventPlayer eventPlayer, HitInfo hitInfo)
{
BaseEventPlayer attacker = GetUser(hitInfo.InitiatorPlayer);
if (GodmodeEnabled || eventPlayer.IsDead || eventPlayer.IsInvincible)
{
ClearDamage(hitInfo);
return;
}
float damageModifier = GetDamageModifier(attacker);
if (damageModifier != 1f)
hitInfo.damageTypes.ScaleAll(damageModifier);
eventPlayer.OnTakeDamage(attacker?.Player.userID ?? 0U);
}
/// <summary>
/// Called prior to event player death logic.Prepares the player for the death cycle by hiding them from other players
/// </summary>
/// <param name="eventPlayer"></param>
/// <param name="hitInfo"></param>
internal virtual void PrePlayerDeath(BaseEventPlayer eventPlayer, HitInfo hitInfo)
{
if (CanDropBackpack())
eventPlayer.DropInventory();
if (eventPlayer.Player.isMounted)
{
BaseMountable baseMountable = eventPlayer.Player.GetMounted();
if (baseMountable != null)
{
baseMountable.DismountPlayer(eventPlayer.Player);
eventPlayer.Player.EnsureDismounted();
}
}
eventPlayer.IsDead = true;
UpdateDeadSpectateTargets(eventPlayer);
eventPlayer.Player.limitNetworking = true;
eventPlayer.Player.DisablePlayerCollider();
eventPlayer.Player.RemoveFromTriggers();
eventPlayer.RemoveFromNetwork();
OnEventPlayerDeath(eventPlayer, GetUser(hitInfo?.InitiatorPlayer), hitInfo);
ClearDamage(hitInfo);
}
internal virtual void OnEventPlayerDeath(BaseEventPlayer victim, BaseEventPlayer attacker = null, HitInfo hitInfo = null)
{
if (victim == null || victim.Player == null)
return;
StripInventory(victim.Player);
if (Configuration.Message.BroadcastKills)
DisplayKillToChat(victim, attacker?.Player != null ? attacker.Player.displayName : string.Empty);
}
/// <summary>
/// Display's the death message in chat
/// </summary>
/// <param name="victim"></param>
/// <param name="attackerName"></param>
protected virtual void DisplayKillToChat(BaseEventPlayer victim, string attackerName)
{
if (string.IsNullOrEmpty(attackerName))
{
if (victim.IsOutOfBounds)
BroadcastToPlayers("Notification.Death.OOB", victim.Player.displayName);
else BroadcastToPlayers("Notification.Death.Suicide", victim.Player.displayName);
}
else BroadcastToPlayers("Notification.Death.Killed", victim.Player.displayName, attackerName);
}
#endregion
#region Winners
/// <summary>
/// Applies winner statistics, give's rewards and print's winner information to chat
/// </summary>
protected void ProcessWinners()
{
List<BaseEventPlayer> winners = Pool.GetList<BaseEventPlayer>();
GetWinningPlayers(ref winners);
for (int i = 0; i < eventPlayers.Count; i++)
{
BaseEventPlayer eventPlayer = eventPlayers[i];
if (eventPlayer == null)
continue;
if (winners.Contains(eventPlayer))
{
EventStatistics.Data.AddStatistic(eventPlayer.Player, "Wins");
Instance.GiveReward(eventPlayer, Configuration.Reward.WinAmount);
}
else EventStatistics.Data.AddStatistic(eventPlayer.Player, "Losses");
EventStatistics.Data.AddStatistic(eventPlayer.Player, "Played");
}
if (Configuration.Message.BroadcastWinners && winners.Count > 0)
{
if (Plugin.IsTeamEvent)
{
Team team = winners[0].Team;
Broadcast("Notification.EventWin.Multiple.Team", team == Team.B ? TeamBColor : TeamAColor, team, winners.Select(x => x.Player.displayName).ToSentence());
}
else
{
if (winners.Count > 1)
Broadcast("Notification.EventWin.Multiple", winners.Select(x => x.Player.displayName).ToSentence());
else Broadcast("Notification.EventWin", winners[0].Player.displayName);
}
}
Pool.FreeList(ref winners);
}
/// <summary>
/// Override to calculate the winning player(s).This should done done on a per event basis
/// </summary>
/// <param name="list"></param>
protected virtual void GetWinningPlayers(ref List<BaseEventPlayer> list) { }
#endregion
#region Kits and Items
/// <summary>
/// Drop's the players belt and main containers in to a bag on death
/// </summary>
/// <returns>Return false to disable this feature</returns>
protected virtual bool CanDropBackpack() => true;
/// <summary>
/// Override to prevent players being given kits
/// </summary>
/// <param name="eventPlayer"></param>
/// <returns></returns>
protected virtual bool CanGiveKit(BaseEventPlayer eventPlayer) => true;
/// <summary>
/// Called after a player has been given a kit.If the event is team based and team attire kits have been set team attire will be given
/// </summary>
/// <param name="eventPlayer"></param>
protected virtual void OnKitGiven(BaseEventPlayer eventPlayer)
{
if (Plugin.IsTeamEvent)
{
string kit = eventPlayer.Team == Team.B ? Config.TeamConfigB.Clothing : Config.TeamConfigA.Clothing;
if (!string.IsNullOrEmpty(kit))
{
List<Item> items = eventPlayer.Player.inventory.containerWear.itemList;
for (int i = 0; i < items.Count; i++)
{
Item item = items[i];
item.RemoveFromContainer();
item.Remove();
}
GiveKit(eventPlayer.Player, kit);
}
}
}
/// <summary>
/// Get's the list of Kits available for the specified team
/// </summary>
/// <param name="team"></param>
/// <returns></returns>
internal List<string> GetAvailableKits(Team team) => team == Team.B ? Config.TeamConfigB.Kits : Config.TeamConfigA.Kits;
#endregion
#region Overrides
/// <summary>
/// Allows you to display additional event details in the event menu.The key should be a localized message for the target player
/// </summary>
/// <param name="list"></param>
/// <param name="playerId">The user's ID for localization purposes</param>
internal virtual void GetAdditionalEventDetails(ref List<KeyValuePair<string, object>> list, ulong playerId) { }
#endregion
#region Spectating
/// <summary>
/// Fill's a list with valid spectate targets
/// </summary>
/// <param name="list"></param>
internal virtual void GetSpectateTargets(ref List<BaseEventPlayer> list)
{
list.Clear();
list.AddRange(eventPlayers);
}
/// <summary>
/// Checks all spectating event players and updates their spectate target if the target has just died
/// </summary>
/// <param name="victim"></param>
private void UpdateDeadSpectateTargets(BaseEventPlayer victim)
{
List<BaseEventPlayer> list = Pool.GetList<BaseEventPlayer>();
GetSpectateTargets(ref list);
bool hasValidSpectateTargets = list.Count > 0;
for (int i = 0; i < eventPlayers.Count; i++)
{
BaseEventPlayer eventPlayer = eventPlayers[i];
if (eventPlayer.Player.IsSpectating() && eventPlayer.SpectateTarget == victim)
{
if (hasValidSpectateTargets)
eventPlayer.UpdateSpectateTarget();
else eventPlayer.FinishSpectating();
}
}
}
#endregion
#region Player Counts
/// <summary>
/// Count the amount of player's that are alive
/// </summary>
/// <returns></returns>
internal int GetAlivePlayerCount()
{
int count = 0;
for (int i = 0; i < eventPlayers.Count; i++)
{
if (!eventPlayers[i]?.IsDead ?? false)
count++;
}
return count;
}
/// <summary>
/// Count the amount of player's on the specified team
/// </summary>
/// <param name="team"></param>
/// <returns></returns>
internal int GetTeamCount(Team team)
{
int count = 0;
for (int i = 0; i < eventPlayers.Count; i++)
{
if (eventPlayers[i]?.Team == team)
count++;
}
return count;
}
/// <summary>
/// Count the amount of player's that are alive on the specified team
/// </summary>
/// <param name="team"></param>
/// <returns></returns>
internal int GetTeamAliveCount(Team team)
{
int count = 0;
for (int i = 0; i < eventPlayers.Count; i++)
{
BaseEventPlayer eventPlayer = eventPlayers[i];
if (eventPlayer != null && eventPlayer.Team == team && !eventPlayer.IsDead)
count++;
}
return count;
}
#endregion
#region Teams
/// <summary>
/// Get the score for the specified team
/// </summary>
/// <param name="team"></param>
/// <returns></returns>
internal virtual int GetTeamScore(Team team) => 0;
/// <summary>
/// Balance the team's if one team has > 2 more player's on it
/// </summary>
protected void BalanceTeams()
{
int aCount = GetTeamCount(Team.A);
int bCount = GetTeamCount(Team.B);
int difference = aCount > bCount + 1 ? aCount - bCount : bCount > aCount + 1 ? bCount - aCount : 0;
Team moveFrom = aCount > bCount + 1 ? Team.A : bCount > aCount + 1 ? Team.B : Team.None;
if (difference > 1 && moveFrom != Team.None)
{
BroadcastToPlayers("Notification.Teams.Unbalanced");
List<BaseEventPlayer> teamPlayers = Pool.GetList<BaseEventPlayer>();
eventPlayers.ForEach(x =>
{
if (x.Team == moveFrom)
teamPlayers.Add(x);
});
for (int i = 0; i < (int)Math.Floor((float)difference / 2); i++)
{
BaseEventPlayer eventPlayer = teamPlayers.GetRandom();
teamPlayers.Remove(eventPlayer);
eventPlayer.Team = moveFrom == Team.A ? Team.B : Team.A;
BroadcastToPlayer(eventPlayer, string.Format(Message("Notification.Teams.TeamChanged", eventPlayer.Player.userID), eventPlayer.Team));
}
Pool.FreeList(ref teamPlayers);
}
}
#endregion
#region Entity Management
/// <summary>
/// Keep's track of entities deployed by event players
/// </summary>
/// <param name="entity"></param>
internal void OnEntityDeployed(BaseCombatEntity entity) => _deployedObjects.Add(entity);
/// <summary>
/// Destroy's any entities deployed by event players
/// </summary>
private void CleanupEntities()
{
for (int i = _deployedObjects.Count - 1; i >= 0; i--)
{
BaseCombatEntity entity = _deployedObjects[i];
if (entity != null && !entity.IsDestroyed)
entity.DieInstantly();
}
_deployedObjects.Clear();
}
#endregion
#region Scoreboard
/// <summary>
/// Rebuild and send the scoreboard to players
/// </summary>
internal void UpdateScoreboard()
{
UpdateScores();
BuildScoreboard();
if (scoreContainer != null)
{
eventPlayers.ForEach((Bas
// ... [Code preview truncated due to length. Full source in attached file] ...