Skip to content

Commit

Permalink
Added the current VMods code and a README.md
Browse files Browse the repository at this point in the history
  • Loading branch information
WhiteFang5 committed Jul 30, 2022
1 parent 2e911de commit e453611
Show file tree
Hide file tree
Showing 46 changed files with 7,416 additions and 0 deletions.
403 changes: 403 additions & 0 deletions .gitignore

Large diffs are not rendered by default.

474 changes: 474 additions & 0 deletions BloodRefill/BloodRefill.csproj

Large diffs are not rendered by default.

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

namespace VMods.BloodRefill
{
public static class BloodRefillConfig
{
#region Properties

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

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

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

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

public static ConfigEntry<float> BloodRefillDifferentBloodTypeMultiplier { get; private set; }

public static ConfigEntry<int> BloodRefillVBloodRefillType { get; private set; }

public static ConfigEntry<float> BloodRefillVBloodRefillMultiplier { get; private set; }

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

public static ConfigEntry<float> BloodRefillAmount { get; private set; }

public static ConfigEntry<float> BloodRefillMultiplier { get; private set; }

#endregion

#region Public Methods

public static void Initialize(ConfigFile config)
{
BloodRefillEnabled = config.Bind(nameof(BloodRefillConfig), nameof(BloodRefillEnabled), false, "Enabled/disable the blood refilling system.");
BloodRefillRequiresFeeding = config.Bind(nameof(BloodRefillConfig), nameof(BloodRefillRequiresFeeding), true, "When enabled, blood can only be refilled when feeding (i.e. when aborting the feed).");
BloodRefillRequiresSameBloodType = config.Bind(nameof(BloodRefillConfig), nameof(BloodRefillRequiresSameBloodType), false, "When enabled, blood can only be refilled when the target has the same blood type.");
BloodRefillExcludeVBloodFromSameBloodTypeCheck = config.Bind(nameof(BloodRefillConfig), nameof(BloodRefillExcludeVBloodFromSameBloodTypeCheck), true, "When enabled, V-blood is excluded from the 'same blood type' check (i.e. it's always considered to be 'the same blood type' as the player's blood type).");
BloodRefillVBloodRefillType = config.Bind(nameof(BloodRefillConfig), nameof(BloodRefillVBloodRefillType), 2, "0 = disabled (i.e. normal refill); 1 = fully refill; 2 = refill based on V-blood monster level.");
BloodRefillVBloodRefillMultiplier = config.Bind(nameof(BloodRefillConfig), nameof(BloodRefillVBloodRefillMultiplier), 0.1f, $"[Only applies when {nameof(BloodRefillVBloodRefillType)} is set to 2] The multiplier used in the v-blood refill calculation ('EnemyLevel' * '{nameof(BloodRefillVBloodRefillMultiplier)}' * '{nameof(BloodRefillMultiplier)}').");
BloodRefillRandomRefill = config.Bind(nameof(BloodRefillConfig), nameof(BloodRefillRandomRefill), true, "When enabled, the amount of refilled blood is randomized (between 1 and the calculated refillable amount).");
BloodRefillAmount = config.Bind(nameof(BloodRefillConfig), nameof(BloodRefillAmount), 2.0f, "The maximum amount of blood to refill with no level difference, a matching blood type and quality (Expressed in Litres of blood).");
BloodRefillMultiplier = config.Bind(nameof(BloodRefillConfig), nameof(BloodRefillMultiplier), 1.0f, $"The multiplier used in the blood refill calculation. [Formula: (('Enemy Level' / 'Player Level') * ((100 - ('Player Blood Quality %' - 'Enemy Blood Quality %')) / 100)) * '{nameof(BloodRefillAmount)}' * '(If applicable) {nameof(BloodRefillDifferentBloodTypeMultiplier)}' * '{nameof(BloodRefillMultiplier)}']");
BloodRefillDifferentBloodTypeMultiplier = config.Bind(nameof(BloodRefillConfig), nameof(BloodRefillDifferentBloodTypeMultiplier), 0.1f, $"The multiplier used in the blood refill calculation as a penalty for feeding on a different blood type (only works when {nameof(BloodRefillRequiresSameBloodType)} is disabled).");
}

#endregion
}
}
39 changes: 39 additions & 0 deletions BloodRefill/Hooks/DeathHook.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using HarmonyLib;
using ProjectM;
using Unity.Collections;
using Wetstone.API;

