C#:
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Oxide.Game.Rust.Cui;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace Oxide.Plugins {
[Info("WaterBases", "Nikedemos", VERSION)]
[Description("Allows vanilla-like constructions on water surface - and underwater too!")]
public class WaterBases : RustPlugin {
private static WaterBases Instance;
#region CONST & STATIC
public const string VERSION = "1.0.4";
public const string PREFAB_FLOOR_SQUARE = "assets/prefabs/building core/floor/floor.prefab";
public const string PREFAB_FLOOR_TRIANGLE = "assets/prefabs/building core/floor.triangle/floor.triangle.prefab";
public const string PREFAB_BARREL = "assets/prefabs/resource/diesel barrel/diesel_barrel_world.prefab";
public const string PREFAB_NETTING = "assets/prefabs/building/wall.frame.netting/wall.frame.netting.prefab";
public const string PREFAB_BARREL_COLLECTABLE = "assets/content/structures/excavator/prefabs/diesel_collectable.prefab";
public const string PREFAB_OILRIG_1 = "OilrigAI";
public const string PREFAB_OILRIG_2 = "OilrigAI2";
public const string PREFAB_POOL_BIG = "assets/prefabs/misc/summer_dlc/abovegroundpool/abovegroundpool.deployed.prefab";
public const string PREFAB_POOL_SMALL = "assets/prefabs/misc/summer_dlc/paddling_pool/paddlingpool.deployed.prefab";
public const string PREFAB_INNER_TUBE = "assets/prefabs/misc/summer_dlc/inner_tube/innertube.deployed.prefab";
public const string PREFAB_BUILD_EFFECT = "assets/bundled/prefabs/fx/build/frame_place.prefab";
public const int ITEM_INNER_TUBE = -697981032;
public const int ITEM_BUILDING_PLANNER = 1525520776;
public const ulong SKIN_FOUNDATION_SQUARE = 2484982352;
public const ulong SKIN_FOUNDATION_TRIANGLE = 2485021365;
//used to identify stuff
public const ulong SKIN_WATER_BARREL = 1337424001;
public const ulong SKIN_GENERIC_DECAY_ENTITY = 1337424002;
//used to identify stuff
public const ulong SKIN_INVERSE_FOUNDATION_SQUARE = 1337424003;
public const ulong SKIN_INVERSE_FOUNDATION_TRIANGLE = 1337424004;
public const ulong SKIN_INVERSE_DECAY_ENTITY = 1337424002;
public static LayerMask DeployedMask = LayerMask.GetMask("Deployed");
public static LayerMask ConstructionMask = LayerMask.GetMask("Construction");
private static object ReusableObject;
private static bool ReusableBool;
private static string ReusableString;
private static float ReusableFloat;
private static bool ReusableBool2;
private static string ReusableString2;
private static float ReusableFloat2;
private static int ReusableInt2;
private static ulong ReusableUlong2;
public static readonly List<string> FramePrefabs = new List<string> {
"assets/prefabs/building core/floor.frame/floor.frame.prefab", "assets/prefabs/building core/floor.triangle.frame/floor.triangle.frame.prefab", "assets/prefabs/building core/wall.frame/wall.frame.prefab"
}
;
public static readonly List<ulong> RelevantSkins = new List<ulong> {
SKIN_FOUNDATION_SQUARE, SKIN_FOUNDATION_TRIANGLE, SKIN_GENERIC_DECAY_ENTITY, SKIN_WATER_BARREL, SKIN_INVERSE_DECAY_ENTITY, SKIN_INVERSE_FOUNDATION_SQUARE, SKIN_INVERSE_FOUNDATION_TRIANGLE
}
;
public static List<Vector3> FloorBarrelPositionsSquare = new List<Vector3> {
new Vector3(0F, -0.4F, 0.33F), new Vector3(0F, -0.4F, -0.33F), new Vector3(-0.33F, -0.4F, 1.12F), new Vector3(-0.33F, -0.4F, 0.36F), new Vector3(-0.33F, -0.4F, -0.36F), new Vector3(-0.33F, -0.4F, -1.12F), new Vector3(0.33F, -0.4F, 1.12F), new Vector3(0.33F, -0.4F, 0.36F), new Vector3(0.33F, -0.4F, -1.12F), new Vector3(0.33F, -0.4F, -0.36F)
}
;
public static List<Vector3> FloorBarrelRotationsSquare = new List<Vector3> {
new Vector3(0F, 90F, 90F), new Vector3(0F, 90F, 270F), new Vector3(0F, 0F, 90F), new Vector3(0F, 0F, 90F), new Vector3(0F, 0F, 90F), new Vector3(0F, 0F, 90F), new Vector3(0F, 0F, 270F), new Vector3(0F, 0F, 270F), new Vector3(0F, 0F, 270F), new Vector3(0F, 0F, 270F)
}
;
public static List<Vector3> FloorBarrelPositionsTriangle= new List<Vector3> {
new Vector3(0F, -0.4F, -0.6F+1.5F), new Vector3(0.42F, -0.4F, -0.42F+1.5F), new Vector3(-0.42F, -0.4F, -0.42F+1.5F), new Vector3(0F, -0.4F, -1.17F+1.5F), new Vector3(0F, -0.4F, -1.17F+1.5F)
}
;
public static List<Vector3> FloorBarrelRotationsTriangle = new List<Vector3> {
new Vector3(0F, 90F, 90F), new Vector3(0F, 0F, 90F), new Vector3(0F, 180F, 90F), new Vector3(0F, 180F, 90F), new Vector3(0F, 0F, 90F)
}
;
#endregion
#region LANG
public const string MSG_ITEM_NAME_TRIANGLE = "MSG_ITEM_NAME_TRIANGLE";
public const string MSG_ITEM_NAME_SQUARE = "MSG_ITEM_NAME_SQUARE";
public const string MSG_CHAT_PREFIX = "MSG_CHAT_PREFIX";
public const string MSG_DEPLOY_ERROR = "MSG_DEPLOY_ERROR";
public const string MSG_CRAFT = "MSG_CRAFT";
public const string MSG_DEPLOY_RESULT_BUILDING_BLOCKED = "MSG_DEPLOY_RESULT_BUILDING_BLOCKED";
public const string MSG_DEPLOY_RESULT_TOPOLOGY_MISMATCH = "MSG_DEPLOY_RESULT_TOPOLOGY_MISMATCH";
public const string MSG_DEPLOY_RESULT_NO_PERM_DEPLOY = "MSG_DEPLOY_RESULT_NO_PERM_DEPLOY";
public const string MSG_DEPLOY_RESULT_NO_PERM_EXPAND = "MSG_DEPLOY_RESULT_NO_PERM_EXPAND";
public const string MSG_DEPLOY_RESULT_TOO_SHALLOW = "MSG_DEPLOY_RESULT_TOO_SHALLOW";
public const string MSG_DEPLOY_RESULT_TOO_DEEP = "MSG_DEPLOY_RESULT_TOO_DEEP";
public const string MSG_DEPLOY_RESULT_OUTSIDE_GRID = "MSG_DEPLOY_RESULT_OUTSIDE_GRID";
public const string MSG_DEPLOY_RESULT_TOO_CLOSE_TO_OILRIG = "MSG_DEPLOY_RESULT_TOO_CLOSE_TO_OILRIG";
public const string MSG_DEPLOY_RESULT_TOO_CLOSE_TO_SHORE = "MSG_DEPLOY_RESULT_TOO_CLOSE_TO_SHORE";
public const string MSG_DEPLOY_RESULT_TOO_FAR_FROM_SHORE = "MSG_DEPLOY_RESULT_TOO_FAR_FROM_SHORE";
public const string MSG_DEPLOY_RESULT_TOO_MANY_FOUNDATIONS = "MSG_DEPLOY_RESULT_TOO_MANY_FOUNDATIONS";
public const string MSG_DEPLOY_RESULT_IN_POOL = "MSG_DEPLOY_RESULT_IN_POOL";
public const string MSG_DEPLOY_RESULT_LACKS_ITEM = "MSG_DEPLOY_RESULT_LACKS_ITEM";
public const string MSG_EXPAND_REINFORCEMENT_NOT_ALLOWED = "MSG_EXPAND_REINFORCEMENT_NOT_ALLOWED";
public const string MSG_REINFORCEMENT_NOT_ALLOWED = "MSG_REINFORCEMENT_NOT_ALLOWED";
public const string MSG_REINFORCEMENT_LACKS_ITEM = "MSG_REINFORCEMENT_LACKS_ITEM";
public const string MSG_EXPAND_REINFORCEMENT_WRONG_ABOVE = "MSG_EXPAND_REINFORCEMENT_WRONG_ABOVE";
public const string MSG_EXPAND_REINFORCEMENT_LACKS_ITEM = "MSG_EXPAND_REINFORCEMENT_LACKS_ITEM";
public const string MSG_UNDERWATER_NETTING_NO_PERM = "MSG_UNDERWATER_NETTING_NO_PERM";
public const string MSG_CRAFTING_NO_PERM = "MSG_CRAFTING_NO_PERM";
public const string MSG_CRAFTING_NO_MATERIALS = "MSG_CRAFTING_NO_MATERIALS";
public const string MSG_CRAFTING_INSUFFICIENT_WB_LEVEL = "MSG_CRAFTING_INSUFFICIENT_WB_LEVEL";
public const string MSG_MAX_TIER_REACHED_MAIN = "MSG_MAX_TIER_REACHED_MAIN";
public const string MSG_MAX_TIER_REACHED_FRAME = "MSG_MAX_TIER_REACHED_FRAME";
public const string MSG_MAX_TIER_REACHED_FOUNDATION = "MSG_MAX_TIER_REACHED_FOUNDATION";
public const string MSG_CARGO_PATH_WARNING = "MSG_CARGO_PATH_WARNING";
public const string MSG_CARGO_PATH_PREVENTION = "MSG_CARGO_PATH_PREVENTION";
public const string MSG_VALUE_HAS_BEEN_SET = "MSG_VALUE_HAS_BEEN_SET";
public const string MSG_CFG_RUNDOWN_FORMAT = "MSG_CFG_RUNDOWN_FORMAT";
public const string MSG_CFG_DETAILS_FORMAT = "MSG_CFG_DETAILS_FORMAT";
public const string MSG_CFG_NO_SETTING_FOUND = "MSG_CFG_NO_SETTING_FOUND";
public const string MSG_CFG_DEFAULT = "MSG_CFG_DEFAULT";
private Dictionary<string, string> LangMessages = new Dictionary<string, string> {
[MSG_CHAT_PREFIX]
= "<color=#4897ce>[Water Bases]</color>", [MSG_DEPLOY_ERROR] = "ERROR:", [MSG_ITEM_NAME_SQUARE] = "Water Foundation (SQUARE)", [MSG_ITEM_NAME_TRIANGLE] = "Water Foundation (TRIANGLE)", [MSG_CRAFT] = "Craft", [MSG_DEPLOY_RESULT_BUILDING_BLOCKED] = "You're building blocked.", [MSG_DEPLOY_RESULT_TOPOLOGY_MISMATCH] = "You're not allowed to build on this topology.", [MSG_DEPLOY_RESULT_NO_PERM_DEPLOY] = "You're not allowed to deploy new water foundations.", [MSG_DEPLOY_RESULT_NO_PERM_EXPAND] = "You're not allowed to expand existing water foundations.", [MSG_DEPLOY_RESULT_TOO_SHALLOW] = "You're not allowed to build in water this shallow.", [MSG_DEPLOY_RESULT_TOO_DEEP] = "You're not allowed to build in water this deep.", [MSG_DEPLOY_RESULT_OUTSIDE_GRID] = "You're not allowed to build outside of the map grid.", [MSG_DEPLOY_RESULT_TOO_CLOSE_TO_OILRIG] = "You're not allowed to build this close to the Oil Rig.", [MSG_DEPLOY_RESULT_TOO_CLOSE_TO_SHORE] = "You're not allowed to build this close to shore.", [MSG_DEPLOY_RESULT_TOO_FAR_FROM_SHORE] = "You're not allowed to build this far from shore.", [MSG_DEPLOY_RESULT_TOO_MANY_FOUNDATIONS] = "You're not allowed this many water foundations per building.", [MSG_DEPLOY_RESULT_IN_POOL] = "Nice try, but building in swimming pools would be a little bit much.", [MSG_MAX_TIER_REACHED_MAIN] = "You're not allowed to upgrade walls/floors/windows/doorways to tier this high.", [MSG_MAX_TIER_REACHED_FRAME] = "You're not allowed to upgrade floor frames/wall frames to tier this high.", [MSG_MAX_TIER_REACHED_FOUNDATION] = "You're not allowed to upgrade water foundations to tier this high.", [MSG_CARGO_PATH_PREVENTION] = "You're not allowed to build this close to the path of the Cargo Ship.", [MSG_CARGO_PATH_WARNING] = "You're building in the path of the Cargo Ship.Your structure is at risk.", [MSG_EXPAND_REINFORCEMENT_NOT_ALLOWED] = "You're not allowed to expand reinforcements like this.Use a hammer on the fully repaired Water Foundation instead.", [MSG_REINFORCEMENT_LACKS_ITEM] = "You need a proper Water Foundation item in your inventory to reinforce this water foundation.", [MSG_EXPAND_REINFORCEMENT_LACKS_ITEM] = "You need 2 proper Water Foundation items in your inventory to expand this reinforced water foundation.", [MSG_REINFORCEMENT_NOT_ALLOWED] = "You're not allowed to reinforce Water Foundations.", [MSG_UNDERWATER_NETTING_NO_PERM] = "You're not allowed to deploy underwater netting.", [MSG_CRAFTING_NO_MATERIALS] = "You need {0} to craft a {1}.", [MSG_CRAFTING_NO_PERM] = "You're not allowed to craft Water Foundations.", [MSG_CRAFTING_INSUFFICIENT_WB_LEVEL] = "You need a Level {0} Workbench to craft Water Foundations.", [MSG_DEPLOY_RESULT_LACKS_ITEM] = "You need a proper Water Foundation item in your inventory to expand this water foundation.", [MSG_EXPAND_REINFORCEMENT_WRONG_ABOVE] = "You can't expand like that.The shapes of the reinforcement and the water foundation above it must match.", [MSG_VALUE_HAS_BEEN_SET] = "Config value <color=yellow>{0}</color> has been set to {1} by an admin.", [MSG_CFG_DEFAULT] = "Here's the current settings.Type <color=yellow>/wb_cfg settingName</color> with no parameters to see the description, the current value and what the accepted arguments are.Type <color=yellow>/wb_cfg settingName acceptedValue</color> to change the setting.", [MSG_CFG_RUNDOWN_FORMAT] = "/wb_cfg <color=yellow>{0}</color> (currently: {1})", [MSG_CFG_DETAILS_FORMAT] = "<color=green>{0}</color>:\n{1} ({2})\nThis value is currently set to: {3}\n", [MSG_CFG_NO_SETTING_FOUND] = "No setting with that name has been found.Type /wb_cfg to get a rundown.",
}
;
private static string MSG(string msg, string userID = null, params object[] args) {
if (args == null) {
return Instance.lang.GetMessage(msg, Instance, userID);
}
else {
return string.Format(Instance.lang.GetMessage(msg, Instance, userID), args);
}
}
private static string MSG_RESULT(DeploymentResult result, string userID = null) {
switch (result) {
default : case DeploymentResult.Success : {
return null;
}
case DeploymentResult.BuildingBlocked : {
return $"{MSG(MSG_DEPLOY_ERROR, userID)} {MSG(MSG_DEPLOY_RESULT_BUILDING_BLOCKED, userID)}";
}
case DeploymentResult.TopologyMismatch : {
return $"{MSG(MSG_DEPLOY_ERROR, userID)} {MSG(MSG_DEPLOY_RESULT_TOPOLOGY_MISMATCH, userID)}";
}
case DeploymentResult.NoPermissionToDeploy : {
return $"{MSG(MSG_DEPLOY_ERROR, userID)} {MSG(MSG_DEPLOY_RESULT_NO_PERM_DEPLOY, userID)}";
}
case DeploymentResult.NoPermissionToExpand : {
return $"{MSG(MSG_DEPLOY_ERROR, userID)} {MSG(MSG_DEPLOY_RESULT_NO_PERM_EXPAND, userID)}";
}
case DeploymentResult.WaterTooShallow : {
return $"{MSG(MSG_DEPLOY_ERROR, userID)} {MSG(MSG_DEPLOY_RESULT_TOO_SHALLOW, userID)}";
}
case DeploymentResult.WaterTooDeep : {
return $"{MSG(MSG_DEPLOY_ERROR, userID)} {MSG(MSG_DEPLOY_RESULT_TOO_DEEP, userID)}";
}
case DeploymentResult.DeployedInPool : {
return $"{MSG(MSG_DEPLOY_ERROR, userID)} {MSG(MSG_DEPLOY_RESULT_IN_POOL, userID)}";
}
case DeploymentResult.TooCloseToOilrig : {
return $"{MSG(MSG_DEPLOY_ERROR, userID)} {MSG(MSG_DEPLOY_RESULT_TOO_CLOSE_TO_OILRIG, userID)}";
}
case DeploymentResult.TooCloseToShore : {
return $"{MSG(MSG_DEPLOY_ERROR, userID)} {MSG(MSG_DEPLOY_RESULT_TOO_CLOSE_TO_SHORE, userID)}";
}
case DeploymentResult.TooFarFromShore : {
return $"{MSG(MSG_DEPLOY_ERROR, userID)} {MSG(MSG_DEPLOY_RESULT_TOO_FAR_FROM_SHORE, userID)}";
}
case DeploymentResult.TooManyFoundations : {
return $"{MSG(MSG_DEPLOY_ERROR, userID)} {MSG(MSG_DEPLOY_RESULT_TOO_MANY_FOUNDATIONS, userID)}";
}
case DeploymentResult.OnCargoShipPath : {
return $"{MSG(MSG_DEPLOY_ERROR, userID)} {MSG(Instance.configData.CargoShipPathHandling == CargoPathHandling.Prevent ? MSG_CARGO_PATH_PREVENTION : MSG_CARGO_PATH_WARNING, userID)}";
}
case DeploymentResult.NoRequiredItem : {
return $"{MSG(MSG_DEPLOY_ERROR, userID)} {MSG(MSG_DEPLOY_RESULT_LACKS_ITEM, userID)}";
}
case DeploymentResult.OutsideMapGrid : {
return $"{MSG(MSG_DEPLOY_ERROR, userID)} {MSG(MSG_DEPLOY_RESULT_OUTSIDE_GRID, userID)}";
}
}
}
#endregion
#region PERMISSIONS
public const string PERM_ADMIN = "waterbases.admin";
public const string PERM_VIP1 = "waterbases.vip1";
public const string PERM_VIP2 = "waterbases.vip2";
public const string PERM_VIP3 = "waterbases.vip3";
public const string PERM_VIP4 = "waterbases.vip4";
public const string PERM_VIP5 = "waterbases.vip5";
#endregion
#region HOOKS
void OnServerInitialized() {
Instance = this;
lang.RegisterMessages(LangMessages, this);
permission.RegisterPermission(PERM_ADMIN, this);
permission.RegisterPermission(PERM_VIP1, this);
permission.RegisterPermission(PERM_VIP2, this);
permission.RegisterPermission(PERM_VIP3, this);
permission.RegisterPermission(PERM_VIP4, this);
permission.RegisterPermission(PERM_VIP5, this);
LoadConfigData();
ProcessConfigData();
ConfigValues = new Dictionary<string, InteractiveConfigValue>();
GenerateAllInteractiveConfigValues();
RecalculateUnderwaterLootWeights();
GuiManager.GenerateGUI();
ActivateSpecialStuff();
}
void Unload() {
Instance = null;
foreach (var helper in UnityEngine.Object.FindObjectsOfType<InverseFoundationHelper>()) {
UnityEngine.Object.DestroyImmediate(helper);
}
foreach (var helper in UnityEngine.Object.FindObjectsOfType<UnderwaterNetHelper>()) {
UnityEngine.Object.DestroyImmediate(helper);
}
foreach (var player in BasePlayer.activePlayerList) {
GuiManager.HideGUI(player);
}
}
void OnPlayerDeath(BasePlayer player, HitInfo info) {
GuiManager.HideGUI(player);
}
void OnItemRemovedFromContainer(ItemContainer container, Item item) {
if (item.info.itemid != ITEM_BUILDING_PLANNER) {
return;
}
var player = container.GetOwnerPlayer();
if (player == null) {
return;
}
if (player.svActiveItemID == item.uid) {
GuiManager.HideGUI(player);
}
}
void OnActiveItemChanged(BasePlayer player, Item oldItem, Item newItem) {
if (newItem != null) {
if (newItem.info.itemid == ITEM_BUILDING_PLANNER) {
if (oldItem?.info.itemid != ITEM_BUILDING_PLANNER || oldItem == null) {
GuiManager.ShowGUI(player);
}
}
else {
if (oldItem != null) {
if (oldItem.info.itemid == ITEM_BUILDING_PLANNER) {
GuiManager.HideGUI(player);
}
}
}
}
else {
if (oldItem != null) {
if (oldItem.info.itemid == ITEM_BUILDING_PLANNER) {
GuiManager.HideGUI(player);
}
}
}
}
object OnRecycleItem(Recycler recycler, Item item) {
if (!IsSkinRelevant(item.skin)) {
return null;
}
List<SerializableItemAmount> costList;
if (item.skin == SKIN_FOUNDATION_SQUARE) {
costList = Instance.configData.CraftingCostSquare;
}
else {
costList = Instance.configData.CraftingCostTriangle;
}
foreach (var cost in costList) {
recycler.MoveItemToOutput(ItemManager.CreateByName(cost.Shortname, Mathf.CeilToInt((float)cost.Amount/2F)));
}
item.UseItem(1);
item.MarkDirty();
return true;
}
object OnStructureUpgrade(BuildingBlock block, BasePlayer player, BuildingGrade.Enum grade) {
if (IsInWaterBase(block)) {
var prof = GetPermissionProfile(player);
bool isFrame = false;
bool isWaterFoundation = false;
var maxTier = prof.MaxBuildingGradeGeneric;
if (IsWaterFoundation(block)) {
maxTier = prof.MaxBuildingGradeWaterFoundations;
isWaterFoundation = true;
}
else {
if (FramePrefabs.Contains(block.PrefabName)) {
maxTier = prof.MaxBuildingGradeFrames;
isFrame = true;
}
}
//check if the grade has not exceeded config max
if ((int)grade > (int)maxTier) {
TellMessage(player, MSG(isWaterFoundation ? MSG_MAX_TIER_REACHED_FOUNDATION : isFrame ? MSG_MAX_TIER_REACHED_FRAME : MSG_MAX_TIER_REACHED_MAIN, player.UserIDString));
return true;
}
}
return null;
}
object OnHammerHit(BasePlayer player, HitInfo info) {
if (player.IsBuildingBlocked()) return null;
//see if the player has enough resources to create inverse...if (info.HitEntity == null) return null;
var buildingBlock = info.HitEntity as BuildingBlock;
if (buildingBlock == null) {
return null;
}
if (buildingBlock.health != buildingBlock.MaxHealth()) {
return null;
}
if (!IsWaterFoundation(buildingBlock)) {
return null;
}
var allChildren = buildingBlock.GetComponentsInChildren<BuildingBlock>();
BuildingBlock inverseFloorAlready = null;
foreach (var block in allChildren) {
if (block.net.ID == buildingBlock.net.ID) continue;
inverseFloorAlready = block;
break;
}
if (inverseFloorAlready == null) {
var needed = FindNeededFoundationItemsInInventory(player, buildingBlock.PrefabName == PREFAB_FLOOR_SQUARE ? SKIN_FOUNDATION_SQUARE : SKIN_FOUNDATION_TRIANGLE);
if (needed == null) {
TellMessage(player, $"{MSG(MSG_DEPLOY_ERROR)} {MSG(MSG_REINFORCEMENT_LACKS_ITEM, player.UserIDString)}");
return true;
}
ConsumeFromSpecificItemList(ref needed);
BuildInverseFoundationAtWaterFoundation(player, buildingBlock);
return true;
}
return null;
}
void OnEntityBuilt(Planner planner, GameObject gameObject) {
var maybeInflatable = gameObject.GetComponent<WaterInflatable>();
//normal building, not from inflatable
if (maybeInflatable == null) {
var maybeDecayEntity = gameObject.GetComponent<DecayEntity>();
if (maybeDecayEntity == null) {
return;
}
var newBuildingBlock = gameObject.GetComponent<BuildingBlock>();
var player = planner.GetOwnerPlayer();
if (newBuildingBlock != null) {
if (IsInWaterBase(newBuildingBlock)) {
//should this BECOME a water foundation? or an inverse foundation?
if (newBuildingBlock.PrefabName == PREFAB_FLOOR_SQUARE || newBuildingBlock.PrefabName == PREFAB_FLOOR_TRIANGLE) {
bool isInverse;
var nearby = TryGetAdjacentSpecialFoundation(newBuildingBlock, out isInverse);
if (nearby == null) {
//for now, maybe let's leave it as generic?
newBuildingBlock.skinID = Mathf.Abs(newBuildingBlock.transform.localEulerAngles.z)<0.1F ? SKIN_GENERIC_DECAY_ENTITY : SKIN_INVERSE_DECAY_ENTITY;
return;
}
if (!isInverse) {
if (newBuildingBlock.PrefabName == PREFAB_FLOOR_SQUARE) {
newBuildingBlock.skinID = SKIN_FOUNDATION_SQUARE;
}
else {
newBuildingBlock.skinID = SKIN_FOUNDATION_TRIANGLE;
}
TurnFloorIntoWaterFoundation(newBuildingBlock, player, false);
return;
}
else {
if (newBuildingBlock.PrefabName == PREFAB_FLOOR_SQUARE) {
newBuildingBlock.skinID = SKIN_INVERSE_FOUNDATION_SQUARE;
}
else {
newBuildingBlock.skinID = SKIN_INVERSE_FOUNDATION_TRIANGLE;
}
var maybeAboveYou = TryFindWaterFoundationAboveYou(newBuildingBlock);
if (maybeAboveYou == null) {
var prof = GetPermissionProfile(player);
//first and foremost: how many water foundation does the building have?
//if you don't do this check, you're gonna have a REALLY bad time
var waterFoundations = CountBuildingWaterFoundations(newBuildingBlock);
if (waterFoundations +1 >= prof.MaxWaterFoundationsPerBuilding) {
TellMessage(player, $"{MSG(MSG_DEPLOY_ERROR, player.UserIDString)} {MSG(MSG_DEPLOY_RESULT_TOO_MANY_FOUNDATIONS, player.UserIDString)}");
newBuildingBlock.Kill(BaseNetworkable.DestroyMode.Gib);
return;
}
if (!prof.CanExpandWaterFoundations) {
TellMessage(player, $"{MSG(MSG_DEPLOY_ERROR, player.UserIDString)} {MSG(MSG_EXPAND_REINFORCEMENT_NOT_ALLOWED, player.UserIDString)}");
newBuildingBlock.Kill(BaseNetworkable.DestroyMode.Gib);
return;
}
//do you have enough of needed kind? you need two!
var neededItems = FindNeededFoundationItemsInInventory(player, newBuildingBlock.PrefabName == PREFAB_FLOOR_SQUARE ? SKIN_FOUNDATION_SQUARE : SKIN_FOUNDATION_TRIANGLE, 2);
if (neededItems == null) {
TellMessage(player, $"{MSG(MSG_DEPLOY_ERROR, player.UserIDString)} {MSG(MSG_EXPAND_REINFORCEMENT_LACKS_ITEM, player.UserIDString)}");
newBuildingBlock.Kill(BaseNetworkable.DestroyMode.Gib);
return;
}
//consume the item
ConsumeFromSpecificItemList(ref neededItems);
//let's make a foundation first.relative please.var transf = GetWorldTransformRelativeTo(newBuildingBlock.transform, Vector3.down * 1.5F, new Vector3(0F, 0F, -180F));
maybeAboveYou = BuildWaterFoundationAt(player, newBuildingBlock.PrefabName == PREFAB_FLOOR_SQUARE ? SKIN_FOUNDATION_SQUARE : SKIN_FOUNDATION_TRIANGLE, transf.Item1, transf.Item2);
maybeAboveYou.AttachToBuilding(nearby);
newBuildingBlock.skinID = newBuildingBlock.PrefabName == PREFAB_FLOOR_SQUARE ? SKIN_INVERSE_FOUNDATION_SQUARE : SKIN_INVERSE_FOUNDATION_TRIANGLE;
newBuildingBlock.AttachToBuilding(nearby);
}
else {
//is the foundation above you the same shape as you?
if (!((maybeAboveYou.skinID == SKIN_FOUNDATION_SQUARE && newBuildingBlock.skinID == SKIN_INVERSE_FOUNDATION_SQUARE) || (maybeAboveYou.skinID == SKIN_FOUNDATION_TRIANGLE && newBuildingBlock.skinID == SKIN_INVERSE_FOUNDATION_TRIANGLE))) {
TellMessage(player, $"{MSG(MSG_EXPAND_REINFORCEMENT_WRONG_ABOVE, player.UserIDString)}");
newBuildingBlock.Kill(BaseNetworkable.DestroyMode.Gib);
return;
}
//do you have enough of needed kind?
var neededItems = FindNeededFoundationItemsInInventory(player, newBuildingBlock.PrefabName == PREFAB_FLOOR_SQUARE ? SKIN_FOUNDATION_SQUARE : SKIN_FOUNDATION_TRIANGLE);
if (neededItems == null) {
TellMessage(player, $"{MSG(MSG_DEPLOY_ERROR, player.UserIDString)} {MSG(MSG_EXPAND_REINFORCEMENT_LACKS_ITEM, player.UserIDString)}");
newBuildingBlock.Kill(BaseNetworkable.DestroyMode.Gib);
return;
}
//consume the item
ConsumeFromSpecificItemList(ref neededItems);
}
TurnFloorIntoInverseFoundation(newBuildingBlock, maybeAboveYou, player);
return;
}
}
else {
//give it generic skin - but still, inverse or not?
//for now, maybe let's leave it as generic?
newBuildingBlock.skinID = Mathf.Abs(newBuildingBlock.transform.localEulerAngles.z) < 0.1F ? SKIN_GENERIC_DECAY_ENTITY : SKIN_INVERSE_DECAY_ENTITY;
//still a building block
ApplyUpkeepMultiplier(newBuildingBlock);
}
}
}
else {
//not a building block, so it must be a regular decay entity
if (IsInWaterBase(maybeDecayEntity)) {
var maybeStability = maybeDecayEntity as StabilityEntity;
if (maybeStability != null) {
if (IsUnderwaterNetting(maybeStability, true)) {
//maybeStability.skinID = SKIN_INVERSE_DECAY_ENTITY;
if (Instance.configData.UnderwaterNetsCollectJunk) {
var prof = GetPermissionProfile(player);
if (prof.CanDeployUnderwaterNets) {
MonoiseUnderwaterNetting(maybeStability);
}
else {
TellMessage(player, $"{MSG(MSG_DEPLOY_ERROR, player.UserIDString)} {MSG(MSG_UNDERWATER_NETTING_NO_PERM, player.UserIDString)}");
maybeStability.Kill(BaseNetworkable.DestroyMode.Gib);
return;
}
}
}
}
ApplyUpkeepMultiplier(maybeDecayEntity);
}
}
}
else //from inflatable
{
if (!IsSkinRelevant(maybeInflatable.skinID)) {
return;
}
var player = planner.GetOwnerPlayer();
Instance.NextTick(() => {
BuildWaterFoundationAt(player, maybeInflatable.skinID, maybeInflatable.transform.position, Quaternion.Euler(maybeInflatable.transform.eulerAngles.WithX(0).WithZ(0)));
maybeInflatable.Kill(BaseNetworkable.DestroyMode.None);
}
);
}
}
void OnItemAddedToContainer(ItemContainer container, Item item) {
if (Instance == null) return;
if (container == null) return;
if (item == null) return;
if (item.info.itemid != ITEM_INNER_TUBE) return;
if (!IsSkinRelevant(item.skin)) return;
EnsureFoundationItemName(item);
}
#endregion
#region MONO
public class UnderwaterNetHelper : MonoBehaviour {
public StabilityEntity netting;
public float NextUpdateAt = NextRandomTime();
public void Prepare(StabilityEntity netting) {
this.netting = netting;
}
private void FixedUpdate() {
if (UnityEngine.Time.realtimeSinceStartup < NextUpdateAt) {
return;
}
NextUpdateAt = NextRandomTime();
if (netting.gameObject.GetComponentsInChildren<DroppedItem>().Length >= Instance.configData.UnderwaterNetsItemLimit) {
return;
}
CreateRandomUnderwaterJunk(netting);
}
public static float NextRandomTime() {
return UnityEngine.Time.realtimeSinceStartup + UnityEngine.Random.Range(Instance.configData.UnderwaterNetsRandomTimerMin, Instance.configData.UnderwaterNetsRandomTimerMin);
}
}
public class InverseFoundationHelper : MonoBehaviour {
public BuildingBlock inverseFoundation;
public BuildingBlock waterFoundationParent;
public static float UPDATE_RATE_MIN = 3F;
public static float UPDATE_RATE_MAX = 5F;
public float UpdateRateCurrent = RandomUpdateRate();
public float UpdatedLast = float.MinValue;
public BuildingManager.Building myBuilding;
public BuildingManager.Building otherBuilding;
public void Prepare(BuildingBlock inverseFoundation) {
this.inverseFoundation = inverseFoundation;
this.waterFoundationParent = inverseFoundation.GetParentEntity() as BuildingBlock;
}
private void FixedUpdate() {
if (UnityEngine.Time.realtimeSinceStartup < UpdatedLast + UpdateRateCurrent) {
return;
}
UpdatedLast = UnityEngine.Time.realtimeSinceStartup;
if (inverseFoundation == null) return;
if (waterFoundationParent == null) return;
if (inverseFoundation.buildingID != waterFoundationParent.buildingID) {
myBuilding = inverseFoundation.GetBuilding();
if (myBuilding == null) return;
otherBuilding = waterFoundationParent.GetBuilding();
if (otherBuilding == null) return;
while (myBuilding.HasDecayEntities()) {
myBuilding.decayEntities[0].AttachToBuilding(otherBuilding.ID);
}
if (ConVar.AI.nav_carve_use_building_optimization) {
myBuilding.isNavMeshCarvingDirty = true;
otherBuilding.isNavMeshCarvingDirty = true;
int ticks = 3;
BuildingManager.server.UpdateNavMeshCarver(myBuilding, ref ticks, 0);
BuildingManager.server.UpdateNavMeshCarver(otherBuilding, ref ticks, 0);
}
UpdateRateCurrent = RandomUpdateRate();
}
}
public static float RandomUpdateRate() {
return UnityEngine.Random.Range(UPDATE_RATE_MIN, UPDATE_RATE_MAX);
}
}
#endregion
#region GUI
[ConsoleCommand("wb_craft.square")]
private void cmdConsoleWBCraftSquare(ConsoleSystem.Arg arg) {
var player = arg.Player();
if (player == null) {
return;
}
TryCraftingWaterFoundation(player, SKIN_FOUNDATION_SQUARE);
}
[ConsoleCommand("wb_craft.triangle")]
private void cmdConsoleWBCraftTriangle(ConsoleSystem.Arg arg) {
var player = arg.Player();
if (player == null) {
return;
}
TryCraftingWaterFoundation(player, SKIN_FOUNDATION_TRIANGLE);
}
public class ColorCode {
public string hexValue;
public UnityEngine.Color rustValue;
public string rustString;
public ColorCode(string hex) {
hex = hex.ToUpper();
hexValue = "#" + hex;
//extract the R, G, B
var r = (float)short.Parse(hex.Substring(0, 2), System.Globalization.NumberStyles.HexNumber) / 255;
var g = (float)short.Parse(hex.Substring(2, 2), System.Globalization.NumberStyles.HexNumber) / 255;
var b = (float)short.Parse(hex.Substring(4, 2), System.Globalization.NumberStyles.HexNumber) / 255;
rustValue = new UnityEngine.Color(r, g, b);
rustString = $"{r} {g} {b}";
}
}
public static class GuiManager {
public static CuiElementContainer ContainerMain;
public static string ContainerMainJson;
public static void GenerateGUI() {
ContainerMain = new CuiElementContainer();
ContainerMain.Add(new CuiPanel {
RectTransform = {
AnchorMin = $"{Instance.configData.GuiAnchorALeft} {Instance.configData.GuiAnchorBBottom}", AnchorMax = $"{Instance.configData.GuiAnchorCRight} {Instance.configData.GuiAnchorDTop}",
}
}
, "Overlay", "wbgui.panel");
ContainerMain.Add(new CuiButton {
Button = {
Color = $"{new ColorCode(Instance.configData.GuiButtonColor).rustString} {Instance.configData.GuiButtonAlpha}", Command = "wb_craft.square",
}
, Text = {
FontSize = Instance.configData.GuiTextSize, Color = $"{new ColorCode(Instance.configData.GuiTextColor).rustString} {Instance.configData.GuiTextAlpha}", Align = TextAnchor.MiddleCenter, Text = $"{MSG(MSG_CRAFT)} {MSG(MSG_ITEM_NAME_SQUARE)}",
}
, RectTransform = {
AnchorMin = "0 0", AnchorMax = "1 0.5"
}
}
, "wbgui.panel", "wbgui.button.craft.square");
ContainerMain.Add(new CuiButton {
Button = {
Color = new ColorCode(Instance.configData.GuiButtonColor).rustString, Command = "wb_craft.triangle",
}
, Text = {
FontSize = Instance.configData.GuiTextSize, Color = new ColorCode(Instance.configData.GuiTextColor).rustString, Align = TextAnchor.MiddleCenter, Text = $"{MSG(MSG_CRAFT)} {MSG(MSG_ITEM_NAME_TRIANGLE)}"
}
, RectTransform = {
AnchorMin = "0 0.5", AnchorMax = "1 1"
}
}
, "wbgui.panel", "wbgui.button.craft.triangle");
ContainerMainJson = ContainerMain.ToJson();
}
public static void ShowGUI(BasePlayer player) {
HideGUI(player);
if (!GetPermissionProfile(player).CanCraftWaterFoundations) {
return;
}
CuiHelper.AddUi(player, ContainerMainJson);
}
public static void HideGUI(BasePlayer player) {
CuiHelper.DestroyUi(player, "wbgui.panel");
}
}
#endregion
#region CHAT & CONSOLE
[ChatCommand("copypaste_prepare")]
private void ChatCommandCopypastePrepare(BasePlayer player, string command, string[] args) {
if (!HasAdminPermission(player)) return;
var tc = player.GetBuildingPrivilege();
if (tc == null) {
player.ChatMessage("You're not near a TC.");
return;
}
if (!IsInWaterBase(tc)) {
player.ChatMessage("This is not a Water Base.");
return;
}
foreach (var ent in tc.GetBuilding().buildingBlocks) {
if (!IsWaterFoundation(ent) && !IsInverseFoundation(ent)) {
continue;
}
var bebes = ent.GetComponentsInChildren<CollectibleEntity>();
foreach (var bebe in bebes) {
bebe.Kill(BaseNetworkable.DestroyMode.None);
}
}
player.ChatMessage("Killed all barrels.Try copying now.");
}
[ChatCommand("wb_cfg")]
private void ChatCommandWBCFG(BasePlayer player, string command, string[] args) {
if (!HasAdminPermission(player)) return;
if (args.Length == 0) {
//display all possible config keys and their values
ReusableString = $"{MSG(MSG_CFG_DEFAULT, player.UserIDString)}\n";
foreach (var entry in ConfigValues) {
ReusableString += $"{MSG(MSG_CFG_RUNDOWN_FORMAT, player.UserIDString, entry.Key, entry.Value.formatter(entry.Value.GetSet))}\n";
}
TellMessage(player, ReusableString);
}
else {
if (ConfigValues.ContainsKey(args[0])) {
//has argument[1] been provided? if not, display the full description.if (args.Length > 1)
{
ConfigValues[args[0]].GetSet = args[1];
}
else {
TellMessage(player, $"{MSG(MSG_CFG_DETAILS_FORMAT, player.UserIDString, ConfigValues[args[0]].name, ConfigValues[args[0]].description, ConfigValues[args[0]].valueType, ConfigValues[args[0]].formatter(ConfigValues[args[0]].GetSet))}.Accepted values are {ConfigValues[args[0]].FormatAcceptable()}");
}
}
else {
TellMessage(player, MSG(MSG_CFG_NO_SETTING_FOUND, player.UserIDString));
}
}
}
[ChatCommand("draw_cargo")]
private void ChatCommandDrawCargo(BasePlayer player, string command, string[] args) {
//if (!HasAdminPermission(player)) return;
//stagger to avoid kicking
float timer = 0.02F;
foreach (var oceanNode in TerrainMeta.Path.OceanPatrolFar) {
Instance.timer.Once(timer, () => {
bool setAdmin = false;
if (!player.HasPlayerFlag(BasePlayer.PlayerFlags.IsAdmin)) {
player.SetPlayerFlag(BasePlayer.PlayerFlags.IsAdmin, true);
player.SendNetworkUpdateImmediate();
setAdmin = true;
}
player.SendConsoleCommand("ddraw.sphere", 15F, Color.red, oceanNode, Instance.configData.MinDistanceFromCargoShipNode);
if (setAdmin) {
player.SetPlayerFlag(BasePlayer.PlayerFlags.IsAdmin, false);
player.SendNetworkUpdateImmediate();
}
}
);
timer += 0.02F;
}
}
[ChatCommand("grid_pos")]
private void ChatCommandGridPos(BasePlayer player, string command, string[] args) {
if (!HasAdminPermission(player)) return;
player.ChatMessage($"OUTSIDE MAP GRID: {IsPositionOutsideMapGrid(player.transform.position)}");
}
[ChatCommand("shore_distance")]
private void ChatCommandShoreDistance(BasePlayer player, string command, string[] args) {
if (!HasAdminPermission(player)) return;
player.ChatMessage(GetCoarseDistanceToShore(player.transform.position).ToString());
}
[ChatCommand("give_square")]
private void ChatCommandGiveSquare(BasePlayer player, string command, string[] args) {
if (!HasAdminPermission(player)) return;
int amount = 1;
if (args.Length > 0) {
if (!int.TryParse(args[0], out amount)) {
amount = 1;
}
}
if (amount<0) {
amount = 1;
}
player.GiveItem(MakeFoundationItem(SKIN_FOUNDATION_SQUARE, amount), BaseEntity.GiveItemReason.Generic);
}
[ChatCommand("give_triangle")]
private void ChatCommandGiveTriangle(BasePlayer player, string command, string[] args) {
if (!HasAdminPermission(player)) return;
int amount = 1;
if (args.Length > 0) {
if (!int.TryParse(args[0], out amount)) {
amount = 1;
}
}
if (amount < 0) {
amount = 1;
}
player.GiveItem(MakeFoundationItem(SKIN_FOUNDATION_TRIANGLE, amount), BaseEntity.GiveItemReason.Generic);
}
#endregion
#region CONFIG
public class InteractiveConfigValue {
private Func<object> getter;
private Action<object> setter;
private Func<object, bool> validator;
public Func<object, string> formatter;
private Func<object, object> parser;
public Type valueType;
private double lowerLimit;
private double upperLimit;
//name also identifies it in the dic
public string name;
public string description;
public InteractiveConfigValue(string name, string description, Func<object> getter, Action<object> setter, Type validatorType = null, double lowerLimit = double.NegativeInfinity, double upperLimit = double.PositiveInfinity) {
this.getter = getter;
this.setter = setter;
this.name = name;
this.description = description;
this.lowerLimit = lowerLimit;
this.upperLimit = upperLimit;
formatter = FormatDefault;
if (validatorType == null || validatorType == typeof(bool)) {
validator = ValidateBool;
parser = ParseBool;
formatter = FormatBool;
}
else if (validatorType == typeof(float)) {
validator = ValidateFloat;
parser = ParseFloat;
}
else if (validatorType == typeof(int)) {
validator = ValidateInt;
parser = ParseInt;
}
else if (validatorType == typeof(ulong)) {
validator = ValidateUlong;
parser = ParseUlong;
}
else if (validatorType == typeof(string)) {
validator = ValidateString;
parser = ParseString;
formatter = FormatString;
}
else if (validatorType == typeof(CargoPathHandling)) {
validator = ValidateEnumCargo;
parser = ParseEnumCargo;
formatter
// ... [Code preview truncated due to length. Full source in attached file] ...