Initial version of FinchWorld and TungstonsDumper
This commit is contained in:
@@ -0,0 +1,415 @@
|
|||||||
|
using Oxide.Core;
|
||||||
|
using Oxide.Core.Plugins;
|
||||||
|
using Rust;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Goal:
|
||||||
|
|
||||||
|
I'd like to be able to play Rust as though it's a pure survival game which is as realistic as
|
||||||
|
possible. To that end, I'd like inanimate objects to *stay put* unless the player moves them. No
|
||||||
|
hyper-fast entropy of any kind. Conversely, once a player moves something, it should *stay moved*:
|
||||||
|
no respawning groceries in "abandoned" supermarkets.
|
||||||
|
|
||||||
|
Similarly, once a plant is picked, it won't grow back. If you eat all the corn, then corn is now
|
||||||
|
extinct on your island. Even then, growing your own crops takes a (slightly) more realistic amount
|
||||||
|
of time, so you really have to plan ahead.
|
||||||
|
|
||||||
|
Current Support:
|
||||||
|
|
||||||
|
- vehicles last forever once spawned, and never re-spawn once destroyed
|
||||||
|
- wild crops do not respawn in the same locations
|
||||||
|
- loot does not respawn in the same locations once harvested
|
||||||
|
- loot crates don't magically disappear once looted
|
||||||
|
- players can put their own stuff back into loot crates
|
||||||
|
|
||||||
|
Future Support:
|
||||||
|
|
||||||
|
- similar sorts of constraints around monuments
|
||||||
|
- loot piles stay put once looted
|
||||||
|
|
||||||
|
Open Issues:
|
||||||
|
|
||||||
|
- car parts baskets need to have model swapped when they're empty
|
||||||
|
- same with food crates
|
||||||
|
- not sure whether this is going to have the desired effect on monument loot
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Oxide.Plugins {
|
||||||
|
|
||||||
|
[Info("FinchWorld", "TungstonMiner", "0.1.0")]
|
||||||
|
[Description("Simulate a real island where resources, once looted, are gone forever")]
|
||||||
|
public class FinchWorld : RustPlugin {
|
||||||
|
|
||||||
|
// Constants ///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private const int FOREVER = 60 * 24 * 60; // 60 days, in minutes
|
||||||
|
|
||||||
|
private const string KNOWN_SPAWNS_FILE = "FinchWorld.KnownSpawns";
|
||||||
|
|
||||||
|
private const float KNOWN_SPAWN_SAVE_INTERVAL = 60f; // in seconds
|
||||||
|
|
||||||
|
private const float SPAWN_RADIUS = 10f;
|
||||||
|
|
||||||
|
private static readonly List<string> VEHICLE_DECAY_SETTINGS = new List<string> {
|
||||||
|
"basesubmarine.deepwaterdecayminutes",
|
||||||
|
"basesubmarine.outsidedecayminutes",
|
||||||
|
"bike.outsidedecayminutes",
|
||||||
|
"hotairballoon.outsidedecayminutes",
|
||||||
|
"modularcar.outsidedecayminutes",
|
||||||
|
"motorrowboat.decaystartdelayminutes",
|
||||||
|
"motorrowboat.deepwaterdecayminutes",
|
||||||
|
"motorrowboat.outsidedecayminutes",
|
||||||
|
"playerhelicopter.insidedecayminutes",
|
||||||
|
"playerhelicopter.outsidedecayminutes",
|
||||||
|
"ridablehorse.decayminutes",
|
||||||
|
"snowmobile.outsidedecayminutes",
|
||||||
|
"traincar.decayminutes",
|
||||||
|
"traincarunloadable.decayminutesafterunload",
|
||||||
|
"tugboat.tugdecayminutes",
|
||||||
|
"tugboat.tugdecaystartdelayminutes"
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly List<string> FIXED_POPULATIONS = new List<string> {
|
||||||
|
"modularcar.population",
|
||||||
|
"minicopter.population",
|
||||||
|
"scraptransporthelicopter.population",
|
||||||
|
"traincar.population",
|
||||||
|
"hotairballoon.population",
|
||||||
|
"rhib.rhibpopulation",
|
||||||
|
"motorrowboat.population",
|
||||||
|
"bike.motorbikemonumentpopulation",
|
||||||
|
"bike.pedalmonumentpopulation",
|
||||||
|
"bike.pedalroadsidepopulation",
|
||||||
|
"metaldetectorsource.population"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Member Variables ////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private HashSet<string> knownSpawns = null;
|
||||||
|
|
||||||
|
private Timer knownSpawnTimer = null;
|
||||||
|
|
||||||
|
private bool enforceKnownSpawnRules = true;
|
||||||
|
|
||||||
|
private bool shouldCloneJunkPiles = true;
|
||||||
|
|
||||||
|
// Hooks ///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// prevent anything from decaying, ever
|
||||||
|
object CanDecay(BaseCombatEntity entity) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// deal with various non-permanent entities spawning when the shouldn't
|
||||||
|
object OnEntitySpawned(BaseNetworkable networkable) {
|
||||||
|
var entity = networkable as BaseEntity;
|
||||||
|
if (entity == null) return null;
|
||||||
|
|
||||||
|
var isJunkPile = entity.ShortPrefabName.StartsWith("junkpile_");
|
||||||
|
if (isJunkPile) {
|
||||||
|
this.CloneJunkPile(entity);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.knownSpawns == null) return null;
|
||||||
|
|
||||||
|
var isControlledEntity = (
|
||||||
|
(entity is LootContainer) ||
|
||||||
|
(entity is CollectibleEntity) ||
|
||||||
|
(entity is OreResourceEntity)
|
||||||
|
);
|
||||||
|
if (!isControlledEntity) return null;
|
||||||
|
|
||||||
|
Vector3 position = entity.transform.position;
|
||||||
|
int x = Mathf.FloorToInt(position.x / SPAWN_RADIUS);
|
||||||
|
int y = Mathf.FloorToInt(position.y / SPAWN_RADIUS);
|
||||||
|
int z = Mathf.FloorToInt(position.y / SPAWN_RADIUS);
|
||||||
|
var key = $"{x},{y},{z}";
|
||||||
|
|
||||||
|
if (this.knownSpawns.Contains(key)) return false;
|
||||||
|
this.knownSpawns.Add(key);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// // modify loot containers to be persistent storage
|
||||||
|
// object OnLootEntity(BasePlayer player, BaseEntity entity) {
|
||||||
|
// var container = entity as LootContainer;
|
||||||
|
// if (container == null) return null;
|
||||||
|
//
|
||||||
|
// container.destroyOnEmpty = false;
|
||||||
|
// container.lootDefinition = null;
|
||||||
|
// container.panelName = "generic"; // same as small wooden box
|
||||||
|
//
|
||||||
|
// container.inventory.SetFlag(ItemContainer.Flag.NoItemInput, false);
|
||||||
|
// container.inventory.SetFlag(ItemContainer.Flag.IsLocked, false);
|
||||||
|
// container.inventory.canAcceptItem = (item, slot) => true;
|
||||||
|
//
|
||||||
|
// container.SendNetworkUpdate(); // push changes to client
|
||||||
|
//
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// adjust settings to promote object persistence
|
||||||
|
void OnServerInitialized() {
|
||||||
|
this.AdjustVehiclePersistence();
|
||||||
|
this.SetUpKnownSpawnSaving();
|
||||||
|
this.SuppressBuildingDecay();
|
||||||
|
this.SuppressPopulationReplenishment();
|
||||||
|
|
||||||
|
Puts("FinchWorld is active");
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop tracking loot spawns
|
||||||
|
void OnUnload() {
|
||||||
|
this.TearDownKnownSpawnSaving();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper Methods //////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// change vehicle settings so they last forever
|
||||||
|
void AdjustVehiclePersistence() {
|
||||||
|
Puts("Adjusting vehicle persistence...");
|
||||||
|
foreach (var setting in VEHICLE_DECAY_SETTINGS) {
|
||||||
|
ConsoleSystem.Run(ConsoleSystem.Option.Server, $"{setting} {FOREVER}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make plants take a week (instead of a day) to grow to maturity
|
||||||
|
void AdjustAgriculture() {
|
||||||
|
Puts("Adjusting agricultural settings...");
|
||||||
|
ConsoleSystem.Run(ConsoleSystem.Option.Server, "server.planttick 120");
|
||||||
|
ConsoleSystem.Run(ConsoleSystem.Option.Server, "server.planttickscale 0.2857");
|
||||||
|
}
|
||||||
|
|
||||||
|
// clones junk piles so the game doesn't automatically nuke them
|
||||||
|
void CloneJunkPile(BaseEntity entity) {
|
||||||
|
Puts($"Setting timer to clone a {entity.ShortPrefabName}");
|
||||||
|
timer.Once(3f, () => {
|
||||||
|
try {
|
||||||
|
if (!this.shouldCloneJunkPiles) {
|
||||||
|
Puts("Skipping because we shouldn't clone junk piles right now");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.shouldCloneJunkPiles = false;
|
||||||
|
this.enforceKnownSpawnRules = false;
|
||||||
|
|
||||||
|
Puts($"Cloning junk pile at {entity.transform.position}");
|
||||||
|
var clonedEntity = this.DeepCloneEntity(entity);
|
||||||
|
if (clonedEntity != null) {
|
||||||
|
Puts("Moving the old pile under ground");
|
||||||
|
this.MoveUnderTerrain(entity);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.enforceKnownSpawnRules = true;
|
||||||
|
this.shouldCloneJunkPiles = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// clones an entire item tree
|
||||||
|
Item DeepCloneItem(Item source) {
|
||||||
|
var target = ItemManager.Create(source.info, source.amount, source.skin);
|
||||||
|
target.condition = source.condition;
|
||||||
|
target.maxCondition = source.maxCondition;
|
||||||
|
target.name = source.name;
|
||||||
|
|
||||||
|
if (source.instanceData != null) {
|
||||||
|
Puts($"Dumping fields on item.instanceData ({source.instanceData.GetType()})");
|
||||||
|
var fields = source.instanceData.GetType().GetFields(
|
||||||
|
System.Reflection.BindingFlags.Public |
|
||||||
|
System.Reflection.BindingFlags.NonPublic |
|
||||||
|
System.Reflection.BindingFlags.Instance);
|
||||||
|
foreach (var field in fields) {
|
||||||
|
Puts(field.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
Puts($"Dumping properties on item.instanceData ({source.instanceData.GetType()})");
|
||||||
|
var properties = source.instanceData.GetType().GetProperties(
|
||||||
|
System.Reflection.BindingFlags.Public |
|
||||||
|
System.Reflection.BindingFlags.NonPublic |
|
||||||
|
System.Reflection.BindingFlags.Instance);
|
||||||
|
foreach (var property in properties) {
|
||||||
|
Puts(property.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// target.instanceData = new ProtoBuf.Item.InstanceData() {
|
||||||
|
// dataInt = source.instanceData.dataInt,
|
||||||
|
// subEntity = source.instanceData.subEntity,
|
||||||
|
// blueprintTarget = source.instanceData.blueprintTarget,
|
||||||
|
// blueprintAmount = source.instanceData.blueprintAmount,
|
||||||
|
// ShouldPool = false,
|
||||||
|
// ShouldReset = false
|
||||||
|
// };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source.contents != null) {
|
||||||
|
target.contents = new ItemContainer();
|
||||||
|
target.contents.ServerInitialize(null, source.contents.capacity);
|
||||||
|
target.contents.GiveUID();
|
||||||
|
|
||||||
|
foreach (var sourceChild in source.contents.itemList) {
|
||||||
|
var targetChild = this.DeepCloneItem(sourceChild);
|
||||||
|
if (targetChild == null) {
|
||||||
|
Puts($"Failed to clone child item {sourceChild.name}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!targetChild.MoveToContainer(target.contents)) {
|
||||||
|
Puts($"Failed to move cloned child item to parent item {target.name}");
|
||||||
|
target.Remove();
|
||||||
|
targetChild.Remove();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clone an entire entity tree
|
||||||
|
BaseEntity DeepCloneEntity(BaseEntity source) {
|
||||||
|
var target = GameManager.server.CreateEntity(
|
||||||
|
source.PrefabName,
|
||||||
|
source.transform.position,
|
||||||
|
source.transform.rotation,
|
||||||
|
true // start active
|
||||||
|
);
|
||||||
|
if (target == null) {
|
||||||
|
Puts($"Failed to target {source.ShortPrefabName} at {source.transform.position}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (target.GetType() != source.GetType()) {
|
||||||
|
Puts($"Clone type ({target.GetType()}) " +
|
||||||
|
$"doesn't match the original ({source.GetType()})");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.enableSaving = true;
|
||||||
|
target.Spawn();
|
||||||
|
target.SendNetworkUpdate();
|
||||||
|
|
||||||
|
var sourceContainer = source as LootContainer;
|
||||||
|
var targetContainer = target as LootContainer;
|
||||||
|
if ((sourceContainer != null) && (targetContainer != null)) {
|
||||||
|
targetContainer.destroyOnEmpty = false;
|
||||||
|
targetContainer.lootDefinition = null;
|
||||||
|
targetContainer.panelTitle = "FinchWorld Loot";
|
||||||
|
targetContainer.inventory.SetFlag(ItemContainer.Flag.NoItemInput, false);
|
||||||
|
targetContainer.inventory.SetFlag(ItemContainer.Flag.IsLocked, false);
|
||||||
|
targetContainer.inventory.canAcceptItem = (item, slot) => true;
|
||||||
|
targetContainer.SendNetworkUpdate();
|
||||||
|
|
||||||
|
for (var index = 0; index < sourceContainer.inventory.capacity; index++) {
|
||||||
|
var sourceItem = sourceContainer.inventory.GetSlot(index);
|
||||||
|
if (sourceItem == null) continue;
|
||||||
|
|
||||||
|
var targetItem = this.DeepCloneItem(sourceItem);
|
||||||
|
if (targetItem == null) continue;
|
||||||
|
if (!targetItem.MoveToContainer(targetContainer.inventory)) {
|
||||||
|
targetItem.Remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var sourceChild in source.children) {
|
||||||
|
var sourceChildEntity = sourceChild as BaseEntity;
|
||||||
|
if (sourceChildEntity == null) continue;
|
||||||
|
|
||||||
|
var targetChildEntity = this.DeepCloneEntity(sourceChildEntity);
|
||||||
|
if (targetChildEntity == null) {
|
||||||
|
Puts(
|
||||||
|
$"Aborting clone: failed to clone child entity " +
|
||||||
|
$"({sourceChildEntity.ShortPrefabName}) at " +
|
||||||
|
$"{sourceChildEntity.transform.position}"
|
||||||
|
);
|
||||||
|
target.Kill();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
targetChildEntity.SetParent(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload spawn locations from disk
|
||||||
|
void LoadKnownSpawns() {
|
||||||
|
if (this.knownSpawns != null) {
|
||||||
|
Puts("Cannot load loot spawns as they've already been loaded.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = new List<string>();
|
||||||
|
try {
|
||||||
|
data = Interface.Oxide.DataFileSystem.ReadObject<List<string>>(
|
||||||
|
KNOWN_SPAWNS_FILE);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Puts($"Failed to load known loot spawns ({e.Message}). Assuming none exist.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.knownSpawns = new HashSet<string>();
|
||||||
|
foreach (var key in data) {
|
||||||
|
this.knownSpawns.Add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// move a given entity (and any children) underground
|
||||||
|
void MoveUnderTerrain(BaseEntity entity) {
|
||||||
|
entity.transform.position = new Vector3(
|
||||||
|
entity.transform.position.x,
|
||||||
|
entity.transform.position.y - 20f,
|
||||||
|
entity.transform.position.z
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach (var child in entity.children) {
|
||||||
|
this.MoveUnderTerrain(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save spawn locations to disk
|
||||||
|
void SaveKnownSpawns() {
|
||||||
|
var data = new List<string>(this.knownSpawns);
|
||||||
|
Interface.Oxide.DataFileSystem.WriteObject(KNOWN_SPAWNS_FILE, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the spawn tracking system
|
||||||
|
void SetUpKnownSpawnSaving() {
|
||||||
|
if (this.knownSpawns != null) {
|
||||||
|
Puts("Aborting loot spawn setup because it's already running.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.LoadKnownSpawns();
|
||||||
|
this.knownSpawnTimer =
|
||||||
|
timer.Every(KNOWN_SPAWN_SAVE_INTERVAL, () => this.SaveKnownSpawns());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop the spawn tracking system
|
||||||
|
void TearDownKnownSpawnSaving() {
|
||||||
|
this.SaveKnownSpawns();
|
||||||
|
this.knownSpawnTimer?.Destroy();
|
||||||
|
this.knownSpawnTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't allow player buildings to decay
|
||||||
|
void SuppressBuildingDecay() {
|
||||||
|
Puts("Suppressing accelerated building decay...");
|
||||||
|
ConsoleSystem.Run(ConsoleSystem.Option.Server, "decay.upkeep false");
|
||||||
|
ConsoleSystem.Run(ConsoleSystem.Option.Server, "decay.scale 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't allow non-renewable populations to repopulate
|
||||||
|
void SuppressPopulationReplenishment() {
|
||||||
|
Puts("Suppressing magically appearing vehicles");
|
||||||
|
foreach (var population in FIXED_POPULATIONS) {
|
||||||
|
ConsoleSystem.Run(ConsoleSystem.Option.Server, $"{population} 0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -168,14 +168,26 @@ namespace Oxide.Plugins {
|
|||||||
|
|
||||||
private void CheckSleepiness() {
|
private void CheckSleepiness() {
|
||||||
var today = this.GetDay();
|
var today = this.GetDay();
|
||||||
|
var now = this.GetTime();
|
||||||
|
|
||||||
if (this.lastYawnDay == today) return;
|
if (this.lastYawnDay == today) return;
|
||||||
|
|
||||||
var now = this.GetTime();
|
|
||||||
if ((now > GoodNightsSleep.BED_TIME) && (now < GoodNightsSleep.BED_TIME + 1f)) {
|
if ((now > GoodNightsSleep.BED_TIME) && (now < GoodNightsSleep.BED_TIME + 1f)) {
|
||||||
|
Puts("Players standing on their beds should be sleepy");
|
||||||
this.lastYawnDay = today;
|
this.lastYawnDay = today;
|
||||||
|
|
||||||
foreach (var player in BasePlayer.activePlayerList) {
|
foreach (var player in BasePlayer.activePlayerList) {
|
||||||
Effect.server.Run(GoodNightsSleep.YAWN_SOUND, player.transform.position);
|
var bag = this.FindNearbySleepingBag(player);
|
||||||
|
if (bag == null) {
|
||||||
|
Puts($"{player.displayName} isn't standing on a sleeping bag");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!this.IsInBase(bag)) {
|
||||||
|
Puts($"{player.displayName}'s sleeping bag isn't in a base");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.ChatMessage("You feel your eyelids drooping...");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -263,14 +275,15 @@ namespace Oxide.Plugins {
|
|||||||
var position = bag.transform.position + Vector3.up * 0.5f;
|
var position = bag.transform.position + Vector3.up * 0.5f;
|
||||||
|
|
||||||
// check that there's some kind of player-placed block under the sleeping bag
|
// check that there's some kind of player-placed block under the sleeping bag
|
||||||
RaycastHit hit;
|
foreach (var hit in Physics.RaycastAll(position, Vector3.down, 2f)) {
|
||||||
Physics.Raycast(position, Vector3.down, out hit, 2f);
|
if (!hit.collider) continue;
|
||||||
if (hit.collider.GetComponentInParent<BuildingBlock>() == null) {
|
var obj = hit.collider.gameObject;
|
||||||
Puts("Sleeping bag is not on a foundation");
|
|
||||||
return false;
|
if (obj.GetComponentInParent<BuildingBlock>()) return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
Puts("Sleeping bag is not on a foundation");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsUnderRoof(SleepingBag bag) {
|
private bool IsUnderRoof(SleepingBag bag) {
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ namespace Oxide.Plugins {
|
|||||||
[Description("Re-read the server configs after loading to override gamemode settings")]
|
[Description("Re-read the server configs after loading to override gamemode settings")]
|
||||||
public class RereadConfigs : RustPlugin {
|
public class RereadConfigs : RustPlugin {
|
||||||
|
|
||||||
|
private const float DELAY = 10f;
|
||||||
|
|
||||||
void OnServerInitialized() {
|
void OnServerInitialized() {
|
||||||
timer.Once(10f, () => {
|
timer.Once(DELAY, () => {
|
||||||
Server.Command("server.readcfg");
|
Server.Command("server.readcfg");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using Oxide.Core;
|
||||||
|
using Oxide.Core.Plugins;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Oxide.Plugins {
|
||||||
|
|
||||||
|
[Info("TungstonsDumper", "TungstonMiner", "0.1.0")]
|
||||||
|
[Description("Add commands to dump data helpful in writing mods.")]
|
||||||
|
public class TungstonsDumper : RustPlugin {
|
||||||
|
|
||||||
|
// Constants ///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private const string PREFAB_LOG = "tungstonsdumper-prefabs.json";
|
||||||
|
|
||||||
|
private const string TYPE_LOG = "tungstonsdumper-types.json";
|
||||||
|
|
||||||
|
// Hooks ///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
void OnServerInitialized() {
|
||||||
|
Puts("Tungston's Dumper is active");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Console Commands ////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
[ConsoleCommand("dump.types")]
|
||||||
|
private void DumpTypes(ConsoleSystem.Arg arg) {
|
||||||
|
if (arg.Connection.authLevel < 2) {
|
||||||
|
arg.ReplyWith("You must be an admin to run this command");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var types = new List<Type>() {
|
||||||
|
GameManager.server.GetType(),
|
||||||
|
typeof(BasePlayer),
|
||||||
|
typeof(BaseEntity),
|
||||||
|
typeof(Item),
|
||||||
|
typeof(RustPlugin)
|
||||||
|
};
|
||||||
|
|
||||||
|
var data = new Dictionary<string, object>();
|
||||||
|
foreach (var type in types) {
|
||||||
|
this.ExtractTypeData(type, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.WriteData(data, TYPE_LOG);
|
||||||
|
arg.ReplyWith($"Dumped type data to {TYPE_LOG}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// [ConsoleCommand("dump.prefabs")]
|
||||||
|
// private void DumpPrefabs(ConsoleSystem.Arg arg) {
|
||||||
|
// if (arg.Connection.authLevel < 2) {
|
||||||
|
// arg.ReplyWith("You must be an admin to run this command");
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var result = new List<object>();
|
||||||
|
// foreach (var (id, attributeSet) in PrefabAttribute.server.prefabs) {
|
||||||
|
// result.Add(new {
|
||||||
|
// name = attributeSet.name
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// this.WriteData(result, PREFAB_LOG);
|
||||||
|
// arg.ReplyWith($"Dumped prefabs to {PREFAB_LOG}");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Helper Methods //////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private void ExtractTypeData(Type type, Dictionary<string, object> data) {
|
||||||
|
if (type == null) return;
|
||||||
|
if (data.ContainsKey(type.FullName)) return;
|
||||||
|
|
||||||
|
this.ExtractTypeData(type.BaseType, data);
|
||||||
|
|
||||||
|
var result = new Dictionary<string, object>() {
|
||||||
|
{ "BaseType", type.BaseType != null ? type.BaseType.FullName : "None" },
|
||||||
|
{ "Fields", new List<string>() },
|
||||||
|
{ "Properties", new List<string>() },
|
||||||
|
{ "Methods", new List<string>() }
|
||||||
|
};
|
||||||
|
|
||||||
|
var flags =
|
||||||
|
System.Reflection.BindingFlags.Public |
|
||||||
|
System.Reflection.BindingFlags.NonPublic |
|
||||||
|
System.Reflection.BindingFlags.Instance |
|
||||||
|
System.Reflection.BindingFlags.DeclaredOnly;
|
||||||
|
|
||||||
|
foreach (var field in type.GetFields(flags)) {
|
||||||
|
((List<string>) result["Fields"]).Add(field.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var property in type.GetProperties(flags)) {
|
||||||
|
((List<string>) result["Properties"]).Add(property.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var method in type.GetMethods(flags)) {
|
||||||
|
((List<string>) result["Methods"]).Add(method.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
data[type.FullName] = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteData(object obj, string fileName) {
|
||||||
|
File.WriteAllText(
|
||||||
|
Path.Combine(Interface.Oxide.LogDirectory, fileName),
|
||||||
|
JsonConvert.SerializeObject(obj, Formatting.Indented)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user