Skip to content

Commit

Permalink
Added a new VMod: ChestPvPProtection
Browse files Browse the repository at this point in the history
  • Loading branch information
WhiteFang5 committed Aug 2, 2022
1 parent 6baf5e7 commit f4e3a10
Show file tree
Hide file tree
Showing 11 changed files with 856 additions and 3 deletions.
471 changes: 471 additions & 0 deletions ChestPvPProtection/ChestPvPProtection.csproj

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions ChestPvPProtection/Configs/ChestPvPProtectionSystemConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using BepInEx.Configuration;

namespace VMods.ChestPvPProtection
{
public static class ChestPvPProtectionSystemConfig
{
#region Properties

public static ConfigEntry<bool> ChestPvPProtectionEnabled { get; private set; }

public static ConfigEntry<bool> ChestPvPProtectionSendMessage { get; private set; }

#endregion

#region Public Methods

public static void Initialize(ConfigFile config)
{
ChestPvPProtectionEnabled = config.Bind(nameof(ChestPvPProtection), nameof(ChestPvPProtectionEnabled), false, "Enabled/disable the Chest PvP Protection system.");
ChestPvPProtectionSendMessage = config.Bind(nameof(ChestPvPProtection), nameof(ChestPvPProtectionSendMessage), true, "Enabled/disable the sending of a system message to the player with the PvP Protection buff that's attempting to interact with an enemy player's chest.");
}

#endregion
}
}
158 changes: 158 additions & 0 deletions ChestPvPProtection/Hooks/InventoryHooks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
using HarmonyLib;
using ProjectM;
using ProjectM.Network;
using Unity.Collections;
using Unity.Entities;
using VMods.Shared;
using Wetstone.API;