namespace VMods.BloodRefill
{
[HarmonyPatch]
public static class DeathHook
{
#region Events

public delegate void DeathEventHandler(DeathEvent deathEvent);
public static event DeathEventHandler DeathEvent;
private static void FireDeathEvent(DeathEvent deathEvent) => DeathEvent?.Invoke(deathEvent);

#endregion

#region Public Methods

[HarmonyPatch(typeof(DeathEventListenerSystem), nameof(DeathEventListenerSystem.OnUpdate))]
[HarmonyPostfix]
private static void OnUpdate(DeathEventListenerSystem __instance)
{
if(!VWorld.IsServer || __instance._DeathEventQuery == null)
{
return;
}

NativeArray<DeathEvent> deathEvents = __instance._DeathEventQuery.ToComponentDataArray<DeathEvent>(Allocator.Temp);
foreach(DeathEvent deathEvent in deathEvents)
{
FireDeathEvent(deathEvent);
}
}

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

namespace VMods.BloodRefill
{
[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);

CommandSystemConfig.Initialize(Config);
BloodRefillConfig.Initialize(Config);

CommandSystem.Initialize();
BloodRefillSystem.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();
BloodRefillSystem.Deinitialize();
CommandSystem.Deinitialize();
Config.Clear();
Utils.Deinitialize();
return true;
}

#endregion
}
}
242 changes: 242 additions & 0 deletions BloodRefill/Systems/BloodRefillSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
using ProjectM;
using ProjectM.Network;
using System;
using System.Linq;
using Unity.Entities;
using VMods.Shared;
using Wetstone.API;
using Random = UnityEngine.Random;

