C#:
using Oxide.Core;
using Oxide.Core.Plugins;
using Rust;
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("FBuildZoneStages", "fufeldev", "1.0.0")]
public class FBuildZoneStages : RustPlugin
{
private const string SpherePrefab = "assets/bundled/prefabs/modding/events/twitch/br_sphere_green.prefab";
private const string GenericMapMarkerPrefab = "assets/prefabs/tools/map/genericradiusmarker.prefab";
private const string VendingMapMarkerPrefab = "assets/prefabs/deployable/vendingmachine/vending_mapmarker.prefab";
private const float ZoneRadius = 30f;
private const float SphereStartRadius = 1f;
private const float SphereExpandSeconds = 8f;
private const float ZoneCheckRate = 0.5f;
private const float DenyAttemptCooldown = 2f;
private const int BuildSeconds = 2700;
private const int PvpSeconds = 1200;
private const int RaidSeconds = 5400;
private const int WarmupSeconds = 300;
private const float WarmupSpawnRadius = 10f;
private const int MaxDenyAttempts = 10;
[PluginReference] private Plugin Stadia, SClan, FClan, Clans, Friends;
private readonly Dictionary<string, BuildZone> _zones = new Dictionary<string, BuildZone>();
private readonly Dictionary<NetworkableId, string> _cupboardZones = new Dictionary<NetworkableId, string>();
private readonly Dictionary<string, NetworkableId> _teamCupboards = new Dictionary<string, NetworkableId>();
private readonly Dictionary<ulong, Vector3> _lastAllowedPositions = new Dictionary<ulong, Vector3>();
private readonly Dictionary<ulong, int> _denyAttempts = new Dictionary<ulong, int>();
private readonly Dictionary<ulong, float> _lastDenyAttemptTime = new Dictionary<ulong, float>();
private Timer _stageTimer;
private Timer _zoneTimer;
private Timer _saveTimer;
private Stage _stage = Stage.Raid;
private bool _syncingStadia;
private StoredData _data = new StoredData();
private enum Stage
{
Warmup,
Build,
Pvp,
Raid
}
private class StoredData
{
public string WipeId;
public Vector3 LobbySpawn;
public Vector3 RespawnSpawn;
public bool HasLobbySpawn;
public bool HasRespawnSpawn;
public string CurrentStage;
public long StageEndsAtUnix;
public long SavedAtUnix;
}
private class BuildZone
{
public string Key;
public ulong OwnerId;
public NetworkableId CupboardId;
public BuildingPrivlidge Cupboard;
public SphereEntity Sphere;
public MapMarkerGenericRadius RadiusMarker;
public VendingMachineMapMarker NameMarker;
}
private void OnServerInitialized()
{
LoadData();
RestoreStage();
StartSaveTimer();
}
private void Unload()
{
SaveStageData();
DestroyTimer(ref _stageTimer);
DestroyTimer(ref _zoneTimer);
DestroyTimer(ref _saveTimer);
ClearZones();
_teamCupboards.Clear();
}
private void BeginStage(Stage stage, bool resetTimer)
{
BeginStage(stage, resetTimer, GetStageSeconds(stage));
}
private void BeginStage(Stage stage, bool resetTimer, int seconds)
{
Stage previousStage = _stage;
_stage = stage;
if (resetTimer)
{
_data.CurrentStage = GetStageName(stage);
_data.StageEndsAtUnix = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + Math.Max(1, seconds);
}
if (resetTimer)
{
DestroyTimer(ref _stageTimer);
_stageTimer = timer.Once(Math.Max(1, seconds), NextStage);
}
if (stage == Stage.Build)
{
StartBuildZones();
ClearActiveWeaponsForAll();
}
else
{
StopBuildZones();
}
SyncStadia(stage, resetTimer ? seconds : GetStageSeconds(stage));
SaveStageData();
if (stage == Stage.Warmup) NextTick(MovePlayersToWarmupSpawn);
if (previousStage == Stage.Warmup && stage != Stage.Warmup) NextTick(MovePlayersToRespawnSpawn);
}
private void NextStage()
{
if (_stage == Stage.Warmup)
{
BeginStage(Stage.Build, true);
return;
}
if (_stage == Stage.Build)
{
BeginStage(Stage.Pvp, true);
return;
}
if (_stage == Stage.Pvp)
{
BeginStage(Stage.Raid, true);
return;
}
BeginStage(Stage.Build, true);
}
private int GetStageSeconds(Stage stage)
{
if (stage == Stage.Warmup) return WarmupSeconds;
if (stage == Stage.Build) return BuildSeconds;
if (stage == Stage.Pvp) return PvpSeconds;
return RaidSeconds;
}
private void SyncStadia(Stage stage, int seconds)
{
if (Stadia == null) return;
string phase = stage == Stage.Warmup ? "warmup" : stage == Stage.Build ? "build" : stage == Stage.Pvp ? "pvp" : "raid";
string time = (Math.Max(1, seconds) + 1).ToString();
_syncingStadia = true;
ConsoleSystem.Run(ConsoleSystem.Option.Server, "stadia.phase", phase, time);
timer.Once(0.1f, () => _syncingStadia = false);
}
private void OnStadiaPhaseChanged(string phase)
{
if (_syncingStadia || string.IsNullOrWhiteSpace(phase)) return;
phase = phase.ToLowerInvariant();
if (phase == "phase_warmup" || phase == "warmup" || phase == "phase_time" || phase == "time") BeginStage(Stage.Warmup, true);
else if (phase == "phase_build" || phase == "build") BeginStage(Stage.Build, true);
else if (phase == "phase_pvp" || phase == "pvp") BeginStage(Stage.Pvp, true);
else if (phase == "phase_raid" || phase == "raid") BeginStage(Stage.Raid, true);
}
[ConsoleCommand("fbuildstage.phase")]
private void StageConsoleCommand(ConsoleSystem.Arg arg)
{
BasePlayer player = arg.Player();
if (player != null && !player.IsAdmin) return;
if (arg.Args == null || arg.Args.Length == 0) return;
SetStageByName(arg.Args[0].ToString());
}
[ChatCommand("fbuildstage")]
private void StageChatCommand(BasePlayer player, string command, string[] args)
{
if (player == null || !player.IsAdmin || args == null || args.Length == 0) return;
SetStageByName(args[0]);
}
private void SetStageByName(string value)
{
if (string.IsNullOrWhiteSpace(value)) return;
value = value.ToLowerInvariant();
if (value == "warmup" || value == "phase_warmup" || value == "time" || value == "phase_time") BeginStage(Stage.Warmup, true);
else if (value == "build" || value == "phase_build") BeginStage(Stage.Build, true);
else if (value == "phase_pvp" || value == "pvp") BeginStage(Stage.Pvp, true);
else if (value == "raid" || value == "phase_raid") BeginStage(Stage.Raid, true);
}
[ChatCommand("warmup")]
private void WarmupChatCommand(BasePlayer player, string command, string[] args)
{
if (player == null || !player.IsAdmin) return;
if (args == null || args.Length == 0 || args[0].ToLowerInvariant() != "set")
{
SendReply(player, "/warmup set - поставить точку лобби-спавна");
return;
}
_data.LobbySpawn = player.transform.position;
_data.HasLobbySpawn = true;
SaveData();
SendReply(player, "Точка лобби-спавна сохранена");
}
[ChatCommand("respawn")]
private void RespawnChatCommand(BasePlayer player, string command, string[] args)
{
if (player == null || !player.IsAdmin) return;
if (args == null || args.Length == 0 || args[0].ToLowerInvariant() != "set")
{
SendReply(player, "/respawn set - поставить точку респавна после вармапа");
return;
}
_data.RespawnSpawn = player.transform.position;
_data.HasRespawnSpawn = true;
SaveData();
SendReply(player, "Точка респавна после вармапа сохранена");
}
[ChatCommand("респавн")]
private void RespawnRuChatCommand(BasePlayer player, string command, string[] args)
{
RespawnChatCommand(player, command, args);
}
[ChatCommand("вармап")]
private void WarmupRuChatCommand(BasePlayer player, string command, string[] args)
{
WarmupChatCommand(player, command, args);
}
[ChatCommand("вармапы")]
private void WarmupsRuChatCommand(BasePlayer player, string command, string[] args)
{
WarmupChatCommand(player, command, args);
}
private void OnPlayerInit(BasePlayer player)
{
QueueWarmupMove(player);
}
private void OnPlayerConnected(BasePlayer player)
{
QueueWarmupMove(player);
}
private void QueueWarmupMove(BasePlayer player)
{
if (_stage != Stage.Warmup || player == null || player.IsNpc) return;
timer.Once(1f, () => MovePlayerToWarmupSpawn(player));
}
private object OnPlayerRespawn(BasePlayer player, BasePlayer.SpawnPoint spawnPoint)
{
if (!CanUseCustomSpawn(player)) return null;
spawnPoint.pos = GetCustomSpawnPosition();
spawnPoint.rot = Quaternion.identity;
return spawnPoint;
}
private object OnFindSpawnPoint(BasePlayer player, ulong teamId)
{
if (!CanUseCustomSpawn(player)) return null;
return new BasePlayer.SpawnPoint
{
pos = GetCustomSpawnPosition(),
rot = Quaternion.identity
};
}
private bool CanUseCustomSpawn(BasePlayer player)
{
if (player != null && player.IsNpc) return false;
if (_data == null) return false;
return _stage == Stage.Warmup ? _data.HasLobbySpawn : _data.HasRespawnSpawn;
}
private Vector3 GetCustomSpawnPosition()
{
return _stage == Stage.Warmup ? GetSpawnPosition(_data.LobbySpawn) : GetSpawnPosition(_data.RespawnSpawn);
}
private void MovePlayersToWarmupSpawn()
{
if (_stage != Stage.Warmup) return;
if (_data == null || !_data.HasLobbySpawn)
{
PrintWarning("Точка лобби-спавна не задана. Используй /warmup set");
return;
}
foreach (BasePlayer player in BasePlayer.activePlayerList)
{
MovePlayerToWarmupSpawn(player);
}
}
private void MovePlayerToWarmupSpawn(BasePlayer player)
{
if (_stage != Stage.Warmup || player == null || !player.IsConnected || player.IsNpc || _data == null || !_data.HasLobbySpawn) return;
Vector3 position = GetSpawnPosition(_data.LobbySpawn);
if (player.isMounted) player.EnsureDismounted();
if (player.IsAlive())
{
player.Teleport(position);
return;
}
player.RespawnAt(position, Quaternion.identity);
}
private void MovePlayersToRespawnSpawn()
{
if (_data == null || !_data.HasRespawnSpawn)
{
PrintWarning("Точка респавна после вармапа не задана. Используй /respawn set");
return;
}
foreach (BasePlayer player in BasePlayer.activePlayerList)
{
MovePlayerToRespawnSpawn(player);
}
}
private void MovePlayerToRespawnSpawn(BasePlayer player)
{
if (player == null || !player.IsConnected || player.IsNpc) return;
Vector3 position = GetSpawnPosition(_data.RespawnSpawn);
if (player.isMounted) player.EnsureDismounted();
if (player.IsAlive())
{
player.Teleport(position);
return;
}
player.RespawnAt(position, Quaternion.identity);
}
private Vector3 GetSpawnPosition(Vector3 center)
{
Vector2 offset = UnityEngine.Random.insideUnitCircle * WarmupSpawnRadius;
Vector3 position = center + new Vector3(offset.x, 0f, offset.y);
return position;
}
private void LoadData()
{
try
{
_data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name) ?? new StoredData();
}
catch
{
_data = new StoredData();
}
}
private void SaveData()
{
Interface.Oxide.DataFileSystem.WriteObject(Name, _data);
}
private void StartSaveTimer()
{
DestroyTimer(ref _saveTimer);
_saveTimer = timer.Every(120f, SaveStageData);
}
private void SaveStageData()
{
if (_data == null) _data = new StoredData();
_data.WipeId = SaveRestore.WipeId;
_data.CurrentStage = GetStageName(_stage);
_data.SavedAtUnix = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
SaveData();
}
private void RestoreStage()
{
if (_data == null) _data = new StoredData();
if (IsNewWipe())
{
_data.CurrentStage = GetStageName(Stage.Warmup);
_data.StageEndsAtUnix = 0;
_data.WipeId = SaveRestore.WipeId;
BeginStage(Stage.Warmup, true);
return;
}
Stage stage;
if (!TryGetStage(_data.CurrentStage, out stage) || _data.StageEndsAtUnix <= 0)
{
BeginStage(Stage.Warmup, true);
return;
}
long now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
long endsAt = _data.StageEndsAtUnix;
for (int i = 0; i < 10000 && endsAt <= now; i++)
{
stage = GetNextStage(stage);
endsAt += GetStageSeconds(stage);
}
if (endsAt <= now)
{
BeginStage(Stage.Build, true);
return;
}
_data.CurrentStage = GetStageName(stage);
_data.StageEndsAtUnix = endsAt;
BeginStage(stage, true, Math.Max(1, (int)(endsAt - now)));
}
private bool IsNewWipe()
{
string wipeId = SaveRestore.WipeId;
if (!string.IsNullOrEmpty(_data.WipeId) && !string.IsNullOrEmpty(wipeId) && _data.WipeId != wipeId) return true;
if (!string.IsNullOrEmpty(_data.WipeId) || _data.SavedAtUnix <= 0 || SaveRestore.SaveCreatedTime.Year < 2020) return false;
long saveCreatedAt = new DateTimeOffset(SaveRestore.SaveCreatedTime.ToUniversalTime()).ToUnixTimeSeconds();
return saveCreatedAt > _data.SavedAtUnix;
}
private Stage GetNextStage(Stage stage)
{
if (stage == Stage.Warmup) return Stage.Build;
if (stage == Stage.Build) return Stage.Pvp;
if (stage == Stage.Pvp) return Stage.Raid;
return Stage.Build;
}
private bool TryGetStage(string value, out Stage stage)
{
stage = Stage.Warmup;
if (string.IsNullOrWhiteSpace(value)) return false;
value = value.ToLowerInvariant();
if (value == "warmup" || value == "phase_warmup" || value == "time" || value == "phase_time")
{
stage = Stage.Warmup;
return true;
}
if (value == "build" || value == "phase_build")
{
stage = Stage.Build;
return true;
}
if (value == "pvp" || value == "phase_pvp")
{
stage = Stage.Pvp;
return true;
}
if (value == "raid" || value == "phase_raid")
{
stage = Stage.Raid;
return true;
}
return false;
}
private string GetStageName(Stage stage)
{
if (stage == Stage.Warmup) return "warmup";
if (stage == Stage.Build) return "build";
if (stage == Stage.Pvp) return "pvp";
return "raid";
}
private void StartBuildZones()
{
ScanCupboards();
if (_zoneTimer == null) _zoneTimer = timer.Every(ZoneCheckRate, CheckPlayersInZones);
}
private void StopBuildZones()
{
DestroyTimer(ref _zoneTimer);
ClearZones();
_lastAllowedPositions.Clear();
_denyAttempts.Clear();
_lastDenyAttemptTime.Clear();
}
private void ScanCupboards()
{
foreach (BaseNetworkable entity in BaseNetworkable.serverEntities)
{
BuildingPrivlidge priv = entity as BuildingPrivlidge;
if (priv == null || priv.IsDestroyed) continue;
TrackCupboard(priv, BasePlayer.FindByID(priv.OwnerID), false);
}
}
private object CanBuild(Planner planner, Construction construction, Construction.Target target)
{
if (!IsCupboardPlacement(planner, construction)) return null;
BasePlayer player = planner?.GetOwnerPlayer() ?? target.player;
if (player == null) return null;
if (_stage == Stage.Raid)
{
Toast(player, "На рейд стадии нельзя ставить шкаф");
return true;
}
string key = GetGroupKey(player.userID, player);
if (!HasTeamCupboard(key, null)) return null;
Toast(player, "У вашей команды уже есть шкаф");
return true;
}
private object CanDeployItem(BasePlayer player, Deployer deployer, NetworkableId entityId)
{
if (player == null || !IsCupboardDeployable(deployer)) return null;
if (_stage == Stage.Raid)
{
Toast(player, "На рейд стадии нельзя ставить шкаф");
return true;
}
string key = GetGroupKey(player.userID, player);
if (!HasTeamCupboard(key, null)) return null;
Toast(player, "У вашей команды уже есть шкаф");
return true;
}
private void OnEntityBuilt(Planner planner, GameObject go)
{
BuildingPrivlidge priv = go == null ? null : UnityEngine.GameObjectEx.ToBaseEntity(go) as BuildingPrivlidge;
if (priv == null) return;
BasePlayer player = planner?.GetOwnerPlayer();
NextTick(() =>
{
if (priv == null || priv.IsDestroyed) return;
TrackCupboard(priv, player, true);
});
}
private void OnItemDeployed(Deployer deployer, ItemModDeployable deployable, BaseEntity entity)
{
TrackDeployedCupboard(deployer, entity);
}
private void OnItemDeployed(Deployer deployer, BaseEntity parent, BaseEntity entity)
{
TrackDeployedCupboard(deployer, entity);
}
private void TrackDeployedCupboard(Deployer deployer, BaseEntity entity)
{
BuildingPrivlidge priv = entity as BuildingPrivlidge;
if (priv == null) return;
BasePlayer player = deployer?.GetOwnerPlayer();
NextTick(() =>
{
if (priv == null || priv.IsDestroyed) return;
TrackCupboard(priv, player, true);
});
}
private void OnEntityKill(BuildingPrivlidge priv)
{
if (priv == null || priv.net == null) return;
if (_cupboardZones.TryGetValue(priv.net.ID, out string zoneKey)) RemoveZone(zoneKey);
RemoveTeamCupboard(priv.net.ID);
}
private void TrackCupboard(BuildingPrivlidge priv, BasePlayer builder, bool enforceLimit)
{
if (priv == null || priv.IsDestroyed || priv.net == null) return;
ulong ownerId = priv.OwnerID.IsSteamId() ? priv.OwnerID : builder != null ? builder.userID : 0;
if (ownerId == 0) return;
BasePlayer owner = BasePlayer.FindAwakeOrSleepingByID(ownerId) ?? builder;
string key = GetGroupKey(ownerId, owner);
if (string.IsNullOrEmpty(key)) return;
if (HasTeamCupboard(key, priv))
{
if (enforceLimit)
{
Toast(builder, "У вашей команды уже есть шкаф");
RefundCupboard(builder);
priv.Kill();
}
return;
}
_teamCupboards[key] = priv.net.ID;
if (_stage != Stage.Build) return;
if (_zones.TryGetValue(key, out BuildZone current) && IsZoneAlive(current)) return;
if (current != null) RemoveZone(key);
BuildZone zone = new BuildZone
{
Key = key,
OwnerId = ownerId,
CupboardId = priv.net.ID,
Cupboard = priv,
Sphere = CreateSphere(priv.transform.position)
};
_zones[key] = zone;
_cupboardZones[priv.net.ID] = key;
}
private bool IsCupboardPlacement(Planner planner, Construction construction)
{
Item item = planner?.GetOwnerItem();
if (item != null && item.info != null && item.info.shortname == "cupboard.tool") return true;
ItemModDeployable deployable = item?.info?.GetComponent<ItemModDeployable>();
string path = deployable == null ? string.Empty : deployable.entityPrefab.resourcePath;
if (!string.IsNullOrEmpty(path) && path.ToLowerInvariant().Contains("cupboard.tool")) return true;
string prefab = construction == null ? string.Empty : construction.fullName;
if (string.IsNullOrEmpty(prefab)) return false;
return prefab.ToLowerInvariant().Contains("cupboard.tool");
}
private bool IsCupboardDeployable(Deployer deployer)
{
Item item = deployer?.GetOwnerItem();
if (item != null && item.info != null && item.info.shortname == "cupboard.tool") return true;
ItemModDeployable deployable = deployer?.GetModDeployable();
string path = deployable == null ? string.Empty : deployable.entityPrefab.resourcePath;
return !string.IsNullOrEmpty(path) && path.ToLowerInvariant().Contains("cupboard.tool");
}
private bool HasTeamCupboard(string key, BuildingPrivlidge ignore)
{
if (string.IsNullOrEmpty(key)) return false;
if (!_teamCupboards.TryGetValue(key, out NetworkableId id)) return false;
if (ignore != null && ignore.net != null && ignore.net.ID == id) return false;
if (IsCupboardAlive(id)) return true;
_teamCupboards.Remove(key);
return false;
}
private bool IsCupboardAlive(NetworkableId id)
{
foreach (BaseNetworkable entity in BaseNetworkable.serverEntities)
{
BuildingPrivlidge priv = entity as BuildingPrivlidge;
if (priv == null || priv.net == null || priv.IsDestroyed) continue;
if (priv.net.ID == id) return true;
}
return false;
}
private void RemoveTeamCupboard(NetworkableId id)
{
string removeKey = null;
foreach (KeyValuePair<string, NetworkableId> pair in _teamCupboards)
{
if (pair.Value != id) continue;
removeKey = pair.Key;
break;
}
if (removeKey != null) _teamCupboards.Remove(removeKey);
}
private void RefundCupboard(BasePlayer player)
{
if (player == null || !player.IsConnected) return;
Item item = ItemManager.CreateByName("cupboard.tool", 1);
if (item != null) player.GiveItem(item);
}
private SphereEntity CreateSphere(Vector3 position)
{
SphereEntity sphere = GameManager.server.CreateEntity(SpherePrefab, position, Quaternion.identity) as SphereEntity;
if (sphere == null) return null;
float targetRadius = ZoneRadius * 2f;
sphere.currentRadius = SphereStartRadius;
sphere.lerpRadius = SphereStartRadius;
sphere.lerpSpeed = targetRadius / SphereExpandSeconds;
sphere.enableSaving = false;
sphere.Spawn();
sphere.LerpRadiusTo(targetRadius, targetRadius / SphereExpandSeconds);
return sphere;
}
private void CreateMapMarkers(BuildZone zone, string tag, Vector3 position)
{
}
private float CalculateMapRadius()
{
const float a = 100 / 6f;
float b = Mathf.Sqrt(a) / 2f;
float c = World.Size / 1300f;
return b / c;
}
private void CheckPlayersInZones()
{
if (_stage != Stage.Build || _zones.Count == 0) return;
foreach (BasePlayer player in BasePlayer.activePlayerList)
{
if (player == null || !player.IsConnected || player.IsNpc || !player.IsAlive()) continue;
BuildZone forbidden = FindForbiddenZone(player);
if (forbidden == null)
{
_lastAllowedPositions[player.userID] = player.transform.position;
_denyAttempts.Remove(player.userID);
_lastDenyAttemptTime.Remove(player.userID);
continue;
}
DenyZoneEntry(player, forbidden);
}
}
private BuildZone FindForbiddenZone(BasePlayer player)
{
return FindForbiddenZone(player, player.transform.position);
}
private BuildZone FindForbiddenZone(BasePlayer player, Vector3 position)
{
List<string> deadZones = null;
foreach (BuildZone zone in _zones.Values)
{
if (!IsZoneAlive(zone))
{
if (deadZones == null) deadZones = new List<string>();
deadZones.Add(zone.Key);
continue;
}
if (Vector3.Distance(position, zone.Cupboard.transform.position) > ZoneRadius) continue;
if (IsAllowedInZone(zone, player)) continue;
return zone;
}
if (deadZones != null)
{
for (int i = 0; i < deadZones.Count; i++)
RemoveZone(deadZones[i]);
}
return null;
}
private void DenyZoneEntry(BasePlayer player, BuildZone zone)
{
Vector3 safePosition = GetSafePosition(player, zone);
float now = Time.realtimeSinceStartup;
if (_lastDenyAttemptTime.TryGetValue(player.userID, out float last) && now - last < DenyAttemptCooldown)
{
ForceOutOfZone(player, safePosition);
return;
}
_lastDenyAttemptTime[player.userID] = now;
int attempts = 0;
_denyAttempts.TryGetValue(player.userID, out attempts);
attempts++;
_denyAttempts[player.userID] = attempts;
Toast(player, $"Запрещено {attempts}/{MaxDenyAttempts}");
if (attempts >= MaxDenyAttempts)
{
player.Kick("Запрещено входить в чужую билд-зону");
_denyAttempts.Remove(player.userID);
_lastDenyAttemptTime.Remove(player.userID);
return;
}
ForceOutOfZone(player, safePosition);
}
private Vector3 GetSafePosition(BasePlayer player, BuildZone zone)
{
if (!_lastAllowedPositions.TryGetValue(player.userID, out Vector3 safePosition))
{
Vector3 direction = player.transform.position - zone.Cupboard.transform.position;
if (direction.sqrMagnitude < 1f) direction = Vector3.forward;
direction.y = 0f;
safePosition = zone.Cupboard.transform.position + direction.normalized * (ZoneRadius + 2f);
safePosition.y = player.transform.position.y;
}
return safePosition;
}
private void ForceOutOfZone(BasePlayer player, Vector3 safePosition)
{
if (player == null) return;
if (player.isMounted) player.EnsureDismounted();
player.Teleport(safePosition);
}
private object CanMountEntity(BasePlayer player, BaseMountable mountable)
{
if (_stage != Stage.Build || player == null || mountable == null) return null;
if (FindForbiddenZone(player, mountable.transform.position) == null) return null;
Toast(player, "Нельзя заезжать в чужую билд-зону");
return true;
}
private void OnEntityMounted(BaseMountable mountable, BasePlayer player)
{
if (_stage != Stage.Build || player == null || mountable == null) return;
BuildZone zone = FindForbiddenZone(player, player.transform.position);
if (zone == null) return;
DenyZoneEntry(player, zone);
}
private bool IsAllowedInZone(BuildZone zone, BasePlayer player)
{
if (zone == null || player == null) return false;
if (player.IsAdmin) return true;
if (zone.OwnerId == player.userID) return true;
if (IsAlly(zone.OwnerId, player.userID)) return true;
BuildingPrivlidge priv = zone.Cupboard;
return priv != null && priv.authorizedPlayers != null && priv.authorizedPlayers.Contains(player.userID);
}
private bool IsAlly(ulong ownerId, ulong playerId)
{
if (ownerId == playerId) return true;
RelationshipManager.PlayerTeam team = RelationshipManager.ServerInstance?.FindPlayersTeam(ownerId);
if (team != null && team.members != null && team.members.Contains(playerId)) return true;
if (SClan?.Call<bool>("IsTeammate", ownerId, playerId) == true) return true;
if (Friends?.Call("GetFriends", ownerId) is ulong[] friends)
{
for (int i = 0; i < friends.Length; i++)
if (friends[i] == playerId)
return true;
}
if (Clans?.Call("GetClanMembers", ownerId.ToString()) is ulong[] clanMembers)
{
for (int i = 0; i < clanMembers.Length; i++)
if (clanMembers[i] == playerId)
return true;
}
if (FClan?.Call("GetClanMembers", ownerId) is List<ulong> fclanMembers && fclanMembers.Contains(playerId)) return true;
return false;
}
private string GetGroupKey(ulong ownerId, BasePlayer player)
{
string tag = GetClanTag(ownerId);
if (!string.IsNullOrEmpty(tag)) return "clan:" + tag;
RelationshipManager.PlayerTeam team = player != null && player.currentTeam != 0 ? player.Team : RelationshipManager.ServerInstance?.FindPlayersTeam(ownerId);
if (team != null && team.teamID != 0) return "team:" + team.teamID;
return "player:" + ownerId;
}
private string GetGroupTag(ulong ownerId, BasePlayer player)
{
string tag = GetClanTag(ownerId);
if (!string.IsNullOrEmpty(tag)) return tag;
RelationshipManager.PlayerTeam team = player != null && player.currentTeam != 0 ? player.Team : RelationshipManager.ServerInstance?.FindPlayersTeam(ownerId);
if (team != null && team.teamID != 0) return "TEAM " + team.teamID;
BasePlayer owner = BasePlayer.FindAwakeOrSleepingByID(ownerId);
return owner == null ? ownerId.ToString() : owner.displayName;
}
private string GetClanTag(ulong ownerId)
{
string tag = FClan?.Call<string>("GetClanTag", ownerId);
if (!string.IsNullOrEmpty(tag)) return tag;
tag = SClan?.Call<string>("GetClanTag", ownerId);
return string.IsNullOrEmpty(tag) ? null : tag;
}
private bool IsZoneAlive(BuildZone zone)
{
return zone != null && zone.Cupboard != null && !zone.Cupboard.IsDestroyed && zone.Sphere != null && !zone.Sphere.IsDestroyed;
}
private void RemoveZone(string key)
{
if (string.IsNullOrEmpty(key)) return;
if (!_zones.TryGetValue(key, out BuildZone zone)) return;
if (zone.Sphere != null && !zone.Sphere.IsDestroyed) zone.Sphere.Kill();
if (zone.RadiusMarker != null && !zone.RadiusMarker.IsDestroyed) zone.RadiusMarker.Kill();
if (zone.NameMarker != null && !zone.NameMarker.IsDestroyed) zone.NameMarker.Kill();
_zones.Remove(key);
_cupboardZones.Remove(zone.CupboardId);
}
private void ClearZones()
{
foreach (BuildZone zone in _zones.Values)
{
if (zone.Sphere != null && !zone.Sphere.IsDestroyed) zone.Sphere.Kill();
if (zone.RadiusMarker != null && !zone.RadiusMarker.IsDestroyed) zone.RadiusMarker.Kill();
if (zone.NameMarker != null && !zone.NameMarker.IsDestroyed) zone.NameMarker.Kill();
}
_zones.Clear();
_cupboardZones.Clear();
}
private object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info)
{
if (entity == null || info == null) return null;
if (info.InitiatorPlayer != null && info.InitiatorPlayer.IsAdmin) return null;
if (_stage == Stage.Build)
{
BasePlayer target = entity as BasePlayer;
if (target != null && !target.IsNpc)
{
if (IsSuicideDamage(target, info)) return null;
if (info.damageTypes.Has(DamageType.Fall)) return null;
return true;
}
if (IsExplosiveDamage(info) && IsBuildProtectedEntity(entity))
{
return true;
}
if (IsPlayerOwnedEntity(entity) && info.InitiatorPlayer != null)
{
return true;
}
}
if (_stage == Stage.Pvp)
{
BasePlayer target = entity as BasePlayer;
if (target != null && !target.IsNpc && IsExplosiveDamage(info))
{
return true;
}
if ((target == null || target.IsNpc) && IsBuildProtectedEntity(entity) && info.InitiatorPlayer != null)
{
return true;
}
}
return null;
}
private object CanLootPlayer(BasePlayer target, BasePlayer looter)
{
if (_stage != Stage.Build) return null;
if (target == null || looter == null || target == looter) return null;
if (!target.IsSleeping()) return null;
Toast(looter, "Спящего игрока нельзя обыскивать");
return false;
}
private bool IsSuicideDamage(BasePlayer target, HitInfo info)
{
if (target == null || info == null) return false;
if (info.damageTypes.Has(DamageType.Suicide)) return true;
return info.InitiatorPlayer == target && info.damageTypes.Total() >= 999f;
}
private object CanEquipItem(PlayerInventory inventory, Item item)
{
if (_stage != Stage.Build || inventory == null || item == null) return null;
BasePlayer player = inventory.GetComponent<BasePlayer>();
if (player == null || player.IsNpc || !IsBlockedHoldItem(player, item)) return null;
Toast(player, "На билд стадии оружие запрещено");
return false;
}
private object CanEquipItem(PlayerInventory inventory, Item item, int targetSlot)
{
return CanEquipItem(inventory, item);
}
private object OnActiveItemChange(BasePlayer player, Item oldItem, ItemId newItemId)
{
if (_stage != Stage.Build || player == null || player.IsNpc || player.inventory?.containerBelt == null) return null;
Item newItem = player.inventory.containerBelt.FindItemByUID(newItemId);
if (newItem == null || !IsBlockedHoldItem(player, newItem)) return null;
Toast(player, "На билд стадии оружие запрещено");
return true;
}
private void OnActiveItemChanged(BasePlayer player, Item oldItem, Item newItem)
{
if (_stage != Stage.Build || player == null || player.IsNpc || newItem == null) return;
if (IsBlockedHoldItem(player, newItem)) NextTick(() => ClearActiveItem(player));
}
private object OnPlayerInput(BasePlayer player, InputState input)
{
if (_stage != Stage.Build || player == null || player.IsNpc) return null;
Item item = player.GetActiveItem();
if (item != null && IsBlockedHoldItem(player, item)) ClearActiveItem(player);
return null;
}
private bool IsBlockedHoldItem(BasePlayer player, Item item)
{
if (item == null || item.info == null) return false;
if (item.info.category == ItemCategory.Tool) return false;
if (item.info.category == ItemCategory.Weapon) return true;
return player != null && player.IsHostileItem(item);
}
private void ClearActiveWeaponsForAll()
{
foreach (BasePlayer player in BasePlayer.activePlayerList)
{
if (player == null || player.IsNpc) continue;
Item item = player.GetActiveItem();
if (item != null && IsBlockedHoldItem(player, item)) ClearActiveItem(player);
}
}
private void ClearActiveItem(BasePlayer player)
{
if (player == null) return;
player.UpdateActiveItem(default(ItemId));
player.SendNetworkUpdateImmediate();
}
private bool IsRocketDamage(HitInfo info)
{
if (info == null || !info.damageTypes.Has(DamageType.Explosion)) return false;
return HasRocketName(info.WeaponPrefab) || HasRocketName(info.Initiator) || HasRocketName(info.ProjectilePrefab);
}
private bool IsExplosiveDamage(HitInfo info)
{
return info != null && info.damageTypes.Has(DamageType.Explosion);
}
private bool HasRocketName(UnityEngine.Object obj)
{
if (obj == null) return false;
BaseEntity entity = obj as BaseEntity;
string name = entity != null ? entity.ShortPrefabName : obj.name;
if (string.IsNullOrEmpty(name)) return false;
name = name.ToLowerInvariant();
return name.Contains("rocket") || name.Contains("mlrs");
}
private bool IsPlayerOwnedEntity(BaseEntity entity)
{
return entity != null && entity.OwnerID.IsSteamId();
}
private bool IsBuildProtectedEntity(BaseEntity entity)
{
if (entity == null) return false;
if (entity is BuildingBlock || entity is SimpleBuildingBlock) return true;
return IsPlayerOwnedEntity(entity);
}
private void Toast(BasePlayer player, string text)
{
if (player == null || !player.IsConnected) return;
player.ShowToast(GameTip.Styles.Red_Normal, new Translate.Phrase("fbuildzonestages.toast", text), false);
}
private void DestroyTimer(ref Timer value)
{
if (value == null) return;
value.Destroy();
value = null;
}
[HookMethod("GetCurrentPhase")]
public string GetCurrentPhase()
{
if (_stage == Stage.Warmup) return "warmup";
if (_stage == Stage.Build) return "build";
if (_stage == Stage.Pvp) return "pvp";
return "raid";
}
[HookMethod("GetCupboardInfo")]
public Dictionary<string, object> GetCupboardInfo(BuildingPrivlidge priv)
{
if (priv == null || priv.net == null) return null;
string key = null;
if (!_cupboardZones.TryGetValue(priv.net.ID, out key))
{
foreach (KeyValuePair<string, NetworkableId> entry in _teamCupboards)
{
if (entry.Value == priv.net.ID)
{
key = entry.Key;
break;
}
}
}
string clanTag = null;
if (!string.IsNullOrEmpty(key) && key.StartsWith("clan:")) clanTag = key.Substring(5);
if (string.IsNullOrEmpty(clanTag)) clanTag = GetClanTag(priv.OwnerID);
return new Dictionary<string, object>
{
["phase"] = GetCurrentPhase(),
["ownerId"] = priv.OwnerID,
["groupKey"] = key ?? string.Empty,
["clanTag"] = clanTag ?? string.Empty
};
}
}
}