namespace VMods.ChestPvPProtection
{
[HarmonyPatch]
public static class InventoryHooks
{
#region Events

public delegate void InventoryInteractionEventHandler(Entity eventEntity, VModCharacter vmodCharacter, params Entity[] inventoryEntities);
public static event InventoryInteractionEventHandler InventoryInteractionEvent;
private static void FireInventoryInteractionEvent(Entity eventEntity, VModCharacter vmodCharacter, params Entity[] inventoryEntities) => InventoryInteractionEvent?.Invoke(eventEntity, vmodCharacter, inventoryEntities);

#endregion

#region Private Methods

[HarmonyPatch(typeof(MoveItemBetweenInventoriesSystem), nameof(MoveItemBetweenInventoriesSystem.OnUpdate))]
[HarmonyPrefix]
private static void MoveItemBetweenInventories(MoveItemBetweenInventoriesSystem __instance)
{
if(!VWorld.IsServer || __instance._MoveItemBetweenInventoriesEventQuery == null)
{
return;
}

var server = VWorld.Server;
var entityManager = server.EntityManager;
var networkIdToEntityMap = __instance._NetworkIdSystem._NetworkIdToEntityMap;

var entities = __instance._MoveItemBetweenInventoriesEventQuery.ToEntityArray(Allocator.Temp);
foreach(var entity in entities)
{
var fromCharacter = new VModCharacter(entityManager.GetComponentData<FromCharacter>(entity), entityManager);
var moveEvent = entityManager.GetComponentData<MoveItemBetweenInventoriesEvent>(entity);
FireInventoryInteractionEvent(entity, fromCharacter, moveEvent.FromInventory.GetNetworkedEntity(networkIdToEntityMap)._Entity, moveEvent.ToInventory.GetNetworkedEntity(networkIdToEntityMap)._Entity);
}
}

[HarmonyPatch(typeof(MoveAllItemsBetweenInventoriesSystem), nameof(MoveAllItemsBetweenInventoriesSystem.OnUpdate))]
[HarmonyPrefix]
private static void MoveAllItemsBetweenInventories(MoveAllItemsBetweenInventoriesSystem __instance)
{
if(!VWorld.IsServer || __instance.__MoveAllItemsJob_entityQuery == null)
{
return;
}

var server = VWorld.Server;
var entityManager = server.EntityManager;
var networkIdToEntityMap = __instance._NetworkIdSystem._NetworkIdToEntityMap;

var entities = __instance.__MoveAllItemsJob_entityQuery.ToEntityArray(Allocator.Temp);
foreach(var entity in entities)
{
var fromCharacter = new VModCharacter(entityManager.GetComponentData<FromCharacter>(entity), entityManager);
var moveEvent = entityManager.GetComponentData<MoveAllItemsBetweenInventoriesEvent>(entity);
FireInventoryInteractionEvent(entity, fromCharacter, moveEvent.FromInventory.GetNetworkedEntity(networkIdToEntityMap)._Entity, moveEvent.ToInventory.GetNetworkedEntity(networkIdToEntityMap)._Entity);
}
}

[HarmonyPatch(typeof(DropInventoryItemSystem), nameof(DropInventoryItemSystem.OnUpdate))]
[HarmonyPrefix]
private static void DropInventoryItem(DropInventoryItemSystem __instance)
{
if(!VWorld.IsServer || __instance.__DropInventoryItemJob_entityQuery == null)
{
return;
}

var server = VWorld.Server;
var entityManager = server.EntityManager;
var networkIdToEntityMap = __instance._NetworkIdSystem._NetworkIdToEntityMap;

var entities = __instance.__DropInventoryItemJob_entityQuery.ToEntityArray(Allocator.Temp);
foreach(var entity in entities)
{
var fromCharacter = new VModCharacter(entityManager.GetComponentData<FromCharacter>(entity), entityManager);
var dropEvent = entityManager.GetComponentData<DropInventoryItemEvent>(entity);
FireInventoryInteractionEvent(entity, fromCharacter, dropEvent.Inventory.GetNetworkedEntity(networkIdToEntityMap)._Entity);
}
}

[HarmonyPatch(typeof(SplitItemSystem), nameof(SplitItemSystem.OnUpdate))]
[HarmonyPrefix]
private static void SplitItem(SplitItemSystem __instance)
{
if(!VWorld.IsServer || __instance._Query == null)
{
return;
}

var server = VWorld.Server;
var entityManager = server.EntityManager;
var networkIdToEntityMap = __instance._NetworkIdSystem._NetworkIdToEntityMap;

var entities = __instance._Query.ToEntityArray(Allocator.Temp);
foreach(var entity in entities)
{
var fromCharacter = new VModCharacter(entityManager.GetComponentData<FromCharacter>(entity), entityManager);
var splitEvent = entityManager.GetComponentData<SplitItemEvent>(entity);
FireInventoryInteractionEvent(entity, fromCharacter, splitEvent.Inventory.GetNetworkedEntity(networkIdToEntityMap)._Entity);
}
}

[HarmonyPatch(typeof(SortAllItemsSystem), nameof(SortAllItemsSystem.OnUpdate))]
[HarmonyPrefix]
private static void SortAllItems(SplitItemSystem __instance)
{
if(!VWorld.IsServer || __instance._Query == null)
{
return;
}

var server = VWorld.Server;
var entityManager = server.EntityManager;
var networkIdToEntityMap = __instance._NetworkIdSystem._NetworkIdToEntityMap;

var entities = __instance._Query.ToEntityArray(Allocator.Temp);
foreach(var entity in entities)
{
var fromCharacter = new VModCharacter(entityManager.GetComponentData<FromCharacter>(entity), entityManager);
var sortEvent = entityManager.GetComponentData<SortAllItemsEvent>(entity);
FireInventoryInteractionEvent(entity, fromCharacter, sortEvent.Inventory.GetNetworkedEntity(networkIdToEntityMap)._Entity);
}
}

[HarmonyPatch(typeof(SmartMergeItemsBetweenInventoriesSystem), nameof(SmartMergeItemsBetweenInventoriesSystem.OnUpdate))]
[HarmonyPrefix]
private static void SmartMergeItemsBetweenInventories(SplitItemSystem __instance)
{
if(!VWorld.IsServer || __instance._Query == null)
{
return;
}

var server = VWorld.Server;
var entityManager = server.EntityManager;
var networkIdToEntityMap = __instance._NetworkIdSystem._NetworkIdToEntityMap;

var entities = __instance._Query.ToEntityArray(Allocator.Temp);
foreach(var entity in entities)
{
var fromCharacter = new VModCharacter(entityManager.GetComponentData<FromCharacter>(entity), entityManager);
var mergeEvent = entityManager.GetComponentData<SmartMergeItemsBetweenInventoriesEvent>(entity);
FireInventoryInteractionEvent(entity, fromCharacter, mergeEvent.FromInventory.GetNetworkedEntity(networkIdToEntityMap)._Entity, mergeEvent.ToInventory.GetNetworkedEntity(networkIdToEntityMap)._Entity);
}
}

#endregion
}
}
56 changes: 56 additions & 0 deletions ChestPvPProtection/Plugin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using BepInEx;
using BepInEx.IL2CPP;
using HarmonyLib;
using System.Reflection;
using VMods.Shared;
using Wetstone.API;