namespace VMods.BloodRefill
{
public static class BloodRefillSystem
{
#region Public Methods

public static void Initialize()
{
DeathHook.DeathEvent += OnDeath;
}

public static void Deinitialize()
{
DeathHook.DeathEvent -= OnDeath;
}

#endregion

#region Private Methods

private static void OnDeath(DeathEvent deathEvent)
{
EntityManager entityManager = VWorld.Server.EntityManager;

// Make sure a player killed an appropriate monster
if(!BloodRefillConfig.BloodRefillEnabled.Value ||
!entityManager.HasComponent<PlayerCharacter>(deathEvent.Killer) ||
!entityManager.HasComponent<Equipment>(deathEvent.Killer) ||
!entityManager.HasComponent<Blood>(deathEvent.Killer) ||
!entityManager.HasComponent<Movement>(deathEvent.Died) ||
!entityManager.HasComponent<UnitLevel>(deathEvent.Died) ||
!entityManager.HasComponent<BloodConsumeSource>(deathEvent.Died))
{
return;
}

PlayerCharacter playerCharacter = entityManager.GetComponentData<PlayerCharacter>(deathEvent.Killer);
Equipment playerEquipment = entityManager.GetComponentData<Equipment>(deathEvent.Killer);
Blood playerBlood = entityManager.GetComponentData<Blood>(deathEvent.Killer);
UnitLevel unitLevel = entityManager.GetComponentData<UnitLevel>(deathEvent.Died);
BloodConsumeSource bloodConsumeSource = entityManager.GetComponentData<BloodConsumeSource>(deathEvent.Died);

#if DEBUG
Utils.Logger.LogMessage($"DE.Killer = {deathEvent.Killer.Index}");
Utils.Logger.LogMessage($"DE.Died = {deathEvent.Died.Index}");
Utils.Logger.LogMessage($"DE.Source = {deathEvent.Source.Index}");
#endif

Entity userEntity = playerCharacter.UserEntity._Entity;
User user = entityManager.GetComponentData<User>(userEntity);

bool killedByFeeding = deathEvent.Killer.Index == deathEvent.Source.Index;

if(!playerBlood.BloodType.ParseBloodType(out BloodType playerBloodType))
{
// Invalid/unknown blood type
return;
}

if(!bloodConsumeSource.UnitBloodType.ParseBloodType(out BloodType bloodType))
{
// Invalid/unknown blood type
return;
}

bool isVBlood = bloodType == BloodType.VBlood;

// Allow V-Bloods to skip the 'killed by feeding' check, otherwise additional feeders won't get a refill.
if(!isVBlood && BloodRefillConfig.BloodRefillRequiresFeeding.Value && !killedByFeeding)
{
// Can only gain blood when killing the enemy while feeding (i.e. abort the feed)
return;
}

bool isSameBloodType = playerBloodType == bloodType || (BloodRefillConfig.BloodRefillExcludeVBloodFromSameBloodTypeCheck.Value && isVBlood);

if(BloodRefillConfig.BloodRefillRequiresSameBloodType.Value && !isSameBloodType)
{
// Can only gain blood when killing an enemy of the same blood type
return;
}

float bloodTypeMultiplier = isSameBloodType ? 1f : BloodRefillConfig.BloodRefillDifferentBloodTypeMultiplier.Value;

float playerLevel = playerEquipment.WeaponLevel + playerEquipment.ArmorLevel + playerEquipment.SpellLevel;
float enemyLevel = unitLevel.Level;

#if DEBUG
Utils.Logger.LogMessage($"Player Blood Quality: {playerBlood.Quality}");
Utils.Logger.LogMessage($"Player Blood Value: {playerBlood.Value}");
Utils.Logger.LogMessage($"Player Level: {playerLevel}");

Utils.Logger.LogMessage($"Enemy Blood Quality: {bloodConsumeSource.BloodQuality}");
Utils.Logger.LogMessage($"Enemy Level {enemyLevel}");
#endif

float levelRatio = enemyLevel / playerLevel;

float qualityRatio = (100f - (playerBlood.Quality - bloodConsumeSource.BloodQuality)) / 100f;

float refillRatio = levelRatio * qualityRatio;

// Config amount is expressed in 'Litres of blood' -> the game's formule is 'blood value / 10', hence the * 10 multiplier here.
float refillAmount = BloodRefillConfig.BloodRefillAmount.Value * 10f * refillRatio;

refillAmount *= bloodTypeMultiplier;

#if DEBUG
Utils.Logger.LogMessage($"Lvl Ratio: {levelRatio}");
Utils.Logger.LogMessage($"Quality Ratio: {qualityRatio}");
Utils.Logger.LogMessage($"Refill Ratio: {refillRatio}");
Utils.Logger.LogMessage($"Blood Type Multiplier: {bloodTypeMultiplier}");
Utils.Logger.LogMessage($"Refill Amount: {refillAmount}");
#endif

if(BloodRefillConfig.BloodRefillRandomRefill.Value)
{
refillAmount = Random.RandomRange(1f, refillAmount);

#if DEBUG
Utils.Logger.LogMessage($"Refill Roll: {refillAmount}");
#endif
}

if(isVBlood)
{
switch(BloodRefillConfig.BloodRefillVBloodRefillType.Value)
{
case 1: // V-blood fully refills the blood pool
refillAmount = playerBlood.MaxBlood - playerBlood.Value;
break;

case 2: // V-blood refills based on the unit's level
refillAmount = enemyLevel * BloodRefillConfig.BloodRefillVBloodRefillMultiplier.Value;
break;
}
}

refillAmount *= BloodRefillConfig.BloodRefillMultiplier.Value;

if(refillAmount > 0f)
{
int roundedRefillAmount = (int)Math.Ceiling(refillAmount);

if(roundedRefillAmount > 0)
{
#if DEBUG
Utils.Logger.LogMessage($"New Blood Amount: {playerBlood.Value + roundedRefillAmount}");
#endif

float newTotalBlood = Math.Min(playerBlood.MaxBlood, playerBlood.Value + roundedRefillAmount);
float actualBloodGained = newTotalBlood - playerBlood.Value;
float refillAmountInLitres = (int)(actualBloodGained * 10f) / 100f;
float newTotalBloodInLitres = (int)Math.Round(newTotalBlood) / 10f;
Utils.SendMessage(userEntity, $"+{refillAmountInLitres}L Blood ({newTotalBloodInLitres}L)", ServerChatMessageType.Lore);

ChangeBloodType(user, playerBloodType, playerBlood.Quality, roundedRefillAmount);
return;
}
}

Utils.SendMessage(userEntity, $"No blood gained from the enemy.", ServerChatMessageType.Lore);
}

private static void ChangeBloodType(User user, BloodType bloodType, float quality, int addAmount)
{
ChangeBloodDebugEvent bloodChangeEvent = new()
{
Source = bloodType.ToPrefabGUID(),
Quality = quality,
Amount = addAmount,
};
VWorld.Server.GetExistingSystem<DebugEventsSystem>().ChangeBloodEvent(user.Index, ref bloodChangeEvent);
}

[Command("setblood", "setblood <blood-type> <blood-quality> [<gain-amount>]", "Sets your blood type to the specified blood-type and blood-quality, and optionally adds a given amount of blood (in Litres).", true)]
private static void OnSetBloodCommand(Command command)
{
var user = command.User;
var argCount = command.Args.Length;
if(argCount >= 2)
{
var searchBloodType = command.Args[0];
var validBloodTypes = BloodTypeExtensions.BloodTypeToPrefabGUIDMapping.Keys.ToList();
if(Enum.TryParse(searchBloodType.ToLowerInvariant(), true, out BloodType bloodType) && validBloodTypes.Contains(bloodType))
{
var searchBloodQuality = command.Args[1];
if(int.TryParse(searchBloodQuality.Replace("%", string.Empty), out var bloodQuality) && bloodQuality >= 1 && bloodQuality <= 100)
{
float? addBloodAmount = null;
if(argCount >= 3)
{
var searchLitres = command.Args[2];
if(float.TryParse(searchLitres.Replace("L", string.Empty), out float parsedAddBloodAmount) && parsedAddBloodAmount >= -10f && parsedAddBloodAmount <= 10f)
{
addBloodAmount = parsedAddBloodAmount;
}
else
{
user.SendSystemMessage($"<color=#ff0000>Invalid gain-amount '{searchBloodQuality}'. Should be between -10 and 10</color>");
}
}
else
{
addBloodAmount = 10f;
}

if(addBloodAmount.HasValue)
{
ChangeBloodType(user, bloodType, bloodQuality, (int)(addBloodAmount.Value * 10f));
user.SendSystemMessage($"Changed blood type to <color=#ff0000>{bloodQuality}%</color> <color=#ffffff>{searchBloodType}</color> and added <color=#ff0000>{addBloodAmount.Value}L</color>");
}
}
else
{
user.SendSystemMessage($"<color=#ff0000>Invalid blood-quality '{searchBloodQuality}'. Should be between 1 and 100</color>");
}
}
else
{
user.SendSystemMessage($"<color=#ff0000>Invalid blood-type '{searchBloodType}'. Options are: {string.Join(", ", validBloodTypes.Select(x => x.ToString()))}</color>");
}
}
else
{
CommandSystem.SendInvalidCommandMessage(command);
}
command.Use();
}

#endregion
}
}
Loading

0 comments on commit e453611

Please sign in to comment.