C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
//Requires: EventHelper
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
/* 1.0.1
* Added API Calls:
- void Skimish_StartRegistration(string activeArena, bool isFFA);
- void Skimish_EndGame();
- void Skimish_StartGame(string activeArena, bool isFFA);
* Changed the message order in OnEntityTakeDamage.* Added a new UI to start and end the game.Type /sk
*/
namespace Oxide.Plugins {
[Info("Skirmish", "imthenewguy", "1.0.5")]
[Description("This plugin was fixed by Инкуб to order [Rust Plugin Sliv]: https://discord.gg/pFgKw6Dyyq")]
class Skirmish : RustPlugin {
#region Config
private Configuration config;
public class Configuration {
[JsonProperty("Maximum hits a player can take before they are out")]
public int max_hits = 7;
[JsonProperty("Maximum ammo capacity of a snowball launcher in the event")]
public int max_ammo = 10;
[JsonProperty("Minimum players required to start a game")]
public int minimum_players = 2;
[JsonProperty("How many seconds do the players have to register to player before we start")]
public float seconds_before_game_starts = 300;
[JsonProperty("Chance for a game to be FFA instead of TDM [%]")]
public int ffa_chance = 40;
[JsonProperty("Maximum time that the game can run for (seconds)")]
public float max_time = 1800f;
[JsonProperty("Sound effect")]
public string damage_sound = "assets/prefabs/deployable/barricades/effects/damage.prefab";
[JsonProperty("Reward dead players on the winning team")]
public bool reward_dead = true;
[JsonProperty("Free for all suit [shortname]")]
public string ffa_suit = "hazmatsuit.nomadsuit";
[JsonProperty("Team death match - team 1 suit [shortname]")]
public string tdm1_suit = "hazmatsuit_scientist_peacekeeper";
[JsonProperty("Team death match - team 2 suit [shortname]")]
public string tdm2_suit = "hazmatsuit_scientist";
[JsonProperty("Clear all data on map wipe")]
public bool clear_data = true;
[JsonProperty("Run the game automatically with EventHelper?")]
public bool use_event_helper = true;
[JsonProperty("How often should we start a game (if not being managed through EventHelper)")]
public float auto_start_time = 7200;
[JsonProperty("Clear NPCs from around the arena when the game starts?")]
public bool delete_npcs = false;
[JsonProperty("Max distance from anchor entities that NPCs will be deleted from on game start.")]
public float delete_max_distance = 100f;
[JsonProperty("Commands to prevent when a player are at the event")]
public string[] prevent_commands = {
"kit"
}
;
[JsonProperty("Automatically give players their items back after leaving [if running auto kit set to false]")]
public bool kit_back_on_death = true;
[JsonProperty("Announce when players join the event")]
public bool announce_on_join = true;
[JsonProperty("Maximum number of participants allowed per round (0 == no limit)")]
public int max_players = 32;
[JsonProperty("Prize table")]
public List<PrizeInfo> prizes = new List<PrizeInfo>();
[JsonProperty("Arena setups - Make sure your entity combinations and arena names are unique")]
public Dictionary<string, ArenaInfo> arenas = new Dictionary<string, ArenaInfo>();
public string ToJson() => JsonConvert.SerializeObject(this);
public Dictionary<string, object> ToDictionary() => JsonConvert.DeserializeObject<Dictionary<string, object>>(ToJson());
}
protected override void LoadDefaultConfig() {
config = new Configuration();
config.arenas = default_Arenas;
config.prizes = defaultPrizes;
}
public List<PrizeInfo> defaultPrizes {
get {
return new List<PrizeInfo>() {
new PrizeInfo("scrap", 50, 200), new PrizeInfo("wood", 1500, 5000), new PrizeInfo("stones", 1500, 5000), new PrizeInfo("sulfur", 1000, 2000), new PrizeInfo("potato", 5, 20, 0, "tasty potatoes")
}
;
}
}
protected override void LoadConfig() {
base.LoadConfig();
try {
config = Config.ReadObject<Configuration>();
if (config == null) {
throw new JsonException();
}
if (!config.ToDictionary().Keys.SequenceEqual(Config.ToDictionary(x => x.Key, x => x.Value).Keys)) {
PrintToConsole("Configuration appears to be outdated; updating and saving");
SaveConfig();
}
}
catch {
PrintToConsole($"Configuration file {Name}.json is invalid; using defaults");
LoadDefaultConfig();
}
}
protected override void SaveConfig() {
PrintToConsole($"Configuration changes saved to {Name}.json");
Config.WriteObject(config, true);
}
public class ArenaInfo {
public bool enabled = true;
public string Anchor_Entity_Main;
public string Anchor_Entity_Secondary;
public ulong Anchor_id;
public List<Vector3> FFA_Spawns = new List<Vector3>();
public List<Vector3> TDM_Spawns_1 = new List<Vector3>();
public List<Vector3> TDM_Spawns_2 = new List<Vector3>();
}
public class PrizeInfo {
public string shortname;
public ulong skin;
public string displayname;
public int min_amount;
public int max_amount;
public PrizeInfo(string shortname, int min_amount, int max_amount, ulong skin = 0, string displayname = null) {
this.shortname = shortname;
this.skin = skin;
this.min_amount = min_amount;
this.max_amount = max_amount;
this.displayname = displayname;
}
}
#endregion
#region Data
PlayerEntity pcdData;
private DynamicConfigFile PCDDATA;
void Init() {
PCDDATA = Interface.Oxide.DataFileSystem.GetFile(this.Name);
LoadData();
permission.RegisterPermission("skirmish.admin", this);
UnsubHooks();
}
void Unload() {
foreach (var player in BasePlayer.activePlayerList) {
CuiHelper.DestroyUi(player, "paintball_splats");
CuiHelper.DestroyUi(player, "SkirmishStartPanel");
}
EndGame();
EventHelper.Call("EMRemoveEvent", this.Name);
SaveData();
}
void SaveData() {
PCDDATA.WriteObject(pcdData);
}
void LoadData() {
try {
pcdData = Interface.Oxide.DataFileSystem.ReadObject<PlayerEntity>(this.Name);
}
catch {
Puts(lang.GetMessage("PlayerDataLoading", this));
pcdData = new PlayerEntity();
}
}
class PlayerEntity {
public Dictionary<ulong, PlayerInfo> pEntity = new Dictionary<ulong, PlayerInfo>();
public Vector3 lobby_point;
public Dictionary<string, ArenaInfo> arenas = new Dictionary<string, ArenaInfo>();
}
class TeleportInfo {
public List<Vector3> ffa_destinations = new List<Vector3>();
public Dictionary<int, List<Vector3>> tdm_destinations = new Dictionary<int, List<Vector3>>();
}
class PlayerInfo {
public int prizes;
}
#endregion;
#region Localization
protected override void LoadDefaultMessages() {
lang.RegisterMessages(new Dictionary<string, string> {
["EliminatedYou"]
= "You have been eliminated by <color=#DFF008>{0}</color>.", ["EliminatedOther"] = "You eliminated <color=#DFF008>{0}</color>.", ["UnclaimedPrizes"] = "You have unclaimed prizes from winning round(s) of skirmish.Type <color=#13ff00>/skprize</color> to redeem them.", ["MissingLobby"] = "Attempted to start skirmish up but we are missing lobby destination.", ["GameStarting"] = "A game of skirmish at the <color=#ffae00>{0}</color> will start in <color=#ffae00>{1}</color> seconds.Type <color=#13ff00>/skjoin</color> to join.\nGame Type: <color=#ffae00>{2}</color>", ["SkirmishStart60"] = "Skirmish starts in <color=#ffae00>60 seconds</color>.Type <color=#13ff00>/skjoin</color> to join.", ["SkirmishStarted"] = "Skirmish registration has closed.", ["SkirmishCancelledNoPlayers"] = "Cancelling game due to low player count.", ["SkirmishCancelledNoArenas"] = "Could not find arena data for {0}.", ["GameStarted"] = "Skirmish has started!", ["EndNoWinners"] = "There were no winners of the skirmish match as everyone died.", ["EndWinnerSolo"] = "<color=#13ff00>{0}</color> has won the round!", ["EndWinnerTeam"] = "<color=#13ff00>Team {0}</color> are the winners:\n- {1}", ["MsgWinner"] = "You received a prize for winning (Available: <color=#13ff00>{0}</color>).Type <color=#13ff00>/skprize</color> to redeem it.", ["MsgWinnerTeam"] = "You received a prize for winning (Available: {0}).Type /skprize to redeem it.", ["ClearedData"] = "Cleared data", ["CannotRedeem"] = "You cannot redeem these while in a match.", ["NoPrizes"] = "You do not have any prizes outstanding.", ["NullPrize"] = "Error - prize was null", ["PrizeReceived"] = "You received <color=#13ff00>{0}x {1}</color>.", ["NoGamesRunning"] = "No games running.", ["NotJoiningAllowed"] = "You are not allowed to join this game.", ["AlreadyRegistered"] = "You have already registed for this event.", ["ErrorJoining"] = "Could not join event.Check print out from EventHelper for more info.", ["GameAlreadyRunning"] = "Game already running", ["EndedMatch"] = "Ended match", ["UnloadingSkirmish"] = "Unloading Skirmish...", ["NoLobbyFoundConsole"] = "No lobby point found.Type /sksetlobby while in game to set one.", ["ConsoleFoundStoredAnchors"] = "Found and stored {0} arenas into memory.", ["AutoStartingConsole"] = "Auto starting Skirmish", ["AddingPrizesConsole"] = "Adding prizes", ["ClearingAI"] = "Clearing AI from arenas.", ["SetLobbyManually"] = "Set the lobby postion to {0}", ["AnchorPairFound"] = "Found the {0} anchor from saved data.", ["SearchingAnchors"] = "Existing anchor data for {0} is invalid.Searching for anchor entity.", ["AnchorFailed"] = "Could not acquire anchors for {0}", ["NewAnchorFound"] = "Found new arena: {0}.Adding to data.", ["NoArenaData"] = "No arena data could be found.", ["PlayerDataLoading"] = "Couldn't load player data, creating new Playerfile", ["UITitle"] = "Skirmish Event", ["UIisRunning"] = "Game Running", ["UIArena"] = "Arena", ["UIMode"] = "Mode", ["UIDelayTitle"] = "Delay before start", ["UIStart"] = "START", ["UIStop"] = "STOP", ["EventFull"] = "This event is full.", ["JoinedMessage"] = "<color=#fbff00>{0}</color> joined the <color=#ffa200>Skirmish</color> lobby <color=#fbff00>[</color>{1}<color=#fbff00>]</color>"
}
, this);
}
#endregion
#region Globals
class PaintballInfo {
public int hits_taken;
public int team;
}
Dictionary<BasePlayer, PaintballInfo> Participants = new Dictionary<BasePlayer, PaintballInfo>();
List<BasePlayer> corpseWatch = new List<BasePlayer>();
private List<PlayerCorpse> Corpses = new List<PlayerCorpse>();
const string perm_admin = "skirmish.admin";
bool isRunning;
bool AllowJoin;
bool isFFA;
bool GameStarted;
Timer GameDelayTimer;
List<BasePlayer> team_1 = new List<BasePlayer>();
List<BasePlayer> team_2 = new List<BasePlayer>();
string active_arena;
Dictionary<string, BaseNetworkable> anchor_entities = new Dictionary<string, BaseNetworkable>();
[PluginReference]
private Plugin EventHelper, NightVision, ImageLibrary;
#endregion
#region Hooks
void OnPlayerDisconnected(BasePlayer player, string reason) {
LeaveEvent(player);
}
object CanMountEntity(BasePlayer player, BaseMountable entity) {
if (Participants.ContainsKey(player)) return false;
return null;
}
void OnEntitySpawned(BaseNetworkable entity) {
if (active_arena == null) return;
if (entity != null && Vector3.Distance(entity.transform.position, anchor_entities[active_arena].transform.position) < 80) {
if (entity is ScientistNPC || entity is BaseAnimalNPC) entity.KillMessage();
}
}
void OnServerInitialized(bool initial) {
if (config.arenas == null || config.arenas.Count == 0) {
config.arenas = default_Arenas;
SaveConfig();
}
if (!FoundAnchors()) {
Puts(lang.GetMessage("UnloadingSkirmish", this));
Interface.Oxide.UnloadPlugin(Name);
return;
}
if (pcdData.lobby_point == Vector3.zero) Puts(lang.GetMessage("NoLobbyFoundConsole", this));
EventHelper.Call("EMCreateEvent", this.Name, config.use_event_helper, true, true, true, config.kit_back_on_death, true, pcdData.lobby_point);
EventHelper.Call("EMExternalPluginSettings", this.Name);
EventHelper.Call("EMBlackListCommands", this.Name, config.prevent_commands);
Puts(string.Format(lang.GetMessage("ConsoleFoundStoredAnchors", this), anchor_entities.Count));
splats = new List<string>() {
"splat1", "splat2", "splat3", "splat4", "splat5", "splat6"
}
;
ImageLibrary?.Call("AddImage", "https://gspics.org/images/2024/01/21/0lmvNN.png", "splat1");
ImageLibrary?.Call("AddImage", "https://gspics.org/images/2024/01/21/0lmXHw.png", "splat2");
ImageLibrary?.Call("AddImage", "https://gspics.org/images/2024/01/21/0lmgwh.png", "splat3");
ImageLibrary?.Call("AddImage", "https://gspics.org/images/2024/01/21/0lm3Ga.png", "splat4");
ImageLibrary?.Call("AddImage", "https://gspics.org/images/2024/01/21/0lmW8x.png", "splat5");
ImageLibrary?.Call("AddImage", "https://gspics.org/images/2024/01/21/0lm6oQ.png", "splat6");
if (!config.use_event_helper) {
if (config.auto_start_time > 0) GameDelayTimer = timer.Every(config.auto_start_time, () => {
Puts(lang.GetMessage("AutoStartingConsole", this));
StartRegistration();
}
);
}
if (config.prizes == null || config.prizes.Count == 0) {
Puts(lang.GetMessage("AddingPrizesConsole", this));
config.prizes = defaultPrizes;
SaveConfig();
}
Puts(lang.GetMessage("ClearingAI", this));
foreach (var arena in anchor_entities) {
ClearAI(arena.Value.transform.position);
}
}
void ClearAI(Vector3 pos) {
if (!config.delete_npcs) return;
var ai = BaseNetworkable.serverEntities.Where(x => (x is BaseAnimalNPC || x is ScientistNPC) && Vector3.Distance(x.transform.position, pos) < config.delete_max_distance).ToList();
if (ai == null || ai.Count == 0) return;
foreach (var entity in ai.ToList()) {
entity.KillMessage();
}
}
object CanDropActiveItem(BasePlayer player) {
if (Participants.ContainsKey(player)) return false;
return null;
}
object OnItemAction(Item item, string action, BasePlayer player) {
if (Participants.ContainsKey(player)) return false;
return null;
}
void OnPlayerDeath(BasePlayer player, HitInfo info) {
if (!Participants.ContainsKey(player)) return;
if (NightVision != null) {
NightVision.Call("UnlockPlayerTime", player);
permission.RevokeUserPermission(player.UserIDString, "nightvision.allowed");
}
corpseWatch.Add(player);
Participants.Remove(player);
CheckWin();
}
void OnNewSave(string filename) {
pcdData.arenas.Clear();
pcdData.lobby_point = Vector3.zero;
if (config.clear_data) {
ClearData();
}
}
object OnEntityTakeDamage(BasePlayer player, HitInfo info) {
PaintballInfo pi;
if (player == null || info == null || !Participants.TryGetValue(player, out pi)) return null;
var attacker = info.InitiatorPlayer;
if (attacker == null || !Participants.ContainsKey(attacker)) return null;
PaintballInfo api;
if (!Participants.TryGetValue(attacker, out api) || (api.team == pi.team && !isFFA)) return null;
var weapon = attacker.GetActiveItem();
if (weapon == null) return null;
if (weapon.info.shortname == "snowball" || weapon.info.shortname == "snowballgun") {
pi.hits_taken++;
if (pi.hits_taken >= config.max_hits) {
PrintToChat(player, string.Format(lang.GetMessage("EliminatedYou", this, player.UserIDString), attacker.displayName));
PrintToChat(attacker, string.Format(lang.GetMessage("EliminatedOther", this, player.UserIDString), player.displayName));
LeaveEvent(player);
CheckWin();
}
else {
SendSplats(player, pi.hits_taken);
EffectNetwork.Send(new Effect(config.damage_sound, player.transform.position, player.transform.position), player.net.connection);
}
return false;
}
return null;
}
void OnPlayerCorpseSpawned(BasePlayer player, PlayerCorpse corpse) {
if (!isRunning) return;
if (corpseWatch.Contains(player)) {
Corpses.Add(corpse);
corpseWatch.Remove(player);
}
}
void OnPlayerRespawned(BasePlayer player) {
if (permission.UserHasPermission(player.UserIDString, "nightvision.allowed")) permission.RevokeUserPermission(player.UserIDString, "nightvision.allowed");
if (pcdData.pEntity.ContainsKey(player.userID) && pcdData.pEntity[player.userID].prizes > 0) PrintToChat(player, lang.GetMessage("UnclaimedPrizes", this, player.UserIDString));
if (Convert.ToBoolean(EventHelper.Call("EMIsParticipating", player, this.Name))) {
EventHelper.Call("EMPlayerLeaveEvent", player, this.Name);
}
}
object CanLootEntity(BasePlayer player, StorageContainer container) {
if (Participants.ContainsKey(player)) return false;
return null;
}
object CanLootEntity(BasePlayer player, LootableCorpse container) {
if (Corpses.Contains(container) && !Participants.ContainsKey(player)) return false;
return null;
}
#endregion
#region Helpers
void UnsubHooks() {
Unsubscribe("OnEntityTakeDamage");
Unsubscribe("CanDropActiveItem");
Unsubscribe("OnItemAction");
Unsubscribe("OnPlayerDeath");
Unsubscribe("OnPlayerCorpseSpawned");
Unsubscribe("CanEntityTakeDamage");
Unsubscribe("CanMountEntity");
Unsubscribe("CanLootEntity");
Unsubscribe("OnExitZone");
Unsubscribe("OnEnterZone");
Unsubscribe("OnEntitySpawned");
Unsubscribe("OnPlayerDisconnected");
}
void DeleteTimers() {
foreach (var t in _timers) {
if (!t.Destroyed) t.Destroy();
}
_timers.Clear();
}
void EndGame() {
// Do end game stuff
Interface.Call("Skimish_EndGame");
DeleteTimers();
if (Participants != null && Participants.Count > 0) {
foreach (var player in Participants.Keys.ToList()) {
if (NightVision != null) {
NightVision.Call("UnlockPlayerTime", player);
permission.RevokeUserPermission(player.UserIDString, "nightvision.allowed");
}
if (player.IsAlive() && player.IsConnected) {
EventHelper.Call("EMPlayerLeaveEvent", player, this.Name);
}
Participants.Remove(player);
}
}
if (Corpses.Count > 0) {
foreach (var corpse in Corpses.ToList()) {
if (corpse != null) corpse.Kill();
}
}
Corpses.Clear();
corpseWatch.Clear();
if (BasePlayer.activePlayerList != null && BasePlayer.activePlayerList.Count > 0) {
foreach (var player in BasePlayer.activePlayerList) {
CuiHelper.DestroyUi(player, "paintball_splats");
}
}
team_1.Clear();
team_2.Clear();
if (!config.use_event_helper) {
if (GameDelayTimer != null && !GameDelayTimer.Destroyed) GameDelayTimer.Destroy();
if (config.auto_start_time > 0) GameDelayTimer = timer.Every(config.auto_start_time, () => {
Puts(lang.GetMessage("AutoStartingConsole", this));
StartRegistration();
}
);
}
UnsubHooks();
active_arena = null;
if (isRunning) {
EventHelper.Call("EMEndEvent", this.Name);
PrintToChat(lang.GetMessage("EndedMatch", this));
isRunning = false;
}
AllowJoin = false;
GameStarted = false;
}
List<Timer> _timers = new List<Timer>();
private void StartRegistration(float time_override = 0, string arena = null, string mode = null) {
if (isRunning) return;
if (GameDelayTimer != null && GameDelayTimer.Destroyed) GameDelayTimer.Destroy();
var randomArena = pcdData.arenas.ToList().GetRandom();
if (!string.IsNullOrEmpty(arena)) {
randomArena = new KeyValuePair<string, ArenaInfo>(arena, config.arenas[arena]);
}
if (pcdData.lobby_point == Vector3.zero || (randomArena.Value.FFA_Spawns == null && config.ffa_chance > 0) || ((randomArena.Value.TDM_Spawns_1.Count == 0 || randomArena.Value.TDM_Spawns_2.Count == 0) && config.ffa_chance < 100)) {
PrintToChat(lang.GetMessage("MissingLobby", this));
return;
}
// Set the active arena key to active_arena var.active_arena = randomArena.Key;
Subscribe("OnPlayerDeath");
Subscribe("OnPlayerCorpseSpawned");
Subscribe("CanMountEntity");
Subscribe("CanLootEntity");
Subscribe("OnPlayerDisconnected");
EventHelper.Call("EMStartEvent", this.Name);
isRunning = true;
AllowJoin = true;
if (string.IsNullOrEmpty(mode)) {
if (config.ffa_chance == 100) isFFA = true;
else if (config.ffa_chance == 0) isFFA = false;
else if (UnityEngine.Random.Range(0, 100) < config.ffa_chance + 1) isFFA = true;
else isFFA = false;
}
else {
if (mode == "ffa") isFFA = true;
else isFFA = false;
}
var str = isFFA ? "Free-For-All" : "Team Death Match";
var time_to_start = time_override > 0 ? time_override : config.seconds_before_game_starts;
PrintToChat(string.Format(lang.GetMessage("GameStarting", this), randomArena.Key, time_to_start, str));
Interface.Call("Skimish_StartRegistration", active_arena, isFFA);
if (time_to_start > 60) {
_timers.Add(timer.Once(time_to_start - 60, () => {
PrintToChat(lang.GetMessage("SkirmishStart60", this));
return;
}
));
}
_timers.Add(timer.Once(time_to_start, () => {
PrintToChat(lang.GetMessage("SkirmishStarted", this));
StartGame();
}
));
}
void StartGame() {
AllowJoin = false;
Subscribe("OnEntityTakeDamage");
Subscribe("CanDropActiveItem");
Subscribe("OnItemAction");
Subscribe("CanEntityTakeDamage");
Subscribe("OnEntitySpawned");
if (Participants.Count < config.minimum_players) {
PrintToChat(lang.GetMessage("SkirmishCancelledNoPlayers", this));
EndGame();
return;
}
ArenaInfo arenaData;
if (!pcdData.arenas.TryGetValue(active_arena, out arenaData)) {
Puts(string.Format(lang.GetMessage("SkirmishCancelledNoArenas", this), active_arena));
EndGame();
return;
}
Interface.Call("Skimish_StartGame", active_arena, isFFA);
PrintToChat(lang.GetMessage("GameStarted", this));
GameStarted = true;
Puts(lang.GetMessage("ClearingAI", this));
ClearAI(anchor_entities[active_arena].transform.position);
if (isFFA) {
List<Vector3> used = new List<Vector3>();
foreach (var player in Participants.Keys) {
if (used.Count == arenaData.FFA_Spawns.Count) used.Clear();
Vector3 RandomPos = arenaData.FFA_Spawns.Where(x => !used.Contains(x)).ToList().GetRandom();
player.flyhackPauseTime = 10f;
Player.Teleport(player, RandomPos);
player.StartSleeping();
player.SetPlayerFlag(BasePlayer.PlayerFlags.ReceivingSnapshot, true);
player.ClientRPCPlayer(null, player, "StartLoading");
player.SendEntityUpdate();
player.UpdateNetworkGroup();
player.SendNetworkUpdateImmediate(false);
GiveSnowballLauncher(player);
}
}
else {
System.Random rand = new System.Random();
var lastGiven = 0;
foreach (var player in Participants.OrderBy(x => rand.Next()).ToDictionary(item => item.Key, item => item.Value)) {
if (lastGiven == 0) {
player.Value.team = 1;
lastGiven++;
}
else if (lastGiven == 1) {
player.Value.team = 2;
lastGiven++;
}
else {
player.Value.team = 1;
lastGiven = 1;
}
player.Key.flyhackPauseTime = 10f;
if (player.Value.team == 1) Player.Teleport(player.Key, arenaData.TDM_Spawns_1.GetRandom());
else Player.Teleport(player.Key, arenaData.TDM_Spawns_2.GetRandom());
player.Key.StartSleeping();
player.Key.SetPlayerFlag(BasePlayer.PlayerFlags.ReceivingSnapshot, true);
player.Key.ClientRPCPlayer(null, player.Key, "StartLoading");
player.Key.SendEntityUpdate();
player.Key.UpdateNetworkGroup();
player.Key.SendNetworkUpdateImmediate(false);
GiveSnowballLauncher(player.Key, player.Value.team);
if (player.Value.team == 1) team_1.Add(player.Key);
else team_2.Add(player.Key);
}
}
_timers.Add(timer.Once(config.max_time, () => {
EndGame();
}
));
}
void ClearData() {
pcdData.pEntity.Clear();
SaveData();
}
private void LeaveEvent(BasePlayer player, bool manually_left = false, bool died = false) {
if (Participants.ContainsKey(player)) Participants.Remove(player);
if (NightVision != null) {
NightVision.Call("UnlockPlayerTime", player);
permission.RevokeUserPermission(player.UserIDString, "nightvision.allowed");
}
if (!died) {
EventHelper.Call("EMPlayerLeaveEvent", player, this.Name, manually_left);
}
CuiHelper.DestroyUi(player, "paintball_splats");
CheckWin();
}
void GiveSnowballLauncher(BasePlayer player, int team = 0) {
var item = ItemManager.CreateByName("snowballgun", 1, 0);
(item.GetHeldEntity() as BaseProjectile).primaryMagazine.capacity = config.max_ammo;
player.GiveItem(item);
for (int i = 0; i < 24 - player.inventory.containerMain.itemList.Count; i++) {
player.GiveItem(ItemManager.CreateByName("snowball", 10));
}
if (team == 0) {
ItemManager.CreateByName(config.ffa_suit, 1).MoveToContainer(player.inventory.containerWear);
}
else if (team == 1) ItemManager.CreateByName(config.tdm1_suit, 1).MoveToContainer(player.inventory.containerWear);
else ItemManager.CreateByName(config.tdm2_suit, 1).MoveToContainer(player.inventory.containerWear);
}
void CheckWin() {
if (!isRunning || !GameStarted) return;
if (Participants.Count == 0) {
PrintToChat(lang.GetMessage("EndNoWinners", this));
EndGame();
return;
}
if (isFFA) {
if (Participants.Count == 1) {
var winner = Participants.Keys.First();
PrintToChat(string.Format(lang.GetMessage("EndWinnerSolo", this), winner.displayName));
PlayerInfo pi;
if (!pcdData.pEntity.TryGetValue(winner.userID, out pi)) pcdData.pEntity.Add(winner.userID, pi = new PlayerInfo());
PrintToChat(winner, string.Format(lang.GetMessage("MsgWinner", this, winner.UserIDString), pi.prizes));
pi.prizes++;
Interface.CallHook("SKWinner", winner);
EndGame();
}
}
else {
var team_1_count = 0;
var team_2_count = 0;
foreach (var player in Participants) {
if (player.Value.team == 1) team_1_count++;
else if (player.Value.team == 2) team_2_count++;
}
if (team_2_count <= 0 || team_1_count <= 0) {
var str = team_1_count <= 0 ? "2" : "1";
List<BasePlayer> winners;
if (config.reward_dead) {
if (str == "1") winners = team_1;
else winners = team_2;
}
else winners = Participants.Keys.ToList();
PrintToChat(string.Format(lang.GetMessage("EndWinnerTeam", this), str, string.Join("\n- ", Participants.Keys.Select(x => x.displayName).ToList())));
foreach (var player in winners) {
if (player == null) continue;
PlayerInfo pi;
if (!pcdData.pEntity.TryGetValue(player.userID, out pi)) pcdData.pEntity.Add(player.userID, pi = new PlayerInfo());
pi.prizes++;
PrintToChat(player, string.Format(lang.GetMessage("MsgWinner", this, player.UserIDString), pi.prizes));
}
Interface.CallHook("SKWinners", winners);
EndGame();
}
}
}
#endregion
#region Chat Commands
[ChatCommand("skcleardata")]
void ClearSkirmishData(BasePlayer player) {
ClearData();
PrintToChat(player, lang.GetMessage("ClearedData", this, player.UserIDString));
}
[ChatCommand("sksetlobby")]
void SetLobby(BasePlayer player) {
if (!permission.UserHasPermission(player.UserIDString, perm_admin)) return;
pcdData.lobby_point = player.transform.position;
EventHelper.Call("EMUpdateLobby", this.Name, pcdData.lobby_point);
PrintToChat(player, string.Format(lang.GetMessage("SetLobbyManually", this, player.UserIDString), player.transform.position));
}
[ChatCommand("skprize")]
void RedeemReward(BasePlayer player) {
if (Participants.ContainsKey(player)) {
PrintToChat(player, lang.GetMessage("CannotRedeem", this, player.UserIDString));
return;
}
PlayerInfo pi;
if (!pcdData.pEntity.TryGetValue(player.userID, out pi) || pi.prizes <= 0) {
PrintToChat(player, lang.GetMessage("NoPrizes", this, player.UserIDString));
return;
}
var prize = config.prizes.GetRandom();
if (prize == null) {
PrintToChat(player, lang.GetMessage("NullPrize", this, player.UserIDString));
return;
}
var item = ItemManager.CreateByName(prize.shortname, UnityEngine.Random.Range(prize.min_amount, prize.max_amount + 1), prize.skin);
if (item == null) return;
if (prize.displayname != null) item.name = prize.displayname;
player.GiveItem(item);
PrintToChat(player, string.Format(lang.GetMessage("PrizeReceived", this, player.UserIDString), item.amount, item.name ?? item.info.displayName.english));
pi.prizes--;
if (pi.prizes == 0) pcdData.pEntity.Remove(player.userID);
}
[ChatCommand("skjoin")]
void RegisterPlayer(BasePlayer player) {
if (!isRunning) {
PrintToChat(player, lang.GetMessage("NoGamesRunning", this, player.UserIDString));
return;
}
if (!AllowJoin) {
PrintToChat(player, lang.GetMessage("NotJoiningAllowed", this, player.UserIDString));
return;
}
if (Participants.ContainsKey(player)) {
PrintToChat(player, lang.GetMessage("AlreadyRegistered", this, player.UserIDString));
return;
}
if (config.max_players > 0 && Participants.Count >= config.max_players) {
PrintToChat(player, lang.GetMessage("EventFull", this, player.UserIDString));
return;
}
if (!Convert.ToBoolean(EventHelper.Call("EMEnrollPlayer", player, this.Name))) {
PrintToChat(player, lang.GetMessage("ErrorJoining", this, player.UserIDString));
return;
}
if (NightVision != null) {
permission.GrantUserPermission(player.UserIDString, "nightvision.allowed", NightVision);
NightVision.Call("LockPlayerTime", player, 10f);
}
Participants.Add(player, new PaintballInfo());
if (config.announce_on_join) {
var str = config.max_players > 0 ? $"{Participants.Count}/{config.max_players}" : Participants.Count.ToString();
PrintToChat(string.Format(lang.GetMessage("JoinedMessage", this), player.displayName, str));
}
}
[ChatCommand("skleave")]
void LeaveSkirmish(BasePlayer player) {
if (Participants.ContainsKey(player)) {
LeaveEvent(player, true);
}
}
[ChatCommand("skstart")]
void StartSkirmishCMD(BasePlayer player, string command, string[] args) {
if (!permission.UserHasPermission(player.UserIDString, perm_admin)) return;
if (isRunning) {
PrintToChat(player, lang.GetMessage("GameAlreadyRunning", this, player.UserIDString));
return;
}
var time_override = 0f;
if (args.Length == 1 && args[0].IsNumeric()) time_override = Convert.ToSingle(args[0]);
if (config.use_event_helper) {
EventHelper.Call("EMManuallyStarted", this.Name);
}
StartRegistration(time_override);
}
[ChatCommand("skend")]
void EndSkirmishCMD(BasePlayer player) {
if (!permission.UserHasPermission(player.UserIDString, perm_admin)) return;
EndGame();
}
#endregion
#region API
object CanEntityTakeDamage(BasePlayer player, HitInfo info) {
if (player == null || info == null) return null;
if (!player.IsNpc && player.userID.IsSteamId()) {
PaintballInfo pi;
if (!Participants.TryGetValue(player, out pi)) return null;
var attacker = info.InitiatorPlayer;
if (attacker == null || attacker.IsNpc || !attacker.userID.IsSteamId()) return null;
if (!Participants.ContainsKey(attacker)) return false;
PaintballInfo api;
if (!Participants.TryGetValue(attacker, out api)) return null;
if (pi.team == api.team && !isFFA) return false;
return false;
}
return null;
}
void EMEndGame(string eventName) {
if (eventName == this.Name) {
EndGame();
}
}
void EMStartNextEvent(string eventName) {
if (eventName == this.Name) {
StartRegistration();
}
}
#endregion
#region Splats
List<string> splats;
void SendSplats(BasePlayer player, int quantity) {
var container = new CuiElementContainer();
container.Add(new CuiPanel {
CursorEnabled = false, Image = {
Color = "0.1254902 0.1254902 0.1254902 0"
}
, RectTransform = {
AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", OffsetMin = "-16.003 -16", OffsetMax = "15.997 16"
}
}
, "Overlay", "paintball_splats");
if (quantity >= 1) {
container.Add(new CuiElement {
Name = "splat_1", Parent = "paintball_splats", Components = {
new CuiRawImageComponent {
Color = "1 1 1 0.8", Png = (string)ImageLibrary?.Call("GetImage", splats.GetRandom())
}
, new CuiRectTransformComponent {
AnchorMin = "0 1", AnchorMax = "0 1", OffsetMin = "-501.997 13.337", OffsetMax = "-245.997 269.337"
}
}
}
);
}
if (quantity >= 2) {
container.Add(new CuiElement {
Name = "splat_2", Parent = "paintball_splats", Components = {
new CuiRawImageComponent {
Color = "1 1 1 0.8", Png = (string)ImageLibrary?.Call("GetImage", splats.GetRandom())
}
, new CuiRectTransformComponent {
AnchorMin = "1 1", AnchorMax = "1 1", OffsetMin = "246.004 13.677", OffsetMax = "502.004 269.677"
}
}
}
);
}
if (quantity >= 3) {
container.Add(new CuiElement {
Name = "splat_3", Parent = "paintball_splats", Components = {
new CuiRawImageComponent {
Color = "1 1 1 0.8", Png = (string)ImageLibrary?.Call("GetImage", splats.GetRandom())
}
, new CuiRectTransformComponent {
AnchorMin = "0 0", AnchorMax = "0 0", OffsetMin = "-501.997 -269.997", OffsetMax = "-245.997 -13.997"
}
}
}
);
}
if (quantity >= 4) {
container.Add(new CuiElement {
Name = "splat_4", Parent = "paintball_splats", Components = {
new CuiRawImageComponent {
Color = "1 1 1 0.8", Png = (string)ImageLibrary?.Call("GetImage", splats.GetRandom())
}
, new CuiRectTransformComponent {
AnchorMin = "1 0", AnchorMax = "1 0", OffsetMin = "246.004 -269.657", OffsetMax = "502.004 -13.657"
}
}
}
);
}
if (quantity >= 5) {
container.Add(new CuiElement {
Name = "splat_5", Parent = "paintball_splats", Components = {
new CuiRawImageComponent {
Color = "1 1 1 0.8", Png = (string)ImageLibrary?.Call("GetImage", splats.GetRandom())
}
, new CuiRectTransformComponent {
AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", OffsetMin = "-261.997 -127.997", OffsetMax = "-5.997 128.003"
}
}
}
);
}
if (quantity >= 6) {
container.Add(new CuiElement {
Name = "splat_6", Parent = "paintball_splats", Components = {
new CuiRawImageComponent {
Color = "1 1 1 0.8", Png = (string)ImageLibrary?.Call("GetImage", splats.GetRandom())
}
, new CuiRectTransformComponent {
AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", OffsetMin = "6.003 -127.997", OffsetMax = "262.003 128.003"
}
}
}
);
}
CuiHelper.DestroyUi(player, "paintball_splats");
CuiHelper.AddUi(player, container);
}
#endregion
#region Find Anchors
bool FoundAnchors() {
foreach (var anchor_pair in config.arenas) {
if (!anchor_pair.Value.enabled) continue;
ArenaInfo arenaData;
if (pcdData.arenas.TryGetValue(anchor_pair.Key, out arenaData)) {
NetworkableId anchorId = new NetworkableId(arenaData.Anchor_id);
var anchor = BaseNetworkable.serverEntities.Find(anchorId);
if (anchor != null) {
Puts(string.Format(lang.GetMessage("AnchorPairFound", this), anchor_pair.Key));
if (!anchor_entities.ContainsKey(anchor_pair.Key)) anchor_entities.Add(anchor_pair.Key, anchor);
if (arenaData.FFA_Spawns.Count == 0) arenaData.FFA_Spawns = ConvertLocalsToWorld(anchor, anchor_pair.Value.FFA_Spawns);
if (arenaData.TDM_Spawns_1.Count == 0) arenaData.TDM_Spawns_1 = ConvertLocalsToWorld(anchor, anchor_pair.Value.TDM_Spawns_1);
if (arenaData.TDM_Spawns_2.Count == 0) arenaData.TDM_Spawns_2 = ConvertLocalsToWorld(anchor, anchor_pair.Value.TDM_Spawns_2);
continue;
}
else {
Puts(string.Format(lang.GetMessage("SearchingAnchors", this), anchor_pair.Key));
arenaData.FFA_Spawns.Clear();
arenaData.TDM_Spawns_1.Clear();
arenaData.TDM_Spawns_2.Clear();
arenaData.Anchor_id = 0;
}
}
List<BaseNetworkable> anchor_main = new List<BaseNetworkable>();
List<BaseNetworkable> anchor_second = new List<BaseNetworkable>();
foreach (var entity in BaseNetworkable.serverEntities) {
if (entity.PrefabName == anchor_pair.Value.Anchor_Entity_Main) anchor_main.Add(entity);
else if (entity.PrefabName == anchor_pair.Value.Anchor_Entity_Secondary) anchor_second.Add(entity);
}
if (anchor_main.Count == 0 || anchor_second.Count == 0) {
Puts(string.Format(lang.GetMessage("AnchorFailed", this), anchor_pair.Key));
continue;
}
var found_anchor = false;
foreach (var main in anchor_main) {
foreach (var second in anchor_second) {
if (Vector3.Distance(second.transform.position, main.transform.position) < 1) {
// Saves the anchor information to a dictionary for use in running the games.anchor_entities.Add(anchor_pair.Key, main);
// Saves the info from the config to the data file with the ID of the anchor entity.ArenaInfo ai;
if (!pcdData.arenas.TryGetValue(anchor_pair.Key, out ai)) pcdData.arenas.Add(anchor_pair.Key, ai = new ArenaInfo());
ai.Anchor_id = main.net.ID.Value;
ai.FFA_Spawns = ConvertLocalsToWorld(main, anchor_pair.Value.FFA_Spawns);
ai.TDM_Spawns_1 = ConvertLocalsToWorld(main, anchor_pair.Value.TDM_Spawns_1);
ai.TDM_Spawns_2 = ConvertLocalsToWorld(main, anchor_pair.Value.TDM_Spawns_2);
ai.Anchor_Entity_Main = main.PrefabName;
ai.Anchor_Entity_Secondary = second.PrefabName;
found_anchor = true;
Puts(string.Format(lang.GetMessage("NewAnchorFound", this), anchor_pair.Key));
break;
}
}
if (found_anchor) break;
}
}
if (anchor_entities.Count > 0) {
if (pcdData.lobby_point == Vector3.zero && ancho
// ... [Code preview truncated due to length. Full source in attached file] ...