C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
//Reference: 0Harmony
using System;
using System.Collections.Generic;
using System.Linq;
using ConVar;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using Oxide.Game.Rust.Libraries;
using Rust;
using UnityEngine;
using Harmony;
namespace Oxide.Plugins {
[Info("RoadBradley", "ThePitereq", "1.5.3")]
public class RoadBradley : RustPlugin {
private static RoadBradley _plugin;
[PluginReference]
private readonly Plugin Economics, ServerRewards, Friends, Clans, ShoppyStock;
private static HarmonyInstance harmony;
private readonly List<BradleyAPC> roadBradleys = new List<BradleyAPC>();
private readonly Dictionary<string, RoadInfo> roads = new Dictionary<string, RoadInfo>();
private readonly Dictionary<ulong, Dictionary<string, int>> purchaseCooldowns = new Dictionary<ulong, Dictionary<string, int>>();
private readonly Dictionary<BradleyAPC, int> bradleyTimers = new Dictionary<BradleyAPC, int>();
private readonly Dictionary<Vector3, string> obstacleChecks = new Dictionary<Vector3, string>();
private readonly Dictionary<string, SpawnConfig> mapRoutes = new Dictionary<string, SpawnConfig>();
private readonly Dictionary<BradleyAPC, ulong> currentBradleyOwners = new Dictionary<BradleyAPC, ulong>();
private readonly List<ulong> routeEditors = new List<ulong>();
private readonly TankDeathInfo tankDeathInfo = new TankDeathInfo();
private string mapName;
private Timer killTimer;
private static readonly Dictionary<string, string> markerPrefabs = new Dictionary<string, string>() {
{
"crate", "assets/prefabs/tools/map/cratemarker.prefab"
}
, {
"chinook", "assets/prefabs/tools/map/ch47marker.prefab"
}
, {
"cargoship", "assets/prefabs/tools/map/cargomarker.prefab"
}
, {
"generic", "assets/prefabs/tools/map/genericradiusmarker.prefab"
}
, {
"vending", "assets/prefabs/deployable/vendingmachine/vending_mapmarker.prefab"
}
}
;
private class RoadInfo {
public bool isDefault = true;
public bool isRandomEvent = false;
public bool isPurchasable = false;
public bool isNowOccupied = false;
public string routeName = string.Empty;
public List<Vector3> checkpoints = new List<Vector3>();
}
private class TankDeathInfo {
public Vector3 tankDeathLoc = Vector3.zero;
public string tankDeathProfile = "";
public ulong tankDeathOwner = 0;
public ulong topDmgUser = 0;
public Dictionary<string, int> crateCount = new Dictionary<string, int>();
}
private void Init() {
Unsubscribe(nameof(OnWeaponFired));
Unsubscribe(nameof(OnPlayerAttack));
Unsubscribe(nameof(OnLoseCondition));
}
private void OnServerInitialized() {
_plugin = this;
cmd.AddChatCommand("tankroute", this, nameof(TankRouteCommand));
permission.RegisterPermission("roadbradley.admin", this);
permission.RegisterPermission("roadbradley.bypass", this);
config = Config.ReadObject<PluginConfig>();
if (config.commands.Count == 0) config.commands.Add("buytank");
AddCovalenceCommand("spawnrbtank", nameof(SpawnTankCommand));
AddCovalenceCommand("tankcount", nameof(TankCountCommand));
Config.WriteObject(config);
LoadMessages();
LoadData();
harmony = HarmonyInstance.Create("com.ThePitereq.RoadBradley");
harmony.Patch(original : AccessTools.Method(typeof(BradleyAPC), nameof(BradleyAPC.DoPhysicsMove)), prefix : new HarmonyMethod(typeof(BradleyLimitMovement), nameof(BradleyLimitMovement.Prefix)));
/*foreach (var road in roadData.roads.ToList())
{
Vector3 prev = Vector3.zero;
bool startDeleting = false;
foreach (var checkpoint in road.Value.ToList())
{
if (checkpoint == prev)
startDeleting = true;
prev = checkpoint;
if (startDeleting)
roadData.roads[road.Key].Remove(checkpoint);
}
}
SaveData();*/
if (config.checkObstacles) foreach (ProtoBuf.PrefabData entry in World.Serialization.world.prefabs) {
if (TerrainMeta.TopologyMap.GetTopology(entry.position, 2048) && !obstacleChecks.ContainsKey(entry.position)) {
string prefabName = StringPool.Get(entry.id).Split('/').Last().Replace(".prefab", "");
bool shouldAdd = true;
foreach (var whitelist in config.obstacleWhitelist) {
if (prefabName.ToLower().Contains(whitelist.ToLower())) {
shouldAdd = false;
break;
}
}
if (shouldAdd) obstacleChecks.Add(entry.position, prefabName);
}
}
FindRoads();
cmd.AddChatCommand("showroute", this, nameof(DebugShowRouteCommand));
//DEBUG
foreach (var command in config.commands) cmd.AddChatCommand(command, this, nameof(BuyTankCommand));
if (!roads.Any()) {
PrintWarning("Roads on map not found! Disabling the plugin...");
Server.Command($"oxide.unload {this.Name}");
return;
}
if (!config.mapTracks.ContainsKey("default")) {
PrintWarning("Default road config not found! Disabling the plugin...");
Server.Command($"oxide.unload {this.Name}");
return;
}
mapName = ConVar.Server.levelurl.Split('/').Last();
foreach (var configValue in config.mapTracks) if (mapName.Contains(configValue.Key)) {
mapName = configValue.Key;
break;
}
foreach (var configValue in config.purchases.Values) if (configValue.permission != "") permission.RegisterPermission(configValue.permission, this);
if (config.mapTracks.ContainsKey(mapName)) foreach (var spawnConfig in config.mapTracks[mapName]) mapRoutes.Add(spawnConfig, config.spawns[spawnConfig]);
else {
mapName = "default";
if (ConVar.Server.level == "HapisIsland") mapName = "HapisIsland";
if (config.mapTracks.ContainsKey(mapName)) foreach (var spawnConfig in config.mapTracks[mapName]) mapRoutes.Add(spawnConfig, config.spawns[spawnConfig]);
}
if (config.ignoreOwnerships) Unsubscribe(nameof(CanLootEntity));
if (!config.enabledBags) Unsubscribe(nameof(CanBeWounded));
Puts($"Setting routes for '{mapName}' map profile...");
GenerateDefaultTracks();
if (config.purchases.Any()) GeneratePurchasableTracks();
foreach (var route in mapRoutes) SpawnDefaultRouteTank(route.Key);
SaveData();
}
private void Unload() {
SaveData();
foreach (var bradley in roadBradleys) bradley?.Kill();
foreach (var player in BasePlayer.activePlayerList) {
CuiHelper.DestroyUi(player, "RoadBradleyUI_Main");
CuiHelper.DestroyUi(player, "RoadBradleyUI_Timer");
CuiHelper.DestroyUi(player, "RoadBradleyUI_Edit");
}
harmony.UnpatchAll("com.ThePitereq.RoadBradley");
}
private void FindRoads() {
int counter = 0;
foreach (var road in TerrainMeta.Path.Roads) {
if (config.asphaltOnly && road.Width <= 5) continue;
roads.TryAdd($"{counter}", new RoadInfo() {
isDefault = true
}
);
roads[$"{counter}"].checkpoints = new List<Vector3>(road.Path.Points.ToList());
roads.Add($"{counter}_Edited", new RoadInfo() {
isDefault = false, isPurchasable = true
}
);
roads[$"{counter}_Edited"].checkpoints = new List<Vector3>();
List<Vector3> alreadyListed = new List<Vector3>();
bool failedCheck = false;
foreach (var checkpoint in road.Path.Points.ToList()) {
if (config.checkObstacles) {
foreach (var obstacle in obstacleChecks) {
if (!alreadyListed.Contains(obstacle.Key) && Vector3.Distance(checkpoint, obstacle.Key) < road.Width / 2) {
alreadyListed.Add(obstacle.Key);
if (config.removeObstacleRoads) {
Puts($"There is an obstacle '{obstacle.Value}' on the road '{counter}' at '{obstacle.Key}'.Removing road from the road pool!");
roads.Remove($"{counter}_Edited");
failedCheck = true;
break;
}
else Puts($"There is an obstacle '{obstacle.Value}' on the road '{counter}' at '{obstacle.Key}'.");
}
}
if (failedCheck) break;
}
Vector3 checkpointFixed = checkpoint;
checkpointFixed.y = TerrainMeta.HeightMap.GetHeight(checkpointFixed);
if (config.heightOffset != 0) checkpointFixed.y += config.heightOffset;
roads[$"{counter}_Edited"].checkpoints.Add(checkpointFixed);
}
counter++;
if (failedCheck) continue;
}
foreach (var road in roadData.roads) {
roads.Add(road.Key, new RoadInfo() {
isDefault = false, checkpoints = road.Value
}
);
}
SaveData();
}
private void GenerateDefaultTracks() {
bool longestSet = false;
foreach (var route in mapRoutes) {
if (!config.spawns.ContainsKey(route.Key)) {
PrintWarning($"Route '{route.Key}' not found in configuration! Skipping...");
continue;
}
if (route.Value.useLongestPath) {
if (longestSet) PrintWarning($"You've already set one route to be on longest route! Bradleys can collide!");
longestSet = true;
int longest = 0;
string longestName = "";
foreach (var road in roads) {
if (road.Value.isDefault) continue;
int count = road.Value.checkpoints.Count;
if (count > longest) {
longest = count;
longestName = road.Key;
}
}
if (roads[longestName].checkpoints.Count < 5) {
PrintWarning($"Route '{longestName}' is too small for a bradley! Cannot be used.");
continue;
}
if (config.removeTimedRoads) roads[longestName].isPurchasable = false;
roads[longestName].isRandomEvent = true;
roads[longestName].routeName = route.Key;
bool isLooped = false;
if (Vector3.Distance(roads[longestName].checkpoints.First(), roads[longestName].checkpoints.Last()) < 5f) isLooped = true;
if (!isLooped) {
if (route.Value.removeCheckpointAmount * 2 >= roads[longestName].checkpoints.Count) PrintWarning($"You are removing more start/end checkpoints that is available on road '{longestName}'!");
List<Vector3> removeCheckpoints = new List<Vector3>();
removeCheckpoints.AddRange(roads[longestName].checkpoints.Take(config.removePurchasableCheckpointAmount));
removeCheckpoints.AddRange(roads[longestName].checkpoints.TakeLast(config.removePurchasableCheckpointAmount));
foreach (var point in removeCheckpoints) roads[longestName].checkpoints.Remove(point);
int pointCount = roads[longestName].checkpoints.Count - 1;
foreach (var point in roads[longestName].checkpoints.ToList()) {
roads[longestName].checkpoints.Add(roads[longestName].checkpoints[pointCount]);
pointCount--;
}
}
Puts($"Route '{longestName}' set and added as longest bradley route!");
}
else {
List<List<string>> routes = new List<List<string>>(route.Value.trackList);
if (route.Value.useAllDefault) {
routes = new List<List<string>>();
foreach (var road in roads) if (road.Value.isDefault) routes.Add(new List<string>() {
road.Key
}
);
}
int routeCount = 0;
foreach (var road1 in routes) {
string fixedName1 = $"{road1[0]}_Edited";
int i = int.TryParse(road1[0], out i) ? i : -1;
if (i == -1) fixedName1 = road1[0];
if (!roads.ContainsKey(fixedName1)) {
PrintWarning($"No data found about road '{fixedName1}' on map! Skipping whole route!");
continue;
}
string roadDisplay = fixedName1;
if (route.Value.trackList.Any()) roadDisplay = string.Join(",", route.Value.trackList[routeCount]);
foreach (var road2 in road1.Skip(1)) {
string fixedName2 = $"{road2}_Edited";
int x;
if (!int.TryParse(road2, out x)) fixedName2 = road2;
if (!roads.ContainsKey(fixedName2)) {
PrintWarning($"No data found about road '{road2}' on map! Skipping this road in route!");
continue;
}
roads[fixedName1].checkpoints.AddRange(roads[fixedName2].checkpoints);
if (config.removeTimedRoads) {
roads[fixedName2].isPurchasable = false;
roads[fixedName2].isRandomEvent = false;
}
}
if (roads[fixedName1].checkpoints.Count < 5) {
PrintWarning($"Route made with roads '{roadDisplay}' is too small! Cannot be used.");
continue;
}
if (config.removeTimedRoads) roads[fixedName1].isPurchasable = false;
roads[fixedName1].isRandomEvent = true;
roads[fixedName1].routeName = route.Key;
bool isLooped = false;
if (Vector3.Distance(roads[fixedName1].checkpoints.First(), roads[fixedName1].checkpoints.Last()) < 5f) isLooped = true;
if (!isLooped) {
if (i != -1) {
if (route.Value.removeCheckpointAmount * 2 >= roads[fixedName1].checkpoints.Count) PrintWarning($"You are removing more start/end checkpoints that is available on road '{fixedName1}'!");
List<Vector3> removeCheckpoints = new List<Vector3>();
removeCheckpoints.AddRange(roads[fixedName1].checkpoints.Take(config.removePurchasableCheckpointAmount));
removeCheckpoints.AddRange(roads[fixedName1].checkpoints.TakeLast(config.removePurchasableCheckpointAmount));
foreach (var point in removeCheckpoints) roads[fixedName1].checkpoints.Remove(point);
}
int pointCount = roads[fixedName1].checkpoints.Count - 1;
foreach (var point in roads[fixedName1].checkpoints.ToList()) {
roads[fixedName1].checkpoints.Add(roads[fixedName1].checkpoints[pointCount]);
pointCount--;
}
}
Puts($"Route made with roads '{roadDisplay}' set and added as bradley route!");
routeCount++;
}
}
}
}
private void GeneratePurchasableTracks() {
int counter = 0;
foreach (var road in roads) {
if (road.Value.isDefault) continue;
if (config.removeTimedRoads && road.Value.isRandomEvent) continue;
if (!road.Value.checkpoints.Any()) continue;
int i = int.TryParse(road.Key.Split('_')[0], out i) ? i : -1;
if (i == -1 && !(config.customRoads.ContainsKey(mapName) && config.customRoads[mapName].Contains(road.Key))) continue;
if (i != -1 && config.roadBlacklist.ContainsKey(mapName) && config.roadBlacklist[mapName].Contains(i)) continue;
road.Value.isPurchasable = true;
bool isLooped = false;
if (Vector3.Distance(road.Value.checkpoints.First(), road.Value.checkpoints.Last()) < 5f) isLooped = true;
if (!isLooped) {
if (i != -1) {
if (config.removePurchasableCheckpointAmount * 2 >= road.Value.checkpoints.Count) PrintWarning($"You are removing more start/end checkpoints that is available on road {road.Key}!");
List<Vector3> removeCheckpoints = new List<Vector3>();
removeCheckpoints.AddRange(road.Value.checkpoints.Take(config.removePurchasableCheckpointAmount));
removeCheckpoints.AddRange(road.Value.checkpoints.TakeLast(config.removePurchasableCheckpointAmount));
foreach (var point in removeCheckpoints) road.Value.checkpoints.Remove(point);
}
int pointCount = road.Value.checkpoints.Count - 1;
foreach (var point in road.Value.checkpoints.ToList()) {
road.Value.checkpoints.Add(road.Value.checkpoints[pointCount]);
pointCount--;
}
}
counter++;
Puts($"Route '{road.Key}' set and added as purchasable bradley route!");
}
if (counter == 0) PrintWarning("You have too less roads to make purchasable bradleys! Decrease amount of timed bradleys or disable purchases!");
else Puts("Tracks for purchasable bradleys set successfully!");
}
private void SpawnDefaultRouteTank(string route) {
int thisRouteCount = 0;
foreach (var tank in roadBradleys.ToList()) {
if (tank == null) {
roadBradleys.Remove(tank);
continue;
}
if (tank.GetComponent<RoadBradleyController>().routeName == route) thisRouteCount++;
}
int playerCount = BasePlayer.activePlayerList.Count;
int bradleys = 0;
foreach (var bradleyAmount in config.spawns[route].onlinePlayers) {
if (playerCount >= bradleyAmount.Value) bradleys = bradleyAmount.Key;
}
int bradleysToSpawn = bradleys - thisRouteCount;
if (bradleysToSpawn <= 0) {
if (thisRouteCount == 0) timer.Once(900, () => SpawnDefaultRouteTank(route));
return;
}
for (int i = 0; i < bradleysToSpawn; i++) {
int chance = 0;
foreach (var tankType in config.spawns[route].chances) chance += tankType.Value;
int rolledTank = Core.Random.Range(1, chance + 1);
chance = 0;
foreach (var tankType in config.spawns[route].chances) {
chance += tankType.Value;
if (chance >= rolledTank) {
SpawnTank(tankType.Key, route);
break;
}
}
}
}
private void SpawnTank(string profileName, string routeName) {
ProfileConfig configValue = config.profiles[profileName];
List<string> possibleRoads = new List<string>();
foreach (var road in roads) {
if (road.Value.routeName == routeName && !road.Value.isNowOccupied) possibleRoads.Add(road.Key);
}
if (!possibleRoads.Any()) {
PrintWarning($"Couldn't find suitable road for bradley with profile '{profileName}' on route '{routeName}'.");
return;
}
string rolledRoad = possibleRoads[Core.Random.Range(0, possibleRoads.Count - 1)];
int spawnPoint = 0;
if (config.randomizeSpawn) spawnPoint = Core.Random.Range(0, roads[rolledRoad].checkpoints.Count - 1);
Vector3 startPosition = roads[rolledRoad].checkpoints[spawnPoint] + new Vector3(0, 0.1f, 0);
RuntimePath runtimePath = new RuntimePath();
RuntimePath runtimePath2 = runtimePath;
IAIPathNode[] nodes = new RuntimePathNode[roads[rolledRoad].checkpoints.Count];
runtimePath2.Nodes = nodes;
IAIPathNode iaipathNode = null;
int num5 = 0;
int num6 = roads[rolledRoad].checkpoints.Count - 1;
for (int j = 0; j <= num6; j++) {
IAIPathNode iaipathNode2 = new RuntimePathNode(roads[rolledRoad].checkpoints[j] + Vector3.up * 1f);
if (iaipathNode != null) {
iaipathNode2.AddLink(iaipathNode);
iaipathNode.AddLink(iaipathNode2);
}
runtimePath.Nodes[num5] = iaipathNode2;
iaipathNode = iaipathNode2;
num5++;
}
RuntimeInterestNode interestNode = new RuntimeInterestNode(roads[rolledRoad].checkpoints[0] + Vector3.up * 1f);
runtimePath.AddInterestNode(interestNode);
RuntimeInterestNode interestNode2 = new RuntimeInterestNode(roads[rolledRoad].checkpoints.Last() + Vector3.up * 1f);
runtimePath.AddInterestNode(interestNode2);
BradleyAPC tank = GameManager.server.CreateEntity("assets/prefabs/npc/m2bradley/bradleyapc.prefab", startPosition) as BradleyAPC;
roads[rolledRoad].isNowOccupied = true;
tank.Spawn();
tank.InstallPatrolPath(runtimePath);
tank.gameObject.AddComponent<RoadBradleyController>();
RoadBradleyController controller = tank.GetComponent<RoadBradleyController>();
controller.roadInfo = roads[rolledRoad];
controller.routeName = routeName;
controller.profile = profileName;
if (configValue.disarmEnabled) {
GameObject go = new GameObject();
go.AddComponent<BradleyDisarmZone>();
BradleyDisarmZone bradleyDisarm = go.GetComponent<BradleyDisarmZone>();
go.transform.SetParent(tank.transform, true);
bradleyDisarm.SetupZone(tank);
}
roadBradleys.Add(tank);
tank.currentPath = new List<Vector3>(roads[rolledRoad].checkpoints);
tank.currentPathIndex = spawnPoint;
tank.moveForceMax = configValue.moveForce;
tank.throttle = configValue.moveSpeed;
tank.leftThrottle = configValue.moveSpeed;
tank.rightThrottle = configValue.moveSpeed;
tank.searchRange = configValue.viewRange;
tank.viewDistance = configValue.viewRange;
if (configValue.lootPreset == "") tank.maxCratesToSpawn = configValue.crateAmount;
else {
int crateAmount = Core.Random.Range(config.lootPresets[configValue.lootPreset].minCrates, config.lootPresets[configValue.lootPreset].maxCrates + 1);
tank.maxCratesToSpawn = crateAmount;
}
tank.coaxBurstLength = configValue.coaxBurstLength;
tank.coaxFireRate = configValue.coaxFireRate;
tank.coaxAimCone = configValue.coaxAimCone;
tank.bulletDamage = configValue.coaxBulletDamage;
tank._health = configValue.health;
tank._maxHealth = configValue.health;
tank.globalBroadcast = true;
if (configValue.tankScale != 1) {
timer.Once(5, () => {
if (tank != null && !tank.IsDestroyed) {
SphereEntity sphere = GameManager.server.CreateEntity("assets/prefabs/visualization/sphere.prefab", tank.transform.position) as SphereEntity;
sphere.Spawn();
tank.SetParent(sphere, true);
sphere.LerpRadiusTo(configValue.tankScale, 1000);
}
}
);
}
string markerType = configValue.markerType.ToLower();
if (markerType != "none") {
BaseEntity marker = GameManager.server.CreateEntity(markerPrefabs[markerType], tank.transform.position);
marker.SetParent(tank, true);
marker.Spawn();
marker.transform.position = startPosition;
}
if (config.enableTankInfo) {
VendingMachineMapMarker marker2 = GameManager.server.CreateEntity(markerPrefabs["vending"], tank.transform.position) as VendingMachineMapMarker;
marker2.SetParent(tank, true);
marker2.Spawn();
marker2.transform.position = startPosition;
marker2.markerShopName = string.Format(config.tankInfo, profileName.ToUpper());
}
if (config.spawns[routeName].announceSpawn) foreach (var player in BasePlayer.activePlayerList) SendReply(player, Lang("BradleySpawned", player.UserIDString, profileName, PhoneController.PositionToGridCoord(startPosition)));
tank.SendNetworkUpdate();
}
private void TryPurchaseTank(BasePlayer player, string profile, bool forceBuy = false) {
if (!config.purchases.ContainsKey(profile)) return;
if (!forceBuy) {
string date = DateTime.Now.ToShortDateString();
limitData.limits.TryAdd(date, new Dictionary<ulong, Dictionary<string, int>>());
limitData.limits[date].TryAdd(player.userID, new Dictionary<string, int>());
limitData.limits[date][player.userID].TryAdd(profile, 0);
if (!permission.UserHasPermission(player.UserIDString, "roadbradley.bypass")) {
if (purchaseCooldowns.ContainsKey(player.userID) && purchaseCooldowns[player.userID].ContainsKey(profile)) return;
if (currentBradleyOwners.ContainsValue(player.userID)) return;
if (config.purchases[profile].maxDaily != 0 && limitData.limits[date][player.userID][profile] >= config.purchases[profile].maxDaily) return;
}
}
string targetRoad = "";
Dictionary<string, float> nearestRoad = new Dictionary<string, float>();
foreach (var road in roads) if (road.Value.isPurchasable && !road.Value.isNowOccupied && road.Value.checkpoints.Any()) nearestRoad.Add(road.Key, Vector3.Distance(player.transform.position, road.Value.checkpoints[0]));
foreach (var road in nearestRoad.OrderBy(x => x.Value)) {
targetRoad = road.Key;
break;
}
CuiHelper.DestroyUi(player, "RoadBradleyUI_Main");
if (targetRoad == "") {
SendReply(player, Lang("NoFreeRoad", player.UserIDString));
return;
}
if (config.maxBradleys > 0) {
int spawnedPurchasedTanks = 0;
foreach (var apc in roadBradleys) {
if (apc == null) continue;
RoadBradleyController apcCont = apc.GetComponent<RoadBradleyController>();
if (apcCont != null && apcCont.ownerId != 0) spawnedPurchasedTanks++;
}
if (spawnedPurchasedTanks > config.maxBradleys) {
SendReply(player, Lang("TooManyBradleys", player.UserIDString));
return;
}
}
if (!forceBuy) {
if (!permission.UserHasPermission(player.UserIDString, "roadbradley.bypass") && !TakeResources(player, config.purchases[profile].requiredItems)) return;
string date = DateTime.Now.ToShortDateString();
limitData.limits[date][player.userID][profile]++;
purchaseCooldowns.TryAdd(player.userID, new Dictionary<string, int>());
purchaseCooldowns[player.userID].TryAdd(profile, 0);
purchaseCooldowns[player.userID][profile] = config.purchases[profile].cooldown;
}
ProfileConfig configValue = config.profiles[profile];
int spawnPoint = 0;
if (config.randomizeSpawn) spawnPoint = Core.Random.Range(0, roads[targetRoad].checkpoints.Count - 1);
Vector3 startPosition = roads[targetRoad].checkpoints[spawnPoint] + new Vector3(0, 0.1f, 0);
BradleyAPC tank = GameManager.server.CreateEntity("assets/prefabs/npc/m2bradley/bradleyapc.prefab", startPosition) as BradleyAPC;
Puts($"Tank '{profile}' purchased by '{player.displayName}' ({player.UserIDString}) has spawned on road '{targetRoad}'!");
tank.Spawn();
Interface.Call("OnRoadBradleyPurchased", player, tank, profile, targetRoad);
roads[targetRoad].isNowOccupied = true;
tank.gameObject.AddComponent<RoadBradleyController>();
RoadBradleyController controller = tank.GetComponent<RoadBradleyController>();
controller.roadInfo = roads[targetRoad];
controller.profile = profile;
controller.ownerId = player.userID;
if (configValue.disarmEnabled) {
GameObject go = new GameObject();
go.AddComponent<BradleyDisarmZone>();
BradleyDisarmZone bradleyDisarm = go.GetComponent<BradleyDisarmZone>();
go.transform.SetParent(tank.transform, true);
bradleyDisarm.SetupZone(tank);
}
roadBradleys.Add(tank);
currentBradleyOwners.Add(tank, player.userID);
bradleyTimers.TryAdd(tank, config.purchases[profile].killTime + config.purchases[profile].comingTime);
tank.currentPath = new List<Vector3>(roads[targetRoad].checkpoints);
tank.currentPathIndex = spawnPoint;
tank.moveForceMax = configValue.moveForce;
tank.throttle = configValue.moveSpeed;
tank.leftThrottle = configValue.moveSpeed;
tank.rightThrottle = configValue.moveSpeed;
tank.searchRange = configValue.viewRange;
tank.viewDistance = configValue.viewRange;
if (configValue.lootPreset == "") tank.maxCratesToSpawn = configValue.crateAmount;
else {
int crateAmount = Core.Random.Range(config.lootPresets[configValue.lootPreset].minCrates, config.lootPresets[configValue.lootPreset].maxCrates + 1);
tank.maxCratesToSpawn = crateAmount;
}
tank.coaxBurstLength = configValue.coaxBurstLength;
tank.coaxFireRate = configValue.coaxFireRate;
tank.coaxAimCone = configValue.coaxAimCone;
tank.bulletDamage = configValue.coaxBulletDamage;
tank._health = configValue.health;
tank._maxHealth = configValue.health;
tank.globalBroadcast = true;
if (configValue.tankScale != 1) {
timer.Once(5, () => {
if (tank != null && !tank.IsDestroyed) {
SphereEntity sphere = GameManager.server.CreateEntity("assets/prefabs/visualization/sphere.prefab", tank.transform.position) as SphereEntity;
sphere.Spawn();
tank.SetParent(sphere, true);
sphere.LerpRadiusTo(configValue.tankScale, 1000);
}
}
);
}
string markerType = configValue.markerType.ToLower();
if (markerType != "none") {
BaseEntity marker = GameManager.server.CreateEntity(markerPrefabs[markerType], tank.transform.position);
marker.Spawn();
marker.SetParent(tank, true);
}
if (config.enablePurchasedTankInfo) {
VendingMachineMapMarker marker2 = GameManager.server.CreateEntity(markerPrefabs["vending"], startPosition) as VendingMachineMapMarker;
marker2.Spawn();
marker2.SetParent(tank, true);
marker2.markerShopName = string.Format(config.purchasedTankInfo, profile.ToUpper(), player.displayName);
}
tank.SendNetworkUpdate();
SendReply(player, Lang("BradleyPurchased", player.UserIDString, profile, PhoneController.PositionToGridCoord(startPosition), CalculateTime(config.purchases[profile].killTime), CalculateTime(config.purchases[profile].comingTime)));
if (killTimer == null) killTimer = timer.Every(1, () => CheckTimers());
}
private void CheckTimers() {
if (!bradleyTimers.Any() && !purchaseCooldowns.Any()) {
killTimer.Destroy();
killTimer = null;
return;
}
foreach (var tank in bradleyTimers.Keys.ToList()) {
if (tank == null) {
bradleyTimers.Remove(tank);
continue;
}
RoadBradleyController controller = tank.GetComponent<RoadBradleyController>();
if (controller.ownerId == 0) continue;
foreach (var fighter in controller.targets) {
BasePlayer player = BasePlayer.FindByID(fighter);
if (player == null) continue;
UpdateTimer(player, tank);
}
bradleyTimers[tank]--;
if (bradleyTimers[tank] <= 0) {
BasePlayer player = BasePlayer.FindByID(controller.ownerId);
if (player != null) {
SendReply(player, Lang("FightLost", player.UserIDString));
CuiHelper.DestroyUi(player, "RoadBradleyUI_Timer");
}
foreach (var fighter in controller.targets) {
if (fighter == controller.ownerId) continue;
player = BasePlayer.FindByID(fighter);
if (player != null) {
SendReply(player, Lang("FightLost", player.UserIDString));
CuiHelper.DestroyUi(player, "RoadBradleyUI_Timer");
}
}
Effect.server.Run("assets/prefabs/weapons/beancan grenade/effects/beancan_grenade_explosion.prefab", tank.transform.position);
tank.Kill();
bradleyTimers.Remove(tank);
}
}
foreach (var timer1 in purchaseCooldowns.ToList()) {
foreach (var timer2 in timer1.Value.ToList()) {
purchaseCooldowns[timer1.Key][timer2.Key]--;
if (purchaseCooldowns[timer1.Key][timer2.Key] <= 0) {
purchaseCooldowns[timer1.Key].Remove(timer2.Key);
if (!purchaseCooldowns[timer1.Key].Any()) purchaseCooldowns.Remove(timer1.Key);
}
}
}
}
private static string CalculateTime(int time) {
int seconds = time % 60;
int minutes = (time - seconds) / 60;
int remaingingMinutes = minutes % 60;
int hours = (minutes - remaingingMinutes) / 60;
if (hours == 0) return $"{minutes}m {seconds}s";
else return $"{hours}h {remaingingMinutes}m";
}
private object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info) {
if (entity is BradleyAPC) {
if (info.WeaponPrefab != null && info.WeaponPrefab.ShortPrefabName == "rocket_mlrs") return false;
BradleyAPC apc = entity as BradleyAPC;
BasePlayer player = info.InitiatorPlayer;
if (info == null || player == null) return null;
RoadBradleyController controller = apc.GetComponent<RoadBradleyController>();
if (controller == null) return null;
if (controller.targets.Contains(player.userID)) {
controller.damageDealt.TryAdd(player.userID, 0);
controller.damageDealt[player.userID] += info.damageTypes.Total();
controller.CheckMLRSSpawn();
return null;
}
if (controller.ownerId != 0 && config.purchases[controller.profile].ownerDamage) {
if ((controller.ownerId == player.userID) || (config.useFriends && Friends != null && AreFriends(controller.ownerId, player.userID)) || (config.useClans && Clans != null && Clans.Call<bool>("IsClanMember", controller.ownerId.ToString(), player.userID.ToString())) || (config.useTeams && player.Team != null && player.Team.members.Contains(controller.ownerId))) {
controller.targets.Add(player.userID);
if (!controller.damageDealt.Any()) CheckFirstDamage(apc, controller);
controller.damageDealt.TryAdd(player.userID, 0);
controller.damageDealt[player.userID] += info.damageTypes.Total();
controller.CheckMLRSSpawn();
}
else {
SendReply(player, Lang("CannotDamage", player.UserIDString));
return false;
}
}
else {
if (!controller.damageDealt.Any()) CheckFirstDamage(apc, controller);
controller.targets.Add(player.userID);
controller.damageDealt.TryAdd(player.userID, 0);
controller.damageDealt[player.userID] += info.damageTypes.Total();
controller.CheckMLRSSpawn();
return null;
}
}
else if (config.callerDamage && info.WeaponPrefab != null && info.WeaponPrefab.ShortPrefabName == "maincannonshell") {
float nearest = 10000;
BradleyAPC apc = null;
RoadBradleyController controller;
/*if (entity is BasePlayer)
{
foreach (var bradley in roadBradleys)
{
float distance = Vector3.Distance(entity.transform.position, bradley.transform.position);
if (distance < nearest)
{
nearest = distance;
apc = bradley;
}
}
if (apc == null) return null;
controller = apc.GetComponent<RoadBradleyController>();
if (!controller.targets.Contains((entity as BasePlayer).userID))
return false;
else
return null;
}*/
if (entity.OwnerID == 0) return null;
if (entity is BasePlayer) return null;
foreach (var bradley in roadBradleys) {
float distance = Vector3.Distance(entity.transform.position, bradley.transform.position);
if (distance < nearest) {
nearest = distance;
apc = bradley;
}
}
if (apc == null) return null;
controller = apc.GetComponent<RoadBradleyController>();
if (controller == null || controller.ownerId == 0) return null;
if (config.callerTeamCheck) {
RelationshipManager.PlayerTeam team = RelationshipManager.ServerInstance.FindPlayersTeam(entity.OwnerID);
if (team != null && team.members.Contains(controller.ownerId)) return null;
}
if (controller.targets.Contains(entity.OwnerID)) return null;
BasePlayer player = BasePlayer.FindByID(controller.ownerId);
controller.damageAuths++;
if (controller.damageAuths % config.damageAuthsRemind == 0 && controller.damageAuths < config.damageAuths) SendReply(player, Lang("FightUnauthorizedInfo", player.UserIDString, controller.damageAuths, config.damageAuths));
if (controller.damageAuths == config.damageAuths) {
bradleyTimers[apc] = 0;
SendReply(player, Lang("FightLostUnauthorized", player.UserIDString, config.damageAuths));
}
return false;
}
else if (info.WeaponPrefab != null && info.WeaponPrefab.ShortPrefabName == "rocket_mlrs") {
if (!roadBradleys.Any()) return null;
DecayEntity hitEntity = entity as DecayEntity;
if (hitEntity == null) return null;
BradleyAPC closest = null;
float distance = float.MaxValue;
foreach (var brad in roadBradleys) {
float bradDistance = Vector3.Distance(entity.transform.position, brad.transform.position);
if (bradDistance < distance) {
distance = bradDistance;
closest = brad;
}
}
if (distance > 100 && Vector3.Distance(tankDeathInfo.tankDeathLoc, entity.transform.position) < 100) {
if (hitEntity.OwnerID == tankDeathInfo.tankDeathOwner) return null;
BuildingPrivlidge cupboard = hitEntity.GetBuildingPrivilege();
if (cupboard == null) return null;
if (!cupboard.IsAuthed(tankDeathInfo.tankDeathOwner)) return false;
return null;
}
else if (distance > 150) return null;
RoadBradleyController controller = closest.GetComponent<RoadBradleyController>();
if (entity is BasePlayer && !controller.targets.Contains((entity as BasePlayer).userID)) return false;
if (controller.ownerId == 0) return null;
if (hitEntity.OwnerID == controller.ownerId) return null;
BuildingPrivlidge tc = hitEntity.GetBuildingPrivilege();
if (tc == null) return null;
if (!tc.IsAuthed(controller.ownerId)) return false;
}
return null;
}
private object CanBeWounded(BasePlayer player, HitInfo info) {
if (info == null) return null;
return OnKilledByBradley(player, info, true);
}
private object OnPlayerDeath(BasePlayer player, HitInfo info) {
if (player is NPCPlayer) return null;
foreach (var tank in roadBradleys) {
RoadBradleyController controller = tank?.GetComponent<RoadBradleyController>();
if (controller == null) continue;
controller.targets.Remove(player.userID);
}
if (config.enabledBags) return OnKilledByBradley(player, info);
else return null;
}
private void OnEntityKill(BradleyAPC apc) {
if (apc == null || apc.net == null) return;
RoadBradleyController controller = apc.GetComponent<RoadBradleyController>();
if (controller == null) return;
if (controller.ownerId == 0) {
int waitTime = Core.Random.Range(config.spawns[controller.roadInfo.routeName].respawnTime - config.spawns[controller.roadInfo.routeName].respawnTimeRandomize, config.spawns[controller.roadInfo.routeName].respawnTime + config.spawns[controller.roadInfo.routeName].respawnTimeRandomize);
timer.Once(waitTime, () => SpawnDefaultRouteTank(controller.roadInfo.routeName));
}
controller.roadInfo.isNowOccupied = false;
SphereEntity sphere = apc.GetParentEntity() as SphereEntity;
if (sphere != null && !sphere.IsDestroyed) sphere.Kill();
roadBradleys.Remove(apc);
bradleyTimers.Remove(apc);
currentBradleyOwners.Remove(apc);
}
private void OnEntityKill(HelicopterDebris debris) {
SphereEntity sphere = debris.GetParentEntity() as SphereEntity;
if (sphere != null && !sphere.IsDestroyed) sphere.Kill();
}
private void OnEntitySpawned(HelicopterDebris debris) {
if (tankDeathInfo.tankDeathProfile == string.Empty) return;
if (debris.ShortPrefabName == "servergibs_bradley") {
NextTick(() => {
if (debris == null || debris.IsDestroyed) return;
if (config.profiles[tankDeathInfo.tankDeathProfile].gibsScale) {
float scale = config.profiles[tankDeathInfo.tankDeathProfile].tankScale;
if (scale != 1) {
Vector3 pos = debris.transform.position;
SphereEntity sphere = GameManager.server.CreateEntity("assets/prefabs/visualization/sphere.prefab", debris.transform.position) as SphereEntity;
sphere.Spawn();
debris.SetParent(sphere, true);
sphere.LerpRadiusTo(scale, 10000);
}
}
debris.InitializeHealth(config.profiles[tankDeathInfo.tankDeathProfile].gibsHealth, config.profiles[tankDeathInfo.tankDeathProfile].gibsHealt
// ... [Code preview truncated due to length. Full source in attached file] ...