namespace VMods.ChestPvPProtection
{
[BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)]
[BepInDependency("xyz.molenzwiebel.wetstone")]
[Reloadable]
public class Plugin : BasePlugin
{
#region Variables

private Harmony _hooks;

#endregion

#region Public Methods

public sealed override void Load()
{
if(VWorld.IsClient)
{
Log.LogMessage($"{PluginInfo.PLUGIN_NAME} only needs to be installed server side.");
return;
}
Utils.Initialize(Log, PluginInfo.PLUGIN_NAME);

ChestPvPProtectionSystemConfig.Initialize(Config);

ChestPvPProtectionSystem.Initialize();

_hooks = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly());

Log.LogInfo($"Plugin {PluginInfo.PLUGIN_NAME} (v{PluginInfo.PLUGIN_VERSION}) is loaded!");
}

public sealed override bool Unload()
{
if(VWorld.IsClient)
{
return true;
}
_hooks?.UnpatchSelf();
ChestPvPProtectionSystem.Deinitialize();
Config.Clear();
Utils.Deinitialize();
return true;
}

#endregion
}
}
76 changes: 76 additions & 0 deletions ChestPvPProtection/Systems/ChestPvPProtectionSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using ProjectM;
using ProjectM.Network;
using ProjectM.Scripting;
using System.Linq;
using Unity.Entities;
using VMods.Shared;
using Wetstone.API;

namespace VMods.ChestPvPProtection
{
public static class ChestPvPProtectionSystem
{
#region Public Methods

public static void Initialize()
{
InventoryHooks.InventoryInteractionEvent += OnInventoryInteraction;
}

public static void Deinitialize()
{
InventoryHooks.InventoryInteractionEvent -= OnInventoryInteraction;
}

#endregion

#region Private Methods

private static void OnInventoryInteraction(Entity eventEntity, VModCharacter vmodCharacter, params Entity[] inventoryEntities)
{
if(!ChestPvPProtectionSystemConfig.ChestPvPProtectionEnabled.Value)
{
return;
}

var server = VWorld.Server;
var entityManager = server.EntityManager;
var teamChecker = server.GetExistingSystem<ServerScriptMapper>()._ServerGameManager._TeamChecker;

if(!vmodCharacter.HasBuff(Utils.PvPProtectionBuff) || vmodCharacter.User.IsAdmin)
{
return;
}

var playerTeam = entityManager.GetComponentData<Team>(vmodCharacter.FromCharacter.Character);
var teams = inventoryEntities.Select(FindTeam);

// Check if the player is not an ally and the inventory isn't neutral (i.e. an npc chest) of any inventory
if(teams.Any(team => !teamChecker.IsAllies(playerTeam, team) && !teamChecker.IsNeutral(team)))
{
entityManager.AddComponent<DestroyTag>(eventEntity);
if(ChestPvPProtectionSystemConfig.ChestPvPProtectionSendMessage.Value)
{
Utils.SendMessage(vmodCharacter.FromCharacter.User, $"Cannot interact with enemy Chests/Workstations while PvP Protected", ServerChatMessageType.System);
}
}

// Nested Method(s)
Team FindTeam(Entity entity)
{
if(entityManager.HasComponent<Team>(entity))
{
return entityManager.GetComponentData<Team>(entity);
}
else
{
// Workstations don't have a team but are attached, thus retrieve the team of the station's parent.
var attach = entityManager.GetComponentData<Attach>(entity);
return entityManager.GetComponentData<Team>(attach.Parent);
}
}
}

#endregion
}
}
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,8 @@ Both the amount of offenses before being punishes as well as the punishment itse
* % of reduced Spell Power

