C#:
using Newtonsoft.Json;
using Oxide.Game.Rust.Cui;
using Facepunch.Extend;
using Newtonsoft.Json.Converters;
using UnityEngine;
using Oxide.Core.Libraries.Covalence;
using Oxide.Plugins.BasementMethods;
using Oxide.Core;
using System.Collections.Generic;
using System.Linq;
using Oxide.Core.Plugins;
using System;
namespace Oxide.Plugins {
[Info("Basements", "mr01sam", "1.0.4")]
[Description("Allows players to build basements underneath their base.")]
public partial class Basements : CovalencePlugin {
public static Basements INSTANCE;
public const string PermissionBuild = "basements.build";
public const string PermissionAdmin = "basements.admin";
public const string PermissionFree = "basements.free";
public bool allowDestroy = false;
public bool newSaveLoaded = false;
public static bool debugging = false;
public static void DEBUG(string message) {
if (debugging) {
INSTANCE?.Puts(message);
}
}
void Init() {
INSTANCE = this;
UnsubscribeAll(nameof(OnLootEntity), nameof(OnLootEntityEnd), nameof(OnActiveItemChanged), nameof(OnStructureRepair), nameof(OnEntitySpawned), nameof(OnEntityKill), nameof(OnEntityDeath), nameof(OnStructureDemolish), nameof(OnStructureRotate), nameof(OnStructureUpgrade), nameof(OnEntityTakeDamage), nameof(CanDemolish), nameof(OnDoorOpened), nameof(CanBuild), nameof(OnBuildingSplit), nameof(OnBuildingMerge), nameof(OnPlayerViolation), nameof(OnHammerHit), nameof(CanLock), nameof(CanUnlock), nameof(CanChangeCode) );
}
void OnServerInitialized() {
// Prevent auto suicide if underground
ConsoleSystem.Run(ConsoleSystem.Option.Server.Quiet(), "antihack.terrain_protection 0");
if (!permission.PermissionExists(PermissionFree)) {
permission.RegisterPermission(PermissionFree, this);
}
if (!newSaveLoaded) {
LoadData();
}
SubscribeAll();
if (!config.SyncLocksOnBasementEntrances) {
Unsubscribe(nameof(CanLock));
Unsubscribe(nameof(CanUnlock));
Unsubscribe(nameof(CanChangeCode));
}
}
void Unload() {
SaveData();
foreach (var basePlayer in BasePlayer.activePlayerList) {
if (basePlayer == null) {
continue;
}
DestroyToolcupboardButton(basePlayer);
DestroyPlacementBanner(basePlayer);
DestroyInsufficientResources(basePlayer);
}
}
void OnNewSave(string strFilename) {
Puts("Map wipe detected, current data files will be cleared upon save.");
newSaveLoaded = true;
}
void OnServerSave() {
SaveData();
}
void SaveData() {
Interface.Oxide.DataFileSystem.WriteObject($"Basements/BasementEntities", BasementEntities);
Interface.Oxide.DataFileSystem.WriteObject($"Basements/BasementEntrances", BasementEntrances);
Interface.Oxide.DataFileSystem.WriteObject($"Basements/BasementStructures", BasementStructures);
}
void LoadData() {
BasementEntities = Interface.Oxide.DataFileSystem.ReadObject<HashSet<ulong>>($"Basements/BasementEntities") ?? new HashSet<ulong>();
BasementEntrances = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<ulong, BasementEntrance>>($"Basements/BasementEntrances") ?? new Dictionary<ulong, BasementEntrance>();
BasementStructures = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<uint, BasementStructure>>($"Basements/BasementStructures") ?? new Dictionary<uint, BasementStructure>();
Puts($"Loaded {BasementStructures.Count} structures {BasementEntrances.Count} entrances {BasementEntities.Count} entities");
}
private List<string> _subscriptions = new List<string>();
public void UnsubscribeAll(params string[] methods) {
_subscriptions.AddRange(methods);
foreach (var method in _subscriptions) {
Unsubscribe(method);
}
}
public void SubscribeAll() {
foreach (var method in _subscriptions) {
switch (method) {
default : Subscribe(method);
break;
}
}
}
}
}
namespace Oxide.Plugins {
public partial class Basements {
[HookMethod("IsBasementEntity")]
private bool API_IsBasementEntity(ulong entityId) {
return BasementEntities.Contains(entityId);
}
[HookMethod("GetBasementBuildingIds")]
private uint[] API_GetBasementBuildingIds(uint surfaceBuildingId) {
var surfaceBuilding = BuildingManager.server.GetBuilding(surfaceBuildingId);
if (surfaceBuilding == null) {
return Array.Empty<uint>();
}
var list = GetBasementsConnectedToSurfaceBuilding(surfaceBuilding);
var result = new uint[list.Count];
for (int i = 0; i < list.Count; i++) {
result[i] = list[i].BasementBuildingId;
}
return result;
}
[HookMethod("GetSurfaceBuildingIds")]
private uint[] API_GetSurfaceBuildingIds(uint basementBuildingId) {
var basement = BasementStructures.GetValueOrDefault(basementBuildingId);
if (basement == null) {
return Array.Empty<uint>();
}
return basement.SurfaceBuildingIds.ToArray();
}
}
}
namespace Oxide.Plugins {
public partial class Basements {
// Key is basement building id
public Dictionary<uint, BasementStructure> BasementStructures = new Dictionary<uint, BasementStructure>();
// Key is hatch id
public Dictionary<ulong, BasementEntrance> BasementEntrances = new Dictionary<ulong, BasementEntrance>();
// Key is entity id
public HashSet<ulong> BasementEntities = new HashSet<ulong>();
#region Models
public class BasementEntrance {
public ulong HatchId;
public bool IsDownHatch;
public ulong ConnectedHatchId;
[JsonIgnore]
private Door _baseEntity = null;
[JsonIgnore]
public Door BaseEntity {
get {
if (_baseEntity == null) {
_baseEntity = FindBaseEntity(HatchId) as Door;
}
return _baseEntity;
}
}
[JsonIgnore]
private BasementEntrance _connectedHatch = null;
[JsonIgnore]
public BasementEntrance ConnectedHatch {
get {
if (_connectedHatch == null) {
var door = FindBaseEntity(ConnectedHatchId) as Door;
_connectedHatch = door == null ? null : INSTANCE.BasementEntrances.GetValueOrDefault(door.net.ID.Value);
}
return _connectedHatch;
}
}
[JsonIgnore]
private BuildingBlock _parentBuildingBlock = null;
[JsonIgnore]
public BuildingBlock ParentBuildingBlock {
get {
if (_parentBuildingBlock == null) {
_parentBuildingBlock = BaseEntity?.GetParentEntity() as BuildingBlock;
}
return _parentBuildingBlock;
}
}
[JsonIgnore]
public uint BasementBuildingId => (IsDownHatch ? ConnectedHatch?.ParentBuildingBlock?.buildingID : ParentBuildingBlock?.buildingID) ?? 0;
[JsonIgnore]
public BuildingManager.Building BasementBuilding => BuildingManager.server.GetBuilding(BasementBuildingId);
[JsonIgnore]
public BasementStructure Basement => INSTANCE.BasementStructures.GetValueOrDefault(BasementBuildingId);
[JsonIgnore]
public uint SurfaceBuildingId => (IsDownHatch ? ParentBuildingBlock?.buildingID : ConnectedHatch?.ParentBuildingBlock?.buildingID) ?? 0;
[JsonIgnore]
public BuildingManager.Building SurfaceBuilding => BuildingManager.server.GetBuilding(SurfaceBuildingId);
public void ClearCache() {
_baseEntity = null;
_connectedHatch = null;
_parentBuildingBlock = null;
}
}
public class BasementStructure {
public uint BasementBuildingId;
public float Elevation;
public bool ShownTip;
[JsonIgnore]
public BuildingManager.Building BasementBuilding => BuildingManager.server.GetBuilding(BasementBuildingId);
[JsonIgnore]
public IEnumerable<BuildingBlock> BasementBlocks => BasementBuilding?.buildingBlocks.Where(x => x.IsBasementEntity()) ?? Array.Empty<BuildingBlock>();
[JsonIgnore]
private List<BasementEntrance> _entrances = null;
[JsonIgnore]
public int EntrancePairCount = 0;
[JsonIgnore]
public IEnumerable<BasementEntrance> Entrances {
get {
if (_entrances == null) {
EntrancePairCount = 0;
_entrances = new List<BasementEntrance>();
if (BasementBuilding != null) {
foreach (var decay in BasementBuilding.decayEntities) {
if (decay != null && decay.IsBasementEntrance()) {
var entrance = INSTANCE.BasementEntrances[decay.net.ID.Value];
_entrances.Add(entrance);
EntrancePairCount++;
var connectedEntrance = entrance.ConnectedHatch;
if (connectedEntrance != null) {
_entrances.Add(connectedEntrance);
}
}
}
}
}
return _entrances;
}
}
[JsonIgnore]
public IEnumerable<uint> SurfaceBuildingIds {
get {
HashSet<uint> ids = new HashSet<uint>();
foreach (var entrance in Entrances) {
if (entrance.IsDownHatch) {
ids.Add(entrance.SurfaceBuildingId);
}
}
return ids;
}
}
[JsonIgnore]
public BuildingManager.Building FirstSurfaceBuilding => SurfaceBuildingIds.Any() == false ? null : BuildingManager.server.GetBuilding(SurfaceBuildingIds.First());
public void ClearCache() {
_entrances = null;
}
}
#endregion
#region Helper Methods
public float GetProjectedBasementElevation(BuildingBlock start) {
var foundations = GetFoundationsForBasement(start, BuildingGrade.Enum.Stone);
// Find lowest foundation
BuildingBlock lowestFoundation = null;
foreach (var foundation in foundations) {
if (lowestFoundation == null || lowestFoundation.transform.position.y > foundation.transform.position.y) {
lowestFoundation = foundation;
}
}
var diff = (int)Math.Floor(start.transform.position.y - lowestFoundation.transform.position.y);
var iseven = diff % 3 == 0;
var elevation = lowestFoundation.transform.position.y + -6f;
if (!iseven) {
elevation -= 1.5f;
}
elevation -= 3f;
// Uncomment if I ever want to disallow water basements
//if (WaterLevel.Test(new Vector3(start.transform.position.x, elevation, start.transform.position.z), false, false))
//{
// // If its in water, make it under the surface
// elevation = TerrainMeta.HeightMap.GetHeight(start.transform.position) - 6f;
//}
return elevation;
}
private List<BasementEntrance> _returnGetEntrancesForSurfaceBuilding = new List<BasementEntrance>();
public List<BasementEntrance> GetDownEntrancesForSurfaceBuilding(BuildingManager.Building surfaceBuilding) {
_returnGetEntrancesForSurfaceBuilding.Clear();
if (surfaceBuilding == null || surfaceBuilding.buildingBlocks == null) {
return _returnGetEntrancesForSurfaceBuilding;
}
foreach (var block in surfaceBuilding.buildingBlocks) {
if (block == null) {
continue;
}
if (block.IsFoundation() && block.children != null) {
Door childHatch = null;
foreach (var child in block.children) {
if (child is Door door && door.IsBasementEntrance()) {
childHatch = door;
break;
}
}
if (childHatch != null && childHatch.net != null) {
var entrance = INSTANCE.BasementEntrances[childHatch.net.ID.Value];
if (entrance != null) {
_returnGetEntrancesForSurfaceBuilding.Add(entrance);
}
}
}
}
return _returnGetEntrancesForSurfaceBuilding;
}
private List<BasementStructure> _returnGetBasementsConnectedToSurfaceBuilding = new List<BasementStructure>();
public List<BasementStructure> GetBasementsConnectedToSurfaceBuilding(BuildingManager.Building surfaceBuilding) {
_returnGetBasementsConnectedToSurfaceBuilding.Clear();
HashSet<uint> seen = new HashSet<uint>();
foreach (var entrance in GetDownEntrancesForSurfaceBuilding(surfaceBuilding)) {
var id = entrance.BasementBuildingId;
if (id == 0 || !seen.Add(id)) continue;
var basement = BasementStructures.GetValueOrDefault(id);
if (basement != null) {
_returnGetBasementsConnectedToSurfaceBuilding.Add(basement);
}
}
return _returnGetBasementsConnectedToSurfaceBuilding;
}
#endregion
#region Main Methods
public BasementStructure SpawnBasement(BuildingBlock surfaceFoundation) {
if (surfaceFoundation == null) {
return null;
}
var building = surfaceFoundation.GetBuilding();
var basements = GetBasementsConnectedToSurfaceBuilding(building);
BasementStructure basement = null;
if (basements.Count != 0) {
// Try to get existing basement that exists directly under this foundation
basement = basements.Select(x => x.BasementBuilding?.GetBasementFoundationFromSurface(surfaceFoundation)).FirstOrDefault(x => x != null)?.GetBasement();
}
if (basement == null) {
// Spawn new basement because there isn't one under this foundation
basement = new BasementStructure {
// If there are other basements, we want the elevations to match
Elevation = basements.Count == 0 ? GetProjectedBasementElevation(surfaceFoundation) : basements.First().Elevation, BasementBuildingId = BuildingManager.server.NewBuildingID()
}
;
// Spawn basement cell at elevation
var basementFoundation = Spawn1x1BasementCell(basement, surfaceFoundation);
var effect = new Effect(Effects.PromoteStone, surfaceFoundation, 0, Vector3.zero, Vector3.zero);
effect.Play();
timer.Repeat(0.5f, 2, () => {
effect.Play();
}
);
BasementStructures[basement.BasementBuildingId] = basement;
}
// Spawn ladder hatches
timer.In(1f, () => // Allow animations to finish
{
if (surfaceFoundation == null) {
return;
}
SpawnLadderHatchPair(basement, surfaceFoundation, out _, out _);
basement?.ClearCache();
}
);
return basement;
}
#endregion
public void SpawnMissingExteriorWalls(BasementStructure basement) {
if (basement == null) {
return;
}
foreach (var block in basement.BasementBlocks) {
if (block.IsNetNull() || !block.IsFoundation() || !block.IsBasementEntity()) {
continue;
}
var triangle = block.IsTriangle();
for (int d = 1; d <= (triangle ? 3 : 4);
d++) {
// check if theres a foundation in that direction
var foundation = block.GetLinkedFoundation(d);
if (foundation == null) {
// spawn a wall if theres no foundation and no wall
var wall = block.GetLinkedWall(d, out Vector3 pos, out Vector3 rot);
if (wall == null) {
SpawnBasementBlock(basement, Prefabs.Wall, block, position : block.transform.position, rotation : block.transform.rotation, localPosition : pos, localRotation : rot, ownerid : block.OwnerID );
}
}
}
// check if theres a ceiling
var ceiling = block.GetCeilingAboveFoundation();
if (ceiling == null) {
SpawnBasementBlock(basement, triangle ? Prefabs.TriangleFloor : Prefabs.Floor, block, position : block.transform.position, rotation : block.transform.rotation, localPosition : new Vector3(0, 3f, 0), ownerid : block.OwnerID );
}
}
}
public void DestroyBasementCell(BuildingBlock basementFoundation) {
if (basementFoundation != null) {
TeleportBasementContents(basementFoundation);
var ceiling = basementFoundation.GetCeilingAboveFoundation();
KillBasementEntity(ceiling);
for (int d = 1; d <= 4; d++) {
var wall = basementFoundation.GetLinkedWall(d, out Vector3 pos, out Vector3 rot);
KillBasementEntity(wall);
}
KillBasementEntity(basementFoundation);
}
}
private BuildingBlock GetSurfaceFoundation(BuildingBlock basementFoundation, BuildingManager.Building surfaceBuilding) {
if (basementFoundation == null) {
return null;
}
foreach (var block in surfaceBuilding?.buildingBlocks) {
if (block == null || !block.ShortPrefabName.Contains("foundation") || block.IsBasementEntity()) {
continue;
}
if (EqualsPosition2D(basementFoundation.WorldSpaceBounds().ToBounds().center, block.WorldSpaceBounds().ToBounds().center)) {
return block;
}
}
return null;
}
private BuildingBlock GetBasementFoundation(BuildingBlock surfaceFoundation, BuildingManager.Building basementBuilding) {
if (surfaceFoundation == null) {
return null;
}
foreach (var block in basementBuilding?.buildingBlocks) {
if (block == null || !block.ShortPrefabName.Contains("foundation") || block.IsBasementEntity()) {
continue;
}
if (EqualsPosition2D(block.transform.position, surfaceFoundation.transform.position)) {
return block;
}
}
return null;
}
private BuildingBlock GetBasementFoundation(BuildingBlock surfaceFoundation) {
if (surfaceFoundation == null) {
return null;
}
return RaycastVertical(surfaceFoundation, Vector3.down, 100f, (x) => (x is BuildingBlock casted && casted.IsBasementEntity() && casted.IsFoundation())) as BuildingBlock;
}
private void DestroyBlocksAbove(BaseEntity entity, float distance) {
foreach(var hit in GetEntitiesAbove(entity, distance)) {
if (!(hit is StabilityEntity block)) {
continue;
}
hit.Kill(BaseNetworkable.DestroyMode.Gib);
}
}
List<BaseEntity> hitList = new List<BaseEntity>();
List<BaseEntity> GetEntitiesAbove(BaseEntity block, float height) {
Vector3 startPos = block.CenterPoint().Mod(y : 0.4f);
hitList.Clear();
float radius = 0.3f;
foreach (var hit in Physics.SphereCastAll(startPos, radius, Vector3.up, height)) {
hitList.Add(hit.GetEntity());
}
return hitList;
}
public void KillBasementEntity(BaseEntity entity, BasementStructure basement = null, bool gibs = false) {
if (entity == null || entity.IsDestroyed) {
return;
}
var eid = entity.net.ID.Value;
BasementEntities.Remove(eid);
destroying = true;
entity.Kill(gibs ? BaseNetworkable.DestroyMode.Gib : BaseNetworkable.DestroyMode.None);
if (entity is DecayEntity de) {
BuildingManager.server.Remove(de);
}
destroying = false;
}
public void DestroyBasementEntrancePair(BasementEntrance entrance, bool gibs = false) {
if (entrance == null) {
return;
}
var connected = entrance.ConnectedHatch;
KillBasementEntity(entrance.BaseEntity, gibs : gibs);
KillBasementEntity(connected?.BaseEntity, gibs : gibs);
BasementEntrances.Remove(entrance.HatchId);
if (connected != null) {
BasementEntrances.Remove(connected.HatchId);
}
}
public void DestroyBasement(BasementStructure basement) {
if (basement == null) {
return;
}
var buildingId = basement.BasementBuildingId;
var blocks = basement.BasementBlocks?.ToArray() ?? Array.Empty<BuildingBlock>();
basement?.ClearCache();
foreach (var entrance in basement.Entrances.ToArray()) {
DestroyBasementEntrancePair(entrance, gibs : false);
}
foreach (var block in blocks) {
if (block.IsNetNull() || !block.IsBasementEntity() || !block.IsFoundation()) {
continue;
}
// Move all entities
TeleportBasementContents(block);
}
foreach (var entity in blocks) {
if (entity.IsNetNull() || !entity.IsBasementEntity()) {
continue;
}
// Kill
try {
foreach (var child in entity.children) {
if (child == null) {
continue;
}
KillBasementEntity(child, basement);
}
KillBasementEntity(entity, basement);
}
catch (Exception) {
// Failed to kill exterior entity
}
}
BasementStructures.Remove(buildingId);
}
public int GetDirOfWall(BuildingBlock foundation, BuildingBlock targetWall) {
if (foundation == null || targetWall == null) {
return 0;
}
for(int i = 1; i <= 4; i++) {
var wall = foundation.GetLinkedWall(i, out _, out _);
if (wall == null) {
continue;
}
if (wall.EqualNetID(targetWall)) {
return i;
}
}
return 0;
}
private readonly List<BaseCombatEntity> _nearbyCombatEntities = new List<BaseCombatEntity>();
public void TeleportBasementContents(BuildingBlock basementFoundation) {
if (basementFoundation.IsFoundation()) {
_nearbyCombatEntities.Clear();
Vis.Entities(basementFoundation.transform.position, 2f, _nearbyCombatEntities);
foreach (var target in _nearbyCombatEntities) {
try {
if (target == null || target.EqualNetID(basementFoundation) || target is BuildingBlock || target.IsBasementEntity() || !IsBelowTerrain(target)) {
continue;
}
var kill = true;
if (target is BasePlayer basePlayer) {
if (basePlayer.IsGod()) {
kill = false;
}
}
try {
target.transform.position = target.transform.position.Set(y : GetTerrainHeightAtPosition(target.transform.position) + 2f);
target.SendNetworkUpdateImmediate();
}
catch (Exception) {
// Could not move entity
}
if (kill) {
target.Die();
}
}
catch (Exception) {
// Failed to kill interior entity
}
}
}
}
void SpawnLadderHatchPair(BasementStructure basement, BuildingBlock surfaceFoundation, out BasementEntrance downHatch, out BasementEntrance upHatch) {
Vector3 position = surfaceFoundation.transform.position + new Vector3(0f, 0.0f, 0f);
Quaternion rotation = surfaceFoundation.transform.rotation;
// Spawn down hatch
var downdoor = SpawnBasementBlock(basement : basement, prefab : Prefabs.LadderHatch, parent : surfaceFoundation, position : position, rotation : rotation, localRotation : new Vector3(0, 0, 180f), localPosition : new Vector3(0, 0.15f, 0f), setParent : true, buildingId : surfaceFoundation.buildingID, ownerid : surfaceFoundation.OwnerID) as Door;
downHatch = new BasementEntrance {
IsDownHatch = true, HatchId = downdoor.net.ID.Value
}
;
BasementEntrances[downHatch.HatchId] = downHatch;
// Spawn up hatch
var entitiesInRadius = GetEntitiesInRadius<BuildingBlock>(surfaceFoundation.transform.position.Set(y : basement.Elevation + 3f), 0.05f);
var ceiling = entitiesInRadius.FirstOrDefault(x => x.ShortPrefabName.Contains("floor"));
Pool<BuildingBlock>.Recycle(entitiesInRadius);
var updoor= SpawnBasementBlock(basement : basement, prefab : Prefabs.LadderHatch, parent : ceiling, position : ceiling.transform.position.Mod(y : -0.15f), rotation : ceiling.transform.rotation, setParent : true, ownerid : surfaceFoundation.OwnerID) as Door;
upHatch = new BasementEntrance {
IsDownHatch = false, HatchId = updoor.net.ID.Value
}
;
BasementEntrances[upHatch.HatchId] = upHatch;
// Link hatches
downHatch.ConnectedHatchId = upHatch.HatchId;
upHatch.ConnectedHatchId = downHatch.HatchId;
var effect = new Effect(Effects.PlaceFrame, downdoor, 0, Vector3.zero, Vector3.zero);
effect.Play();
}
public BuildingBlock Spawn1x1BasementCell(BasementStructure basement, BuildingBlock surfaceFoundation) {
var foundation = GetBasementFoundation(surfaceFoundation);
if (foundation == null) {
foundation = SpawnBasementBlock(basement, Prefabs.Foundation, surfaceFoundation, ownerid : surfaceFoundation.OwnerID) as BuildingBlock;
}
// Spawn walls if there isnt one
for (int d = 1; d <= 4; d++) {
// check if theres a foundation in that direction
var adjFoundation = foundation.GetLinkedFoundation(d);
if (adjFoundation == null) {
// spawn a wall if theres no foundation and no wall
var wall = foundation.GetLinkedWall(d, out Vector3 pos, out Vector3 rot);
if (wall == null) {
SpawnBasementBlock(basement, Prefabs.Wall, foundation, position : foundation.transform.position, rotation : foundation.transform.rotation, localPosition : pos, localRotation : rot, ownerid : foundation.OwnerID );
}
}
else if (adjFoundation != null) {
// remove the wall in this direction if its a basement entity
var wall = foundation.GetLinkedWall(d, out Vector3 pos, out Vector3 rot);
if (wall != null && wall.IsBasementEntity()) {
KillBasementEntity(wall, basement, gibs : true);
}
}
}
var ceiling = foundation.GetCeilingAboveFoundation();
if (ceiling == null) {
SpawnBasementBlock(basement, Prefabs.Floor, surfaceFoundation, localPosition : new Vector3(0f, 3f, 0f), localRotation : new Vector3(0, 0, 0), ownerid : foundation.OwnerID);
}
return foundation;
}
public void Spawn1x1BasementCellTriangle(BasementStructure basement, BuildingBlock surfaceFoundation) {
var foundation = GetBasementFoundation(surfaceFoundation);
if (foundation == null) {
foundation = SpawnBasementBlock(basement, Prefabs.TriangleFoundation, surfaceFoundation) as BuildingBlock;
}
// Spawn walls if there isnt one
for (int d = 1; d <= 3; d++) {
// check if theres a foundation in that direction
var adjFoundation = foundation.GetLinkedFoundation(d);
if (adjFoundation == null) {
// spawn a wall if theres no foundation and no wall
var wall = foundation.GetLinkedWall(d, out Vector3 pos, out Vector3 rot);
if (wall == null) {
SpawnBasementBlock(basement, Prefabs.Wall, foundation, position : foundation.transform.position, rotation : foundation.transform.rotation, localPosition : pos, localRotation : rot, ownerid : foundation.OwnerID );
}
}
}
var ceiling = foundation.GetCeilingAboveFoundation();
if (ceiling == null) {
SpawnBasementBlock(basement, Prefabs.TriangleFloor, surfaceFoundation, localPosition : new Vector3(0f, 3f, 0f), ownerid : foundation.OwnerID);
}
}
public BaseEntity SpawnBasementBlock(BasementStructure basement, string prefab, StabilityEntity parent, Vector3? position = null, Quaternion? rotation = null, Vector3? localPosition = null, Vector3? localRotation = null, bool setParent = false, bool applyGrade = true, ulong ownerid = 0, uint? buildingId = null) {
var entity = GameManager.server.CreateEntity(prefab, position ?? parent.transform.position.Set(y : basement.Elevation), rotation ?? parent.transform.rotation) as StabilityEntity;
if (localPosition != null) {
entity.transform.Translate(localPosition.Value);
}
if (localRotation != null) {
entity.transform.Rotate(localRotation.Value);
}
if (entity is BuildingBlock block) {
block.blockDefinition = PrefabAttribute.server.Find<Construction>(entity.prefabID);
if (applyGrade) {
block.grade = config.BasementBuildingGrade;
}
else {
block.grade = BuildingGrade.Enum.Wood;
}
}
entity.grounded = true;
entity.OwnerID = ownerid;
entity.AttachToBuilding(buildingId == null ? basement.BasementBuildingId : buildingId.Value);
entity.Spawn();
entity.Heal(10000f);
if (setParent) {
entity.SetParent(parent, worldPositionStays : true, sendImmediate : true);
}
BasementEntities.Add(entity.net.ID.Value);
return entity;
}
public HashSet<ulong> CachedEntitiesBelowTerrain = new HashSet<ulong>();
public bool IsBelowTerrain(BaseEntity entity) {
if (entity == null || entity.net == null) {
return false;
}
if (CachedEntitiesBelowTerrain.Contains(entity.net.ID.Value)) {
return true;
}
Vector3 position = entity.transform.position;
float terrainHeight = TerrainMeta.HeightMap.GetHeight(position);
if (position.y < terrainHeight - 3f) {
CachedEntitiesBelowTerrain.Add(entity.net.ID.Value);
return true;
}
return false;
}
public BaseLock GetLockOnDoor(Door door) {
BaseLock @lock = null;
foreach (var child in door.children) {
if (child is BaseLock) {
@lock = (BaseLock)child;
break;
}
}
return @lock;
}
public BaseLock SpawnLockOnDoor(Door door, BaseLock clone, bool replaceOldLock = true) {
if (replaceOldLock) {
BaseLock oldLock = GetLockOnDoor(door);
lockDestroyingInProgress = true;
oldLock?.Kill();
lockDestroyingInProgress = false;
}
BaseLock newLock = null;
if (clone is CodeLock cloneCodeLock) {
var newCodeLock = GameManager.server.CreateEntity("assets/prefabs/locks/keypad/lock.code.prefab") as CodeLock;
newCodeLock.gameObject.Identity();
newCodeLock.SetParent(door, door.GetSlotAnchorName(BaseEntity.Slot.Lock));
newCodeLock.Spawn();
newCodeLock.code = cloneCodeLock.code;
newCodeLock.hasCode = cloneCodeLock.hasCode;
newCodeLock.guestCode = cloneCodeLock.guestCode;
newCodeLock.hasGuestCode = cloneCodeLock.hasGuestCode;
newCodeLock.whitelistPlayers = cloneCodeLock.whitelistPlayers;
Effect.server.Run("assets/prefabs/locks/keypad/effects/lock-code-deploy.prefab", newLock.transform.position);
newLock = newCodeLock;
}
else if (clone is KeyLock cloneKeyLock) {
var newKeyLock = GameManager.server.CreateEntity("assets/prefabs/locks/keylock/lock.key.prefab") as KeyLock;
newKeyLock.gameObject.Identity();
newKeyLock.SetParent(door, door.GetSlotAnchorName(BaseEntity.Slot.Lock));
newKeyLock.Spawn();
newKeyLock.keyCode = cloneKeyLock.keyCode;
newLock = newKeyLock;
}
if (newLock != null) {
newLock.SetFlag(BaseEntity.Flags.Locked, clone.HasFlag(BaseEntity.Flags.Locked));
newLock.OwnerID = clone.OwnerID;
door.SetSlot(BaseEntity.Slot.Lock, newLock);
}
return newLock;
}
public void GetBuildingBlocksBelowTerrain(float distance, List<BuildingBlock> blocksBelowTerrain) {
foreach (var entity in BaseNetworkable.serverEntities) {
if (entity is BuildingBlock block) {
Vector3 position = block.transform.position;
float terrainHeight = TerrainMeta.HeightMap.GetHeight(position);
if (position.y < terrainHeight - distance) {
blocksBelowTerrain.Add(block);
}
}
}
}
public bool IsWithinBasement(Vector3 position) {
return RaycastFiltered(position, Vector3.up, 3f, out _, (hit) => {
return hit is BuildingBlock block && block.IsBasementEntity() && block.IsCeiling();
}
);
}
}
}
namespace Oxide.Plugins.BasementMethods {
public static partial class ExtensionMethods {
public static Basements.BasementEntrance AsBasementEntrance(this Door door) => door == null ? null : Basements.INSTANCE.BasementEntrances.GetValueOrDefault(door.net.ID.Value);
public static bool IsBasement(this BuildingManager.Building basementBuilding) => basementBuilding == null ? false : Basements.INSTANCE.BasementStructures.ContainsKey(basementBuilding.ID);
public static Basements.BasementStructure AsBasement(this BuildingManager.Building basementBuilding) => basementBuilding == null ? null : Basements.INSTANCE.BasementStructures.GetValueOrDefault(basementBuilding.ID);
public static IEnumerable<Basements.BasementStructure> GetBasements(this BuildingManager.Building surfaceBuilding) => Basements.INSTANCE.GetBasementsConnectedToSurfaceBuilding(surfaceBuilding);
public static BuildingBlock GetBasementFoundationFromSurface(this IEnumerable<Basements.BasementStructure> basements, BuildingBlock surfaceFoundation) {
if (surfaceFoundation == null) {
return null;
}
return basements.Select(x => GetBasementFoundationFromSurface(x.BasementBuilding, surfaceFoundation)).FirstOrDefault();
}
public static BuildingBlock GetBasementFoundationFromSurface(this BuildingManager.Building basementBuilding, BuildingBlock surfaceFoundation) {
if (surfaceFoundation == null) {
return null;
}
foreach (var basementBlock in basementBuilding?.buildingBlocks) {
if (basementBlock != null && basementBlock.IsBasementEntity() && basementBlock.IsFoundation() && basementBlock.Equals2DPosition(surfaceFoundation)) {
return basementBlock;
}
}
return null;
}
public static BuildingBlock GetSurfaceFoundationFromBasement(this BuildingManager.Building surfaceBuilding, BuildingBlock basementFoundation) {
if (basementFoundation == null) {
return null;
}
foreach (var surfaceBlock in surfaceBuilding?.buildingBlocks) {
if (surfaceBlock != null && surfaceBlock.IsFoundation() && surfaceBlock.Equals2DPosition(basementFoundation)) {
return surfaceBlock;
}
}
return null;
}
}
}
namespace Oxide.Plugins {
public partial class Basements {
[Command("basements.getid"), Permission(PermissionAdmin)]
private void CmdBasementsGetId(IPlayer player, string command, string[] args) {
var basePlayer = player.Object as BasePlayer;
if (basePlayer == null) {
player.Message(Lang(player.Id, "command in game"));
return;
}
var buildingblock = GetObjectRaycast(basePlayer, 100f) as BuildingBlock;
if (buildingblock == null) {
player.Message(Lang(player.Id, "command looking"));
return;
}
var surfaceBuilding = buildingblock.GetBuilding();
if (surfaceBuilding == null) {
player.Message(Lang(player.Id, "command looking"));
return;
}
var basements = GetBasementsConnectedToSurfaceBuilding(surfaceBuilding);
if (basements?.Any() == false) {
player.Message(Lang(player.Id, "command no basement"));
return;
}
else {
player.Message(Lang(player.Id, "command basements found", basements.Count, basements.Select(x => x.BasementBuildingId).ToSentence()));
return;
}
}
[Command("basements.destroy"), Permission(PermissionAdmin)]
private void CmdBasementsDestroy(IPlayer player, string command, string[] args) {
var usage = Lang(player.Id, "command usage", "basements.destroy <building id>");
if (args.Length == 0) {
player.Message(usage);
return;
}
uint bid;
if (!uint.TryParse(args[0], out bid)) {
player.Message(Lang(player.Id, "command invalid bid"));
return;
}
var basement = BasementStructures.GetValueOrDefault(bid);
if (basement == null) {
player.Message(Lang(player.Id, "command no basement for id", bid));
}
try {
DestroyBasement(basement);
}
catch (Exception) {
player.Message(Lang(player.Id, "command failed to destroy", bid));
return;
}
player.Message(Lang(player.Id, "command destroy success", bid));
}
[Command("basements.destroyall"), Permission(PermissionAdmin)]
private void CmdBasementsDestroyAll(IPlayer player, string command, string[] args) {
player.Message(Lang(player.Id, "command destroy all"));
int destroyed = 0;
UnsubscribeAll();
foreach (var basement in BasementStructures.Values.ToArray()) {
try {
DestroyBasement(basement);
destroyed++;
}
catch (Exception) {
player.Message(Lang(player.Id, "command failed to destroy", basement?.BasementBuildingId));
}
}
foreach (var entrance in BasementEntrances.Values) {
entrance.BaseEntity?.Kill();
}
BasementStructures.Clear();
BasementEntities.Clear();
BasementEntrances.Clear();
// Destroy any orphaned basements
var blocksBelowTerrain = Pool<BuildingBlock>.Get();
GetBuildingBlocksBelowTerrain(0.5f, blocksBelowTerrain);
foreach (var block in blocksBelowTerrain) {
try {
KillBasementEntity(block);
}
catch (Exception) {
// Ignore exception
}
}
Pool<BuildingBlock>.Recycle(blocksBelowTerrain);
player.Message(Lang(player.Id, "command destroy all success", destroyed));
SubscribeAll();
}
[Command("basements.list"), Permission(PermissionAdmin)]
private void CmdBasementsList(IPlayer player, string command, string[] args) {
var msg = "BASEMENTS\n";
foreach (var kvp in BasementStructures) {
var basement = kvp.Value;
basement.ClearCache();
msg += $"KEY={kvp.Key} ID={basement.BasementBuildingId} ELE={basement.Elevation} DOORS {basement.Entrances?.Count() ?? 0}" + "\n";
}
msg += "\nENTRANCES\n";
foreach (var kvp in BasementEntrances) {
var entrance = kvp.Value;
msg += $"KEY={kvp.Key} ID={entrance.HatchId} BASEMENT={entrance.BasementBuildingId} ISDOWN={entrance.IsDownHatch} Connected={entrance.ConnectedHatchId} ConnectedExists={entrance.ConnectedHatch != null}\n";
}
player.Message(msg);
}
}
}
namespace Oxide.Plugins {
public partial class Basements {
private Configuration config;
private class Configuration {
[JsonProperty(PropertyName = "Basement Entrance Build Cost")]
public string BasementEntranceBuildCost = "300 metal.fragments";
[JsonProperty(PropertyName = "Basement Cell Build Cost (Square)")]
public string BasementSquareCellBuildCost = "500 stones";
[JsonProperty(PropertyName = "Basement Cell Build Cost (Triangle)")]
public string BasementTriangleCellBuildCost = "250 stones";
[JsonProperty(PropertyName = "Basement Cell Building Grade (Mostly Cosmetic)")]
[JsonConverter(typeof(StringEnumConverter))]
public BuildingGrade.Enum BasementBuildingGrade = BuildingGrade.Enum.Stone;
[JsonProperty(PropertyName = "Required Foundation Grade For Basements")]
[JsonConverter(typeof(StringEnumConverter))]
public BuildingGrade.Enum RequiredFoundationGrade = BuildingGrade.Enum.Stone;
[JsonProperty(PropertyName = "Max Basement Entrances Per Building")]
public int EntranceLimit = 3;
[JsonProperty(PropertyName = "Damage Required To Break Locks on Basement Entrances")]
public float DamageRequiredToBreakLock = 50f;
[JsonProperty(PropertyName = "Damage Required To Drill New Basement Cells")]
public float DamageRequiredToDrillBasement = 5f;
[JsonProperty(PropertyName = "Destroy Basement Cell When Surface Foundation is Destroyed")]
public bool DestroyBasementUnderFoundation = true;
[JsonProperty(PropertyName = "Allow Hatch Removal (requires other plugins)")]
public bool AllowHatchRemoval = false;
[JsonProperty(PropertyName = "Restrict Drill Items")]
public bool RestrictDrillItems = true;
[JsonProperty(PropertyName = "Sync Locks on Basement Entrances")]
public bool SyncLocksOnBasementEntrances = true;
[JsonProperty(PropertyName = "Allowed Items for Drilling (if Restrict Drill Items is true)")]
public string[] BasementDrillItems = new string[] {
"jackhammer"
}
;
[JsonProperty(PropertyName = "Forbidden Deployables in Basement (full prefab path)")]
public string[] ForbiddenDeployablesInBasement = new string[] {
"assets/prefabs/deployable/tool cupboard/shockbyte/cupboard.tool.shockbyte.deployed.prefab", "assets/prefabs/deployable/cupboard.tool.retro.deployed.prefab", "assets/prefabs/deployable/tool cupboard/cupb
// ... [Code preview truncated due to length. Full source in attached file] ...