C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Facepunch;
using Network;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using Rust;
using Rust.Workshop;
using UnityEngine;
using Random = UnityEngine.Random;
namespace Oxide.Plugins {
[Info("Duelist", "nivex", "1.1.5", ResourceId = 2520), Description("1v1 & TDM dueling event.")]
public class Duelist : RustPlugin {
public enum Team {
Good = 0, Evil = 1, None = 2
}
private static readonly string hewwPrefab = "assets/prefabs/building/wall.external.high.wood/wall.external.high.wood.prefab";
private static readonly string heswPrefab = "assets/prefabs/building/wall.external.high.stone/wall.external.high.stone.prefab";
private static Duelist ins;
private static bool init;
// are we initialized properly? if not disable certain functionality
private readonly bool debugMode = false;
private static readonly List<string> readyUiList = new List<string>();
private static readonly List<string> spectators = new List<string>();
private static readonly List<Rematch> rematches = new List<Rematch>();
private static readonly Dictionary<string, AttackerInfo> tdmAttackers = new Dictionary<string, AttackerInfo>();
private static readonly Dictionary<string, string> tdmKits = new Dictionary<string, string>();
private static readonly HashSet<GoodVersusEvilMatch> tdmMatches = new HashSet<GoodVersusEvilMatch>();
private static readonly List<DuelingZone> duelingZones = new List<DuelingZone>();
// where all the fun is at
private static StoredData duelsData = new StoredData();
private static readonly Dictionary<string, string> dataDuelists = new Dictionary<string, string>();
// active duelers
private static readonly Dictionary<string, long> dataImmunity = new Dictionary<string, long>();
// players immune to damage
private static readonly Dictionary<string, Vector3> dataImmunitySpawns = new Dictionary<string, Vector3>();
// players spawn points
private readonly int blockedMask = LayerMask.GetMask("Player (Server)", "Prevent Building", "Construction", "Deployed", "Trigger");
// layers we won't be setting a zone within 50 meters of
private readonly int constructionMask = LayerMask.GetMask("Construction", "Deployed");
private static bool matchUpdateRequired;
private readonly int groundMask = LayerMask.GetMask("Terrain", "World", "Default");
// used to find dueling zone/set custom zone and create spawn points
private readonly int wallMask = LayerMask.GetMask("Terrain", "World", "Default", "Construction", "Deployed");
private readonly int waterMask = LayerMask.GetMask("Water");
// used to count water colliders when finding a random dueling zone on the map
private readonly int worldMask = LayerMask.GetMask("World");
private Timer announceTimer;
private readonly SortedDictionary<string, string> boneTags = new SortedDictionary<string, string> {
["r_"]
= "Right ", ["l_"] = "Left ", [".prefab"] = "", ["1"] = "", ["2"] = "", ["3"] = "", ["4"] = "", ["END"] = "", ["_"] = " ", ["."] = " "
}
;
private static readonly Dictionary<string, string> announcements = new Dictionary<string, string>();
// users id and announcement
private readonly Dictionary<string, long> dataDeath = new Dictionary<string, long>();
// users id and timestamp of when they're to be executed
private readonly Dictionary<string, string> dataRequests = new Dictionary<string, string>();
// users requesting a duel and to whom
private readonly Dictionary<string, bool> deployables = new Dictionary<string, bool>();
private readonly Dictionary<ulong, List<BaseEntity>> duelEntities = new Dictionary<ulong, List<BaseEntity>>();
private DynamicConfigFile duelsFile;
private Timer eventTimer;
// timer to check for immunity and auto death time of duelers
private Timer matchTimer;
// timer to check for updates to the match ui
private readonly SpawnFilter filter = new SpawnFilter();
// RandomDropPosition()
[PluginReference]
private Plugin Kits, ZoneManager, Economics, ServerRewards, LustyMap, Backpacks, PermaMap;
private readonly Dictionary<Vector3, float> managedZones = new Dictionary<Vector3, float>();
// blocked zones from zonemanager plugin
private List<Vector3> monuments = new List<Vector3>();
// positions of monuments on the server
private readonly Dictionary<string, string> prefabs = new Dictionary<string, string>();
private bool resetDuelists;
// if wipe is detected then assign awards and wipe VictoriesSeed / LossesSeed
private readonly Dictionary<string, List<ulong>> skinsCache = new Dictionary<string, List<ulong>>();
// used to randomize custom kit skins which skin id values are 0
private readonly Dictionary<string, string> tdmRequests = new Dictionary<string, string>();
// users requesting a deathmatch and to whom
private readonly Dictionary<string, List<ulong>> workshopskinsCache = new Dictionary<string, List<ulong>>();
private readonly List<string> dcsBlock = new List<string>();
// users blocked from 1v1 for 60 seconds after suiciding or disconnecting
private readonly Dictionary<string, string> playerZones = new Dictionary<string, string>();
// id, set zone name
public class StoredData {
public List<string> Allowed = new List<string>();
// list of users that allow duel requests
public Dictionary<string, List<string>> AutoGeneratedSpawns = new Dictionary<string, List<string>>();
public Dictionary<string, string> Bans = new Dictionary<string, string>();
// users banned from dueling
public Dictionary<string, BetInfo> Bets = new Dictionary<string, BetInfo>();
// active bets users have placed
public Dictionary<string, List<string>> BlockedUsers = new Dictionary<string, List<string>>();
// users and the list of players they blocked from requesting duels with
public List<string> Chat = new List<string>();
// user ids of those who opted out of seeing duel death messages
public List<string> ChatEx = new List<string>();
// user ids of those who opted to see duel death messages when the config blocks them for all players
public Dictionary<string, List<BetInfo>> ClaimBets = new Dictionary<string, List<BetInfo>>();
// active bets users need to claim after winning a bet
public Dictionary<string, string> CustomKits = new Dictionary<string, string>();
// userid and custom kit
public bool DuelsEnabled;
// enable/disable dueling for all players (not admins)
public Dictionary<string, string> Homes = new Dictionary<string, string>();
// user id and location of where they teleported from
public Dictionary<string, string> Kits = new Dictionary<string, string>();
// userid and kit.give kit when they wake up inside of the dueling zone
public Dictionary<string, int> Losses = new Dictionary<string, int>();
// user id / losses for lifetime
public Dictionary<string, int> LossesSeed = new Dictionary<string, int>();
// user id / losses for seed
public SortedDictionary<long, string> Queued = new SortedDictionary<long, string>();
// queued duelers sorted by timestamp and user id.first come first serve
public List<string> Restricted = new List<string>();
// list of users blocked from requesting a duel for 60 seconds
public List<string> Spawns = new List<string>();
// custom spawn points
public List<string> AutoReady = new List<string>();
public Dictionary<string, int> MatchVictories = new Dictionary<string, int>();
// player name & total wins
public Dictionary<string, int> MatchVictoriesSeed = new Dictionary<string, int>();
// player name & wins for current seed
public Dictionary<string, int> MatchLosses = new Dictionary<string, int>();
// player name & total losses
public Dictionary<string, int> MatchLossesSeed = new Dictionary<string, int>();
// player name & losses for current seed
public Dictionary<string, int> MatchDeaths = new Dictionary<string, int>();
// player name & total deaths
public Dictionary<string, int> MatchDeathsSeed = new Dictionary<string, int>();
// player name & deaths for the seed
public Dictionary<string, int> MatchKills = new Dictionary<string, int>();
// player name & total kills
public Dictionary<string, int> MatchKillsSeed = new Dictionary<string, int>();
// player name & kills for current seed
public Dictionary<string, Dictionary<string, int>> MatchSizesVictories = new Dictionary<string, Dictionary<string, int>>();
// size, id, wins
public Dictionary<string, Dictionary<string, int>> MatchSizesVictoriesSeed = new Dictionary<string, Dictionary<string, int>>();
// size, id, wins seed
public Dictionary<string, Dictionary<string, int>> MatchSizesLosses = new Dictionary<string, Dictionary<string, int>>();
// size, id, losses
public Dictionary<string, Dictionary<string, int>> MatchSizesLossesSeed = new Dictionary<string, Dictionary<string, int>>();
// size, id, losses seed
public int TotalDuels;
// the total amount of duels ever played on the server
public Dictionary<string, int> Victories = new Dictionary<string, int>();
// user id / wins for lifetime
public Dictionary<string, int> VictoriesSeed = new Dictionary<string, int>();
// user id / wins for seed
public List<string> ZoneIds = new List<string>();
// the locations of each dueling zone
public Dictionary<string, string> DuelZones = new Dictionary<string, string>();
// location, name
}
public class PlayerComparer : IEqualityComparer<BasePlayer> {
public bool Equals(BasePlayer player, BasePlayer target) {
return player != null && target != null && player.UserIDString == target.UserIDString;
}
public int GetHashCode(BasePlayer player) {
return player?.UserIDString.GetHashCode() ?? 0;
}
}
public class Rematch {
public List<BasePlayer> Duelists {
get;
}
= new List<BasePlayer>();
public List<BasePlayer> Ready {
get;
}
= new List<BasePlayer>();
private List<BasePlayer> Evil {
get;
}
= new List<BasePlayer>();
private List<BasePlayer> Good {
get;
}
= new List<BasePlayer>();
public GoodVersusEvilMatch match {
get;
set;
}
private Timer _notify {
get;
set;
}
public List<BasePlayer> Players {
get {
Duelists.RemoveAll(player => !player || !player.IsConnected);
Good.RemoveAll(player => !player || !player.IsConnected);
Evil.RemoveAll(player => !player || !player.IsConnected);
Ready.RemoveAll(player => !player || !player.IsConnected);
return match == null ? Duelists : Good.Union(Evil, new PlayerComparer()).ToList();
}
}
public bool HasPlayer(BasePlayer player) {
return Players.Contains(player);
}
public bool AddRange(List<BasePlayer> players, Team team) {
foreach (var player in players) {
if (!player || !player.IsConnected || InEvent(player) || Good.Contains(player) || Evil.Contains(player)) break;
if (team == Team.Evil) Evil.Add(player);
else Good.Add(player);
}
return (team == Team.Evil ? Evil.Count : Good.Count) == players.Count;
}
public bool IsReady(BasePlayer player) {
if (InEvent(player) || !ins.IsNewman(player) || duelsData.Bans.ContainsKey(player.UserIDString)) return false;
return true;
}
public bool IsReady() {
if (Players.Any(player => !IsReady(player))) {
Reset("RematchFailed2");
return false;
}
return Ready.Count == (match == null ? 2 : match.TeamSize * 2);
}
private void Reset(string key) {
if (match != null) tdmMatches.Remove(match);
MessageAll(key);
Duelists.Clear();
Good.Clear();
Evil.Clear();
Ready.Clear();
_notify?.Destroy();
rematches.Remove(this);
matchUpdateRequired = true;
}
public void MessageAll(string key, params object[] args) {
foreach (var player in Players) {
player.ChatMessage(ins.msg(key, player.UserIDString, args != null ? args : new string[0]));
}
}
public void Notify() {
MessageAll("RematchNotify", 60f, match == null ? szDuelChatCommand : szMatchChatCommand);
foreach (var player in Players) if (duelsData.AutoReady.Contains(player.UserIDString)) Ready.Add(player);
if (IsReady()) {
Start();
rematches.Remove(this);
}
else if (match == null || !match.IsPublic) _notify = ins.timer.Once(60f, () => Cancel());
}
private void Cancel() {
if (match != null && match.IsPublic) return;
if (rematches.Contains(this)) {
if (sendHomeSpectatorWhenRematchTimesOut) {
foreach (var player in Players) {
if (IsSpectator(player)) {
EndSpectate(player);
ins.SendHome(player);
}
}
}
if (match != null) {
match.Reuse();
tdmMatches.Remove(match);
}
Reset("RematchTimedOut");
}
}
public void Start() {
if (match == null) {
var player = Ready[0];
var target = Ready[1];
if (!ins.SelectZone(player, target)) {
player.ChatMessage(ins.msg("AllZonesFull", player.UserIDString, duelingZones.Count, playersPerZone));
target.ChatMessage(ins.msg("AllZonesFull", target.UserIDString, duelingZones.Count, playersPerZone));
}
}
else {
match.Reuse();
tdmMatches.Add(match);
if (!AddMatchPlayers(Good, Team.Good) || !AddMatchPlayers(Evil, Team.Evil)) {
Reset("RematchFailed");
match.Reuse();
}
}
_notify?.Destroy();
rematches.Remove(this);
}
private bool AddMatchPlayers(List<BasePlayer> players, Team team) {
foreach (var player in players) if (!match.AddMatchPlayer(player, team)) return false;
return true;
}
}
public class AttackerInfo {
public string AttackerName {
get;
set;
}
= "";
public string AttackerId {
get;
set;
}
= "";
public string BoneName {
get;
set;
}
= "";
public string Distance {
get;
set;
}
= "";
public string Weapon {
get;
set;
}
= "";
}
public class GoodVersusEvilMatch {
private readonly HashSet<ulong> _banned = new HashSet<ulong>();
private readonly HashSet<BasePlayer> _evil = new HashSet<BasePlayer>();
private readonly HashSet<ulong> _evilKIA = new HashSet<ulong>();
private readonly List<BasePlayer> _evilRematch = new List<BasePlayer>();
private readonly HashSet<BasePlayer> _good = new HashSet<BasePlayer>();
private readonly HashSet<ulong> _goodKIA = new HashSet<ulong>();
private readonly List<BasePlayer> _goodRematch = new List<BasePlayer>();
private string _goodHostName {
get;
set;
}
= "";
private string _evilHostName {
get;
set;
}
= "";
private string _goodHostId {
get;
set;
}
= "";
private string _evilHostId {
get;
set;
}
= "";
private string _goodCode {
get;
set;
}
= "";
private string _evilCode {
get;
set;
}
= "";
private int _teamSize {
get;
set;
}
= 2;
private bool _started {
get;
set;
}
private bool _ended {
get;
set;
}
private string _kit {
get;
set;
}
= "";
private DuelingZone _zone {
get;
set;
}
private Timer _queueTimer {
get;
set;
}
private bool _enteredQueue {
get;
set;
}
private bool _public {
get;
set;
}
private bool _canRematch {
get;
set;
}
= true;
public bool CanRematch {
get {
return _canRematch;
}
set {
_canRematch = value;
}
}
public string Id {
get {
return _goodHostId + _evilHostId;
}
}
public string Versus {
get {
return string.Format("{0} / {1} {2}v{2}", _goodHostName, _evilHostName, _teamSize);
}
}
public bool IsPublic {
get {
return _public;
}
set {
_public = value;
matchUpdateRequired = true;
MessageAll(_public ? "MatchPublic" : "MatchPrivate");
}
}
public int TeamSize {
get {
return _teamSize;
}
set {
if (IsStarted) return;
_teamSize = value;
matchUpdateRequired = true;
MessageAll("MatchSizeChanged", _teamSize);
}
}
public DuelingZone Zone {
get {
return _zone;
}
}
public bool EitherEmpty {
get {
return _good.Count == 0 || _evil.Count == 0;
}
}
public bool IsStarted {
get {
return _started;
}
set {
_started = value;
matchUpdateRequired = true;
}
}
public bool IsOver {
get {
return _ended;
}
set {
_ended = value;
matchUpdateRequired = true;
}
}
public string Kit {
get {
return _kit;
}
set {
_kit = value;
if (!EitherEmpty) {
_good.RemoveWhere(target => !target || !target.IsConnected);
_evil.RemoveWhere(target => !target || !target.IsConnected);
foreach (var player in _good.Union(_evil, new PlayerComparer())) duelsData.Kits[player.UserIDString] = _kit;
MessageAll("MatchKitSet", _kit);
}
}
}
public void Reuse() {
if (_zone != null) _zone.IsLocked = false;
_evilRematch.Clear();
_goodRematch.Clear();
_good.Clear();
_evil.Clear();
_goodKIA.Clear();
_evilKIA.Clear();
_started = false;
_ended = false;
_enteredQueue = false;
_kit = ins.GetRandomKit();
_goodHostId = BasePlayer.activePlayerList.Find(x => x.displayName == _goodHostName)?.UserIDString ?? _goodHostId;
_evilHostId = BasePlayer.activePlayerList.Find(x => x.displayName == _evilHostName)?.UserIDString ?? _evilHostId;
matchUpdateRequired = true;
}
public void Setup(BasePlayer player, BasePlayer target) {
tdmMatches.Add(this);
_goodHostName = player.displayName;
_goodHostId = player.UserIDString;
_evilHostName = target.displayName;
_evilHostId = target.UserIDString;
_goodCode = Random.Range(10000, 99999).ToString();
_evilCode = Random.Range(10000, 99999).ToString();
if (_teamSize < minDeathmatchSize) _teamSize = minDeathmatchSize;
AddMatchPlayer(player, Team.Good);
AddMatchPlayer(target, Team.Evil);
if (tdmKits.ContainsKey(player.UserIDString)) {
Kit = tdmKits[player.UserIDString];
tdmKits.Remove(player.UserIDString);
}
else if (tdmKits.ContainsKey(target.UserIDString)) {
Kit = tdmKits[target.UserIDString];
tdmKits.Remove(target.UserIDString);
}
else Kit = ins.GetRandomKit();
if (TeamSize > 1) {
player.ChatMessage(ins.msg("MatchOpened", player.UserIDString, szMatchChatCommand, _goodCode));
target.ChatMessage(ins.msg("MatchOpened", target.UserIDString, szMatchChatCommand, _evilCode));
}
matchUpdateRequired = true;
}
public bool IsFull() {
return _good.Count == _teamSize && _evil.Count == _teamSize;
}
public bool IsFull(Team team) {
return team == Team.Good ? _good.Count == _teamSize : _evil.Count == _teamSize;
}
public void MessageAll(string key, params object[] args) {
Message(Team.Good, key, args);
Message(Team.Evil, key, args);
}
public void Message(Team team, string key, params object[] args) {
foreach (var player in team == Team.Evil ? _evil : _good) {
if (!player || !player.IsConnected) continue;
player.ChatMessage(ins.msg(key, player.UserIDString, args != null ? args : new string[0]));
}
}
public Team GetTeam(BasePlayer player) {
return _good.Contains(player) ? Team.Good : _evil.Contains(player) ? Team.Evil : Team.None;
}
public bool IsHost(BasePlayer player) {
return player.UserIDString == _goodHostId || player.UserIDString == _evilHostId;
}
public void SetCode(BasePlayer player, string code) {
if (GetTeam(player) == Team.Evil) _evilCode = code;
else if (GetTeam(player) == Team.Good) _goodCode = code;
}
public string Code(Team team) {
return team == Team.Good ? _goodCode : _evilCode;
}
public bool AlliedTo(BasePlayer player, Team team) {
return ins.IsAllied(player.UserIDString, team == Team.Good ? _goodHostId : _evilHostId);
}
public bool IsBanned(ulong targetId) {
return _banned.Contains(targetId);
}
public bool Ban(BasePlayer target) {
if (target.UserIDString == _goodHostId || target.UserIDString == _evilHostId || IsBanned(target.userID)) return false;
_banned.Add(target.userID);
RemoveMatchPlayer(target);
return true;
}
public bool Equals(GoodVersusEvilMatch match) {
return match._good.Equals(_good) && match._evil.Equals(_evil);
}
public string GetNames(Team team) {
return string.Join(", ", team == Team.Good ? _good.Select(player => player.displayName).ToArray() : _evil.Select(player => player.displayName).ToArray());
}
public void GiveShirt(BasePlayer player) {
Item item = ItemManager.CreateByName(teamShirt, 1, GetTeam(player) == Team.Evil ? teamEvilShirt : teamGoodShirt);
if (item == null) return;
if (item.info.category != ItemCategory.Attire) {
item.Remove(0.01f);
return;
}
foreach (Item wear in player.inventory.containerWear.itemList) {
if (wear.info.shortname.Contains("shirt")) {
wear.RemoveFromContainer();
wear.Remove(0.01f);
break;
}
}
item.MoveToContainer(player.inventory.containerWear, -1, false);
if (!player.inventory.containerWear.HasFlag(ItemContainer.Flag.IsLocked)) player.inventory.containerWear.SetFlag(ItemContainer.Flag.IsLocked, true);
}
public bool AddMatchPlayer(BasePlayer player, Team team) {
if (_started) {
player.ChatMessage(ins.msg("MatchStartedAlready", player.UserIDString));
return false;
}
_good.RemoveWhere(target => !target || !target.IsConnected);
_evil.RemoveWhere(target => !target || !target.IsConnected);
if (_banned.Contains(player.userID)) return false;
if (!ins.IsNewman(player)) {
player.ChatMessage(ins.msg("MustBeNaked", player.UserIDString));
return false;
}
switch (team) {
case Team.Good : if (_good.Count == _teamSize) {
player.ChatMessage(ins.msg("MatchTeamFull", player.UserIDString, _teamSize));
return false;
}
_good.Add(player);
MessageAll("MatchJoinedTeam", player.displayName, _goodHostName, _good.Count, _teamSize, _evilHostName, _evil.Count);
break;
case Team.Evil : if (_evil.Count == _teamSize) {
player.ChatMessage(ins.msg("MatchTeamFull", player.UserIDString, _teamSize));
return false;
}
_evil.Add(player);
MessageAll("MatchJoinedTeam", player.displayName, _evilHostName, _evil.Count, _teamSize, _goodHostName, _good.Count);
break;
}
if (_good.Count == _teamSize && _evil.Count == _teamSize) Queue();
return true;
}
public bool RemoveMatchPlayer(BasePlayer player) {
if (player == null) return false;
if (player.inventory.containerWear.HasFlag(ItemContainer.Flag.IsLocked)) player.inventory.containerWear.SetFlag(ItemContainer.Flag.IsLocked, false);
ins.Metabolize(player, false);
ins.RemoveEntities(player.userID);
Interface.Oxide.CallHook("DisableBypass", player.userID);
if (DuelTerritory(player.transform.position)) {
if (sendDefeatedHome) ins.SendHome(player);
else StartSpectate(player);
}
if (IsOver) {
_good.Remove(player);
_evil.Remove(player);
return true;
}
if (_good.Remove(player)) {
if (_good.Count == 0) {
if (_started) {
_goodKIA.Add(player.userID);
_goodRematch.Add(player);
}
else MessageAll("MatchNoPlayersLeft");
EndMatch(Team.Evil);
return true;
}
if (_started) {
_goodKIA.Add(player.userID);
_goodRematch.Add(player);
}
if (player.UserIDString == _goodHostId) AssignGoodHostId();
return true;
}
if (_evil.Remove(player)) {
if (_evil.Count == 0) {
if (_started) {
_evilKIA.Add(player.userID);
_evilRematch.Add(player);
}
else MessageAll("MatchNoPlayersLeft");
EndMatch(Team.Good);
return true;
}
if (_started) {
_evilKIA.Add(player.userID);
_evilRematch.Add(player);
}
if (player.UserIDString == _evilHostId) AssignEvilHostId();
return true;
}
return false;
}
private void AssignGoodHostId() {
_good.RemoveWhere(player => !player || !player.IsConnected);
if (_good.Count > 0) {
_goodHostId = _good.First().UserIDString;
matchUpdateRequired = true;
}
else EndMatch(Team.Evil);
}
private void AssignEvilHostId() {
_evil.RemoveWhere(player => !player || !player.IsConnected);
if (_evil.Count > 0) {
_evilHostId = _evil.First().UserIDString;
matchUpdateRequired = true;
}
else EndMatch(Team.Good);
}
private void Finalize(Team team) {
Interface.CallHook("OnDuelistFinalized", team == Team.Good ? _goodKIA : _evilKIA);
switch (team) {
case Team.Evil : {
foreach (ulong playerId in _goodKIA) {
UpdateMatchStats(playerId.ToString(), false, true, false, false);
UpdateMatchSizeStats(playerId.ToString(), true, false, _teamSize);
}
foreach (ulong playerId in _evilKIA) {
AwardPlayer(playerId, teamEconomicsMoney, teamServerRewardsPoints);
UpdateMatchStats(playerId.ToString(), true, false, false, false);
UpdateMatchSizeStats(playerId.ToString(), false, true, _teamSize);
}
break;
}
case Team.Good : {
foreach (ulong playerId in _evilKIA) {
UpdateMatchStats(playerId.ToString(), false, true, false, false);
UpdateMatchSizeStats(playerId.ToString(), true, false, _teamSize);
}
foreach (ulong playerId in _goodKIA) {
AwardPlayer(playerId, teamEconomicsMoney, teamServerRewardsPoints);
UpdateMatchStats(playerId.ToString(), true, false, false, false);
UpdateMatchSizeStats(playerId.ToString(), false, true, _teamSize);
}
break;
}
}
_goodKIA.Clear();
_evilKIA.Clear();
}
private bool SetupRematch() {
if (!CanRematch || _goodRematch.Any(player => !player || !player.IsConnected) || _evilRematch.Any(player => !player || !player.IsConnected)) return false;
var rematch = new Rematch();
if (rematch.AddRange(_evilRematch, Team.Evil)) {
if (rematch.AddRange(_goodRematch, Team.Good)) {
rematch.match = this;
rematches.Add(rematch);
rematch.Notify();
return true;
}
}
return false;
}
private void EndMatch(Team team) {
if (!_ended && _started) {
Finalize(team);
ins.Puts(ins.msg("MatchDefeat", null, team == Team.Evil ? _evilHostName : _goodHostName, team == Team.Evil ? _goodHostName : _evilHostName, _teamSize));
IsOver = true;
IsStarted = false;
foreach (var player in _evil.ToList()) {
RemoveMatchPlayer(player);
if (player != null && player.IsConnected && !_evilRematch.Contains(player)) _evilRematch.Add(player);
}
foreach (var player in _good.ToList()) {
RemoveMatchPlayer(player);
if (player != null && player.IsConnected && !_goodRematch.Contains(player)) _goodRematch.Add(player);
}
foreach (var target in BasePlayer.activePlayerList.Where(p => p?.displayName != null)) {
if (guiAnnounceUITime > 0f && (_goodKIA.Contains(target.userID) || _evilKIA.Contains(target.userID))) CreateAnnouncementUI(target, ins.msg("MatchDefeat", target.UserIDString, team == Team.Evil ? _evilHostName : _goodHostName, team == Team.Evil ? _goodHostName : _evilHostName, _teamSize));
if (duelsData.Chat.Contains(target.UserIDString) && !_goodKIA.Contains(target.userID) && !_evilKIA.Contains(target.userID)) continue;
target.ChatMessage(ins.msg("MatchDefeat", target.UserIDString, team == Team.Evil ? _evilHostName : _goodHostName, team == Team.Evil ? _goodHostName : _evilHostName, _teamSize));
}
if (!SetupRematch()) {
foreach (var player in _evilRematch.Union(_goodRematch, new PlayerComparer())) {
if (!player || !player.IsConnected) continue;
player.ChatMessage(ins.msg("RematchFailed2", player.UserIDString));
}
Reuse();
}
}
End();
}
public void End() {
if (_zone != null) _zone.IsLocked = false;
_queueTimer?.Destroy();
_good.RemoveWhere(player => !player);
_evil.RemoveWhere(player => !player);
foreach (var player in _good.Union(_evil, new PlayerComparer())) {
if (player.inventory.containerWear.HasFlag(ItemContainer.Flag.IsLocked)) player.inventory.containerWear.SetFlag(ItemContainer.Flag.IsLocked, false);
if (IsStarted || IsOver) {
if (DuelTerritory(player.transform.position)) {
player.inventory.Strip();
ins.SendHome(player);
}
ins.Metabolize(player, false);
}
}
_good.Clear();
_evil.Clear();
tdmMatches.Remove(this);
matchUpdateRequired = true;
if (dataDuelists.Count == 0 && tdmMatches.Count == 0) ins.Unsubscribe(nameof(OnPlayerHealthChange));
}
private void Queue() {
DuelingZone zone = null;
foreach (var player in _good.Union(_evil, new PlayerComparer())) {
if (!ins.IsNewman(player)) {
player.ChatMessage(ins.msg("MustBeNaked", player.UserIDString));
MessageAll("MatchIsNotNaked", player.displayName);
_queueTimer = ins.timer.Once(30f, () => Queue());
return;
}
if (zone == null) zone = ins.GetPlayerZone(player, TeamSize);
}
var zones = duelingZones.Where(x => x.TotalPlayers == 0 && !x.IsLocked && x.Spawns.Count >= (requireTeamSize ? TeamSize * 2 : 2)).ToList();
if (zones == null || zones.Count == 0) {
if (!_enteredQueue) {
MessageAll("MatchQueued");
_enteredQueue = true;
}
_queueTimer = ins.timer.Once(2f, () => Queue());
return;
}
_zone = zone ?? LastZone ?? zones.GetRandom();
_queueTimer?.Destroy();
Start();
}
public DuelingZone LastZone {
get {
DuelingZone zone = null;
if (_good.Any(player => DuelTerritory(player.transform.position))) zone = GetDuelZone(_good.First(player => DuelTerritory(player.transform.position)).transform.position);
if (_evil.Any(player => DuelTerritory(player.transform.position))) zone = GetDuelZone(_evil.First(player => DuelTerritory(player.transform.position)).transform.position);
return zone == null || zone.TotalPlayers > 0 || zone.IsLocked ? null : zone;
}
}
private void Start() {
ins.SubscribeHooks(true);
var goodSpawn = _zone.Spawns.GetRandom();
var evilSpawn = goodSpawn;
float dist = -100f;
foreach (var spawn in _zone.Spawns) // get the furthest spawn point away from the good team and assign it to the evil team
{
float distance = Vector3.Distance(spawn, goodSpawn);
if (distance > dist) {
dist = distance;
evilSpawn = spawn;
}
}
Message(Team.Good, "MatchStarted", GetNames(Team.Evil));
Message(Team.Evil, "MatchStarted", GetNames(Team.Good));
_zone.IsLocked = true;
IsStarted = true;
Spawn(_good, goodSpawn);
Spawn(_evil, evilSpawn);
}
private void Spawn(HashSet<BasePlayer> players, Vector3 spawn) {
foreach (var player in players) {
duelsData.Kits[player.UserIDString] = _kit;
if (!DuelTerritory(player.transform.position) || !duelsData.Homes.ContainsKey(player.UserIDString)) {
var ppos = player.transform.position;
if (IsOnConstruction(ppos)) ppos.y += 1;
// prevent player from becoming stuck or dying when teleported home
duelsData.Homes[player.UserIDString] = ppos.ToString();
}
RemoveFromQueue(player.UserIDString);
ins.Teleport(player, spawn);
if (immunityTime >= 1) {
dataImmunity[player.UserIDString] = TimeStamp() + immunityTime;
dataImmunitySpawns[player.UserIDString] = spawn;
}
}
}
}
public class DuelKitItem {
public string ammo;
public int amount;
public string container;
public List<string> mods;
public string shortname;
public ulong skin;
public int slot;
}
public class BetInfo {
public string trigger;
// the trigger used to request this as a bet
public int amount;
// amount the player bet
public int itemid;
// the unique identifier of the item
public int max;
// the maximum amount allowed to bet on this item
public bool Equals(BetInfo bet) {
return bet.amount == amount && bet.itemid == itemid;
}
}
public class DuelingZone // Thanks @Jake_Rich for helping me get this started!
{
private HashSet<BasePlayer> _players = new HashSet<BasePlayer>();
private HashSet<BasePlayer> _waiting = new HashSet<BasePlayer>();
private Vector3 _zonePos;
private List<Vector3> _duelSpawns = new List<Vector3>();
// spawn points generated on the fly
private int _kills;
// create a new zone randomly on the map when the counter hits the configured amount once all duels are finished
private bool _locked;
public bool IsLocked {
get {
return _locked;
}
set {
_locked = value;
}
}
public int Kills {
get {
return _kills;
}
set {
_kills = value;
}
}
public int TotalPlayers {
get {
return _players.Count;
}
}
public List<BasePlayer> Players {
get {
return _players.ToList();
}
}
public List<Vector3> Spawns {
get {
var spawns = GetSpawnPoints(this);
// get custom spawn points if any exist
return spawns == null || spawns.Count < 2 ? _duelSpawns : spawns;
}
}
public bool IsFull {
get {
return TotalPlayers + _waiting.Count + 2 > playersPerZone || _locked;
}
}
public Vector3 Position {
get {
return _zonePos;
}
}
public void Setup(Vector3 position) {
_zonePos = position;
_duelSpawns = GetAutoSpawns(this);
}
public float Distance(Vector3 position) {
position.y = 0f;
return Vector3.Distance(new Vector3(_zonePos.x, 0f, _zonePos.z), position);
}
public bool? AddWaiting(BasePlayer player, BasePlayer target) {
if (IsFull) return false;
if (requiredDuelMoney > 0.0 && ins.Economics != null) {
double playerMoney = Convert.ToDouble(ins.Economics.Call("GetPlayerMoney", player.userID));
double targetMoney = Convert.ToDouble(ins.Economics.Call("GetPlayerMoney", target.userID));
if (playerMoney < requiredDuelMoney || targetMoney < requiredDuelMoney) {
RemoveFromQueue(player.UserIDString);
RemoveFromQueue(target.UserIDString);
player.ChatMessage(ins.msg("MoneyRequired", player.UserIDString, requiredDuelMoney));
target.ChatMessage(ins.msg("MoneyRequired", target.UserIDString, requiredDuelMoney));
return null;
}
bool playerWithdrawn = Convert.ToBoolean(ins.Economics.Call("Withdraw", player.userID, requiredDuelMoney));
bool targetWithdrawn = Convert.ToBoolean(ins.Economics.Call("Withdraw", target.userID, requiredDuelMoney));
if (!playerWithdrawn || !targetWithdrawn) {
RemoveFromQueue(player.UserIDString);
RemoveFromQueue(target.UserIDString);
return null;
}
}
_waiting.Add(player);
_waiting.Add(target);
return true;
}
public bool IsWaiting(BasePlayer player) {
return _waiting.Contains(player);
}
public void AddPlayer(BasePlayer player) {
_waiting.Remove(player);
_players.Add(player);
}
public void RemovePlayer(string playerId) {
_players.RemoveWhere(player => !player);
foreach (var player in _players.ToList()) {
if (player.UserIDString == playerId) {
_players.Remove(player);
_waiting.Remove(player);
break;
}
}
}
// ... [Code preview truncated due to length. Full source in attached file] ...