</details>

## Chest PvP Protection
A server-side only mod that prevents looting of enemy player chests/workstations by players with the PvP Protection buff.

Besides looting, it'll also prevent moving, sorting, swapping and merging ("compilsively count") of items within those chests/workstations.
13 changes: 13 additions & 0 deletions Shared/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public static class Utils

public static readonly PrefabGUID SevereGarlicDebuff = new(1582196539);

public static readonly PrefabGUID PvPProtectionBuff = new(1111481396);

#endregion

#region Variables
Expand Down Expand Up @@ -291,6 +293,17 @@ public static void LogAllComponentTypes(Entity entity, EntityManager? entityMana
Logger.LogMessage($"---");
}

public static void LogAllComponentTypes(EntityQuery entityQuery)
{
var types = entityQuery.GetQueryTypes();
Logger.LogMessage($"---");
foreach(var t in types)
{
Logger.LogMessage($"Query Component Type: {t}");
}
Logger.LogMessage($"---");
}

#endregion

#region Nested
Expand Down
8 changes: 8 additions & 0 deletions Shared/VModCharacter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ public readonly struct VModCharacter
Character = user.LocalCharacter._Entity,
});

public VModCharacter(FromCharacter fromCharacter, EntityManager? entityManager = null)
{
entityManager ??= Utils.CurrentWorld.EntityManager;
User = entityManager.Value.GetComponentData<User>(fromCharacter.User);
Character = entityManager.Value.GetComponentData<PlayerCharacter>(fromCharacter.Character);
FromCharacter = fromCharacter;
}

public VModCharacter(Entity userEntity, Entity charEntity, EntityManager? entityManager = null)
{
entityManager ??= Utils.CurrentWorld.EntityManager;
Expand Down
25 changes: 25 additions & 0 deletions Thunderstone/ChestPvPProtection/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Chest PvP Protection
A server-side only mod that prevents looting of enemy player chests/workstations by players with the PvP Protection buff.

Besides looting, it'll also prevent moving, sorting, swapping and merging ("compilsively count") of items within those chests/workstations.

## How to manually install
* Install [BepInEx](https://v-rising.thunderstore.io/package/BepInEx/BepInExPack_V_Rising/)
* Install [Wetstone](https://v-rising.thunderstore.io/package/molenzwiebel/Wetstone/)
* (Locally hosted games only) Install [ServerLaunchFix](https://v-rising.thunderstore.io/package/Mythic/ServerLaunchFix/)
* Extract the Vmods._mod-name_.dll
* Move the desired mod(s) to the `[VRising (server) folder]/BepInEx/WetstonePlugins/`
* Launch the server (or game) to auto-generate the config files
* Edit the configs as you desire (found in `[VRising (server) folder]/BepInEx/config/`)
* Reload the mods using the Wetstone commands (by default F6 for client-side mods, and/or `!reload` for server-side mods)
* If this doesn't work, or isn't enabled, restart the server/game

## Commands
Most of the VMods come with a set of commands that can be used. To see the available commands, by default a player or admin can use `!help`.
Normal players won't see the Admin-only commands listed.
The prefix (`!`) can be changed on a per-mod basis.
To prevent spam/abuse there's also a command cooldown for non-admins, this value can also be tweaked on a per-mod basis.
Commands can also be disabled completely on a per-mod basis.

## More Details
* [ChangeLog](https://github.com/WhiteFang5/VMods/blob/master/CHANGELOG.md#chest-pvp-protection)
Loading

0 comments on commit f4e3a10

Please sign in to comment.