Skip to content

Commit

Permalink
Great Worm Bounties
Browse files Browse the repository at this point in the history
- Provides the team of the player who kills a Great Worm a bounty of resources
- The resource bounty is somewhat random but depends on how the Great Worm was killed
- Allows server operators to adjust wildlife the total number of Great Worms with "GreatWorms_MaxNumber"
- Allows server operators to adjust wildlife probability that a new Great Worm will spawn with "GreatWorms_SpawnChance"
- Creates new admin command with the Slay power to `!devour <player>` which will spawn a Great Worm at the player's location
  • Loading branch information
data-bomb committed Apr 27, 2024
1 parent 4224c96 commit d538575
Show file tree
Hide file tree
Showing 3 changed files with 244 additions and 0 deletions.
215 changes: 215 additions & 0 deletions Si_WormBounty/Si_WormBounty.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
/*
Silica Worm Bounty
Copyright (C) 2024 by databomb
* Description *
Allows players who kill a Great Worm to receive a bounty, and allows
server operators to adjust the probabilities and number of Great Worms.
* License *
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#if NET6_0
using Il2Cpp;
#else
using System.Reflection;
#endif

using HarmonyLib;
using MelonLoader;
using SilicaAdminMod;
using System;
using UnityEngine;
using Si_WormBounty;

[assembly: MelonInfo(typeof(WormBounty), "Worm Bounty", "0.9.0", "databomb", "https://github.com/data-bomb/Silica")]
[assembly: MelonGame("Bohemia Interactive", "Silica")]
[assembly: MelonOptionalDependencies("Admin Mod")]

namespace Si_WormBounty
{
public class WormBounty : MelonMod
{
static MelonPreferences_Category _modCategory = null!;
static MelonPreferences_Entry<int> _Pref_GreatWorms_MaxNumber = null!;
static MelonPreferences_Entry<float> _Pref_GreatWorms_SpawnChance = null!;

public override void OnInitializeMelon()
{
_modCategory ??= MelonPreferences.CreateCategory("Silica");
_Pref_GreatWorms_MaxNumber ??= _modCategory.CreateEntry<int>("GreatWorms_MaxNumber", 2);
_Pref_GreatWorms_SpawnChance ??= _modCategory.CreateEntry<float>("GreatWorms_SpawnChance", 0.25f);
}

public override void OnLateInitializeMelon()
{
HelperMethods.CommandCallback devourCallback = Command_Devour;
HelperMethods.RegisterAdminCommand("devour", devourCallback, Power.Slay, "Spawns a Great Worm at the location of the player. Usage: !devour <player>");
}

public void Command_Devour(Player? callerPlayer, String args)
{
string commandName = args.Split(' ')[0];

// validate argument count
int argumentCount = args.Split(' ').Length - 1;
if (argumentCount > 1)
{
HelperMethods.SendChatMessageToPlayer(callerPlayer, HelperMethods.chatPrefix, commandName, ": Too many arguments");
return;
}
else if (argumentCount < 1)
{
HelperMethods.SendChatMessageToPlayer(callerPlayer, HelperMethods.chatPrefix, commandName, ": Too few arguments");
return;
}

// validate argument contents
string targetText = args.Split(' ')[1];
Player? targetPlayer = HelperMethods.FindTargetPlayer(targetText);

if (targetPlayer == null)
{
HelperMethods.SendChatMessageToPlayer(callerPlayer, HelperMethods.chatPrefix, commandName, ": Ambiguous or invalid target");
return;
}

if (callerPlayer != null && !callerPlayer.CanAdminTarget(targetPlayer))
{
HelperMethods.ReplyToCommand_Player(targetPlayer, "is immune due to level");
return;
}

// do the devouring
AmbientLife wildLifeInstance = GameObject.FindObjectOfType<AmbientLife>();
Vector3 targetPosition = targetPlayer.ControlledUnit.WorldPhysicalCenter;


#if NET6_0
wildLifeInstance.SpawnBoss(targetPosition, null);
#else
Type ambientLifeType = typeof(AmbientLife);
MethodInfo spawnBossMethod = ambientLifeType.GetMethod("SpawnBoss", BindingFlags.Instance | BindingFlags.NonPublic);
spawnBossMethod.Invoke(wildLifeInstance, parameters: new object?[] { targetPosition, null });
#endif

MelonLogger.Msg("Spawning Great Worm to devour player (" + targetPlayer.PlayerName + ")");
HelperMethods.AlertAdminActivity(callerPlayer, targetPlayer, "devoured");
}

#if NET6_0
[HarmonyPatch(typeof(AmbientLife), nameof(AmbientLife.OnEnable))]
#else
[HarmonyPatch(typeof(AmbientLife), "OnEnable")]
#endif
private static class WormBounty_Patch_AmbientLife_OnEnable
{
public static void Postfix(AmbientLife __instance)
{
try
{
__instance.NumBossMax = _Pref_GreatWorms_MaxNumber.Value;
__instance.SpawnBossChance = _Pref_GreatWorms_SpawnChance.Value;
}
catch (Exception error)
{
HelperMethods.PrintError(error, "Failed to run AmbientLife::OnEnable");
}
}
}

#if NET6_0
[HarmonyPatch(typeof(AmbientLife), nameof(AmbientLife.OnUnitDestroyed))]
#else
[HarmonyPatch(typeof(AmbientLife), "OnUnitDestroyed")]
#endif
private static class WormBounty_Patch_AmbientLife_OnUnitDestroyed
{
public static void Prefix(AmbientLife __instance, Unit __0, EDamageType __1, GameObject __2)
{
try
{
if (__2 == null)
{
return;
}

// a Basic wildlife was destroyed, ignore that
if (__instance.Boss != __0.ObjectInfo)
{
return;
}

// check if this is an actual player
BaseGameObject attackerBase = GameFuncs.GetBaseGameObject(__2);
if (attackerBase == null)
{
return;
}

NetworkComponent attackerNetComp = attackerBase.NetworkComponent;
if (attackerNetComp == null)
{
return;
}

Player attackerPlayer = attackerNetComp.OwnerPlayer;
if (attackerPlayer == null)
{
return;
}

if (attackerPlayer.Team == null)
{
return;
}

// determine bounty amount
int bountyAmount = FindBounty(__1);

// award bounty
AwardBounty(attackerPlayer, bountyAmount);
}
catch (Exception error)
{
HelperMethods.PrintError(error, "Failed to run AmbientLife::OnUnitDestroyed");
}
}
}

private static int FindBounty(EDamageType damageType)
{
// explosion kills earn a bit less
int baseAmount = (damageType == EDamageType.Explosion) ? 500 : 1000;

// give a little more, perhaps
System.Random randomIndex = new System.Random();
int varyingAmount = randomIndex.Next(0, 750);

// round down to nearest ten
varyingAmount = (int)Math.Round(varyingAmount / 10.0) * 10;

return baseAmount + varyingAmount;
}

private static void AwardBounty(Player player, int bounty)
{
Team team = player.Team;
team.StoreResource(bounty);

HelperMethods.ReplyToCommand_Player(player, "defeated a Great Worm. ", bounty.ToString(), " awarded to ", team.TeamShortName);
}
}
}
4 changes: 4 additions & 0 deletions Si_WormBounty/Si_WormBounty.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\include\common.csproj" />
<Import Project="..\include\net6.csproj" Condition="'$(TargetFramework)' == 'net6.0'" />
</Project>
25 changes: 25 additions & 0 deletions Si_WormBounty/Si_WormBounty.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.6.33723.286
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Si_WormBounty", "Si_WormBounty.csproj", "{98DA2BBC-E151-40EB-97B3-76B36D8165D5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{98DA2BBC-E151-40EB-97B3-76B36D8165D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{98DA2BBC-E151-40EB-97B3-76B36D8165D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{98DA2BBC-E151-40EB-97B3-76B36D8165D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{98DA2BBC-E151-40EB-97B3-76B36D8165D5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F5AA1254-2B36-4A67-9939-0D011BF03A98}
EndGlobalSection
EndGlobal

0 comments on commit d538575

Please sign in to comment.