xzjv
Community Developer
- Joined
- Mar 17, 2014
- Messages
- 1,243
- Reaction score
- 46
This guide is for people wanting to create their own combat routines in Trinity. There is a lot of info that could be covered on this topic so ill do this first pass and then expand on it later if people are interested.
What is a combat routine?
Combat routines decide which powers should be cast, where they should be cast and how the bot moves around when fighting enemies. Trinity provides many routines for each class by default; and provides a way for coding-inclined users to make their own custom routines.
How do i use them?
Appropriate routines are selected Automatically - When you start DemonBuddy or open the trinity Settings window it scans your hero for equipped items and uses that information to automatically select the best routine available for you. Settings for each routine can be found in Trinity Settings/Config under the 'Routines' tab.
You can choose a specific routine - You have the option to force Trinity to use the routine you want. This is particularly useful if there are multiple versions of a build for your class, or if you've been customizing an existing routine to suit your needs, or maybe you want to try experimental/beta routines that aren't yet ready to be placed onto auto-detection.
What does the routine code look like?
The routines can be found in your Trinity folder: \Plugins\Trinity\Routines\
A basic routine could look something like the code below. So lets break down the parts.
.
IRoutine
At the heart of the system is the interface IRoutine - a contract that defines all the things a routine is expected to provide. The base classes make it so you don't really have to worry about these details.
Any .cs file in the assembly that implements IRoutine will be found and shown in the Routine settings tab. It will have any .Xaml settings displayed and automatically have Save/Load/Import/Export handled.
If you know what you're doing you can use some of the more advanced parts of your IRoutine implementation to take more control of the bot, such as changing weighting, avoidance, target selection. Here's the full spec:
Editing Tools
In terms of productivity and avoiding basic syntax errors i strongly recommend you download VisualStudio Community edition. Free IDE and Tools | Visual Studio Community
If you're familiar with VisualStudio and C# you should grab the solution from SVN unifiedtrinity.Master - Revision 290: / and fix the references to point to the files in your DB folder (Demonbuddy.exe, GreyMagic.dll, IronPython.dll, Microsoft.Dynamic.dll, Microsoft.Scripting.dll) and also check the post-build script which is currently set to copy files around and change/remove it as needed.
Understanding the Base Classes
Each class (monk/barb etc) has a 'base' that contains commonly used stuff like all the skills you can cast. This means your routine is able to focus just on what is different and special about the build without duplicating all the boring stuff.
You do this by 'inheriting' from the base. e.g. In the previous example MyBasicMonkRoutine inherits from MonkBase.
To find out what is in the base class just open the file MonkBase.cs located in the same routines folder. You'll see a lot of boilerplate code you can use or duplicate with changes in your routine. For example
Each base for a player class (barb/monk etc) itself inherits from another base, which contains more common resources that all routines may use. This one is called 'RoutineBase.cs' and located in Trinity\Routines\ folder.
Some examples of the resources in RoutineBase:
Overriding Cast Conditions
Most of the default routines are leveraging the base methods:
Alternatively, you could write this without the helper methods as:
How you structure your routine and how much of the base resources you leverage is entirely up to you. You could skip all the helper stuff and simply write old style routine code.
Lets assume that you want to use "TrySecondaryPower" but also change the condition for when SevenSidedStrike is cast, or who its cast on. How would that work?
What you would do is 'override' the base method for "ShouldSevenSidedStrike" causing TrySecondaryPower to use your version instead of the default one.
You can go and look at the default in MonkBase.cs and copy/paste it into your routine, and add the 'override' keyword.
This method does two things, it returns a true/false answer to the question, Should we cast SevenSidedStrike, and also specifies which target it should be cast on.
How do i learn some basic C#?
Take a look at this video tutorial series: https://app.pluralsight.com/player?...ive&clip=0&course=csharp-fundamentals-csharp5
There is also this YouTube series that comes highly recommended How to program in C# - Beginner Course - YouTube
Some free e-book/PDF by Rob Miles on c# http://www.robmiles.com/s/CSharp-Book-2016-Rob-Miles-82.pdf
To be continued.
What is a combat routine?
Combat routines decide which powers should be cast, where they should be cast and how the bot moves around when fighting enemies. Trinity provides many routines for each class by default; and provides a way for coding-inclined users to make their own custom routines.
How do i use them?
Appropriate routines are selected Automatically - When you start DemonBuddy or open the trinity Settings window it scans your hero for equipped items and uses that information to automatically select the best routine available for you. Settings for each routine can be found in Trinity Settings/Config under the 'Routines' tab.

You can choose a specific routine - You have the option to force Trinity to use the routine you want. This is particularly useful if there are multiple versions of a build for your class, or if you've been customizing an existing routine to suit your needs, or maybe you want to try experimental/beta routines that aren't yet ready to be placed onto auto-detection.

What does the routine code look like?
The routines can be found in your Trinity folder: \Plugins\Trinity\Routines\
A basic routine could look something like the code below. So lets break down the parts.
.
GetOffensivePower() is called when in combat. Trinity says 'hey routine guy, give me a power to attack this monster with' and your routine provides that information.
GetDefensivePower() is called right before avoidance takes place. As an opportunity to cast life-saving type spells.
GetBuffPower() is called always (except when dead etc). Asks routine for a buff spell to cast.
GetDestructiblePower() is called when the current target is a destructible container/door/barricade.
GetMovementPower() is called always for movement. Literally any DB plugin and DB itself will call to your routine for a power to move with. Making it very useful for skills like whirlwind that replace walking.
GetDefensivePower() is called right before avoidance takes place. As an opportunity to cast life-saving type spells.
GetBuffPower() is called always (except when dead etc). Asks routine for a buff spell to cast.
GetDestructiblePower() is called when the current target is a destructible container/door/barricade.
GetMovementPower() is called always for movement. Literally any DB plugin and DB itself will call to your routine for a power to move with. Making it very useful for skills like whirlwind that replace walking.
Code:
public class MyBasicMonkRoutine : MonkBase, IRoutine
{
public string DisplayName => "Example Monk Routine with no settings";
public string Description => "A very simple example build ";
public string Author => "xzjv";
public string Version => "0.1";
public string Url => string.Empty;
public Build BuildRequirements => null;
public IDynamicSetting RoutineSettings => null;
public TrinityPower GetOffensivePower()
{
TrinityPower power;
if (TrySpecialPower(out power))
return power;
if (TrySecondaryPower(out power))
return power;
if (TryPrimaryPower(out power))
return power;
if (IsNoPrimary)
return Walk(CurrentTarget);
return null;
}
public TrinityPower GetDefensivePower() => GetBuffPower();
public TrinityPower GetBuffPower() => DefaultBuffPower();
public TrinityPower GetDestructiblePower() => DefaultDestructiblePower();
public TrinityPower GetMovementPower(Vector3 destination) => Walk(destination);
}
IRoutine
At the heart of the system is the interface IRoutine - a contract that defines all the things a routine is expected to provide. The base classes make it so you don't really have to worry about these details.
Any .cs file in the assembly that implements IRoutine will be found and shown in the Routine settings tab. It will have any .Xaml settings displayed and automatically have Save/Load/Import/Export handled.
If you know what you're doing you can use some of the more advanced parts of your IRoutine implementation to take more control of the bot, such as changing weighting, avoidance, target selection. Here's the full spec:
Code:
using System;
using System.Threading.Tasks;
using Trinity.Components.Combat;
using Trinity.Components.Combat.Resources;
using Trinity.Framework.Actors.ActorTypes;
using Trinity.Framework.Objects;
using Trinity.Settings;
using Zeta.Common;
using Zeta.Game;
namespace Trinity.Routines
{
public interface IRoutine
{
string DisplayName { get; }
string Description { get; }
string Author { get; }
string Version { get; }
ActorClass Class { get; }
string Url { get; }
Build BuildRequirements { get; }
IDynamicSetting RoutineSettings { get; }
// Kiting
KiteMode KiteMode { get; }
float KiteDistance { get; }
int KiteStutterDuration { get; }
int KiteStutterDelay { get; }
int KiteHealthPct { get; }
// Range
float TrashRange { get; }
float EliteRange { get; }
float HealthGlobeRange { get; }
float ShrineRange { get; }
// Cluster
float ClusterRadius { get; }
int ClusterSize { get; }
// Misc
int PrimaryEnergyReserve { get; }
int SecondaryEnergyReserve { get; }
float EmergencyHealthPct { get; }
// Power Selection
TrinityPower GetOffensivePower();
TrinityPower GetDefensivePower();
TrinityPower GetBuffPower();
TrinityPower GetDestructiblePower();
TrinityPower GetMovementPower(Vector3 destination);
// Hardcore Overrides
Task<bool> HandleKiting();
Task<bool> HandleAvoiding();
Task<bool> HandleTargetInRange();
Task<bool> MoveToTarget();
bool SetWeight(TrinityActor cacheObject);
// Temporary Overrides
Func<bool> ShouldIgnoreNonUnits { get; }
Func<bool> ShouldIgnorePackSize { get; }
Func<bool> ShouldIgnoreAvoidance { get; }
Func<bool> ShouldIgnoreKiting { get; }
Func<bool> ShouldIgnoreFollowing { get; }
}
}
Editing Tools
In terms of productivity and avoiding basic syntax errors i strongly recommend you download VisualStudio Community edition. Free IDE and Tools | Visual Studio Community
If you're familiar with VisualStudio and C# you should grab the solution from SVN unifiedtrinity.Master - Revision 290: / and fix the references to point to the files in your DB folder (Demonbuddy.exe, GreyMagic.dll, IronPython.dll, Microsoft.Dynamic.dll, Microsoft.Scripting.dll) and also check the post-build script which is currently set to copy files around and change/remove it as needed.
Understanding the Base Classes
Each class (monk/barb etc) has a 'base' that contains commonly used stuff like all the skills you can cast. This means your routine is able to focus just on what is different and special about the build without duplicating all the boring stuff.
You do this by 'inheriting' from the base. e.g. In the previous example MyBasicMonkRoutine inherits from MonkBase.
Code:
public class MyBasicMonkRoutine : MonkBase, IRoutine

To find out what is in the base class just open the file MonkBase.cs located in the same routines folder. You'll see a lot of boilerplate code you can use or duplicate with changes in your routine. For example
Code:
protected static bool HasShenLongBuff
=> Core.Buffs.HasBuff(SNOPower.P3_ItemPassive_Unique_Ring_026, 1);
protected static bool HasRaimentDashBuff
=> Core.Buffs.HasBuff(SNOPower.P2_ItemPassive_Unique_Ring_033, 2);
protected static bool HasSpiritGuardsBuff
=> Core.Buffs.HasBuff(SNOPower.P2_ItemPassive_Unique_Ring_034, 1);
protected virtual TrinityPower FistsOfThunder(TrinityActor target)
=> new TrinityPower(SNOPower.Monk_FistsofThunder, MeleeAttackRange, target.AcdId);
protected virtual TrinityPower DeadlyReach(TrinityActor target)
=> new TrinityPower(SNOPower.Monk_DeadlyReach, MeleeAttackRange, target.AcdId);
Each base for a player class (barb/monk etc) itself inherits from another base, which contains more common resources that all routines may use. This one is called 'RoutineBase.cs' and located in Trinity\Routines\ folder.
Some examples of the resources in RoutineBase:
Code:
protected static bool IsMultiSpender
=> SkillUtils.Active.Count(s => s.IsAttackSpender) > 1;
protected static bool IsNoPrimary
=> SkillUtils.Active.Count(s => s.IsGeneratorOrPrimary) == 0;
protected static bool ShouldRefreshBastiansGenerator
=> Sets.BastionsOfWill.IsFullyEquipped && !Core.Buffs.HasBastiansWillGeneratorBuff
&& SpellHistory.TimeSinceGeneratorCast >= 3750;
protected static bool ShouldRefreshBastiansSpender
=> Sets.BastionsOfWill.IsFullyEquipped && !Core.Buffs.HasBastiansWillGeneratorBuff
&& SpellHistory.TimeSinceSpenderCast >= 3750;
protected static int EndlessWalkOffensiveStacks
=> Core.Buffs.GetBuffStacks(447541, 1);
protected static int EndlessWalkDefensiveStacks
=> Core.Buffs.GetBuffStacks(447541, 2);
Overriding Cast Conditions
Most of the default routines are leveraging the base methods:
- TrySpecialPower
- TrySecondaryPower
- TryPrimaryPower
Code:
public TrinityPower GetOffensivePower()
{
TrinityPower power;
if (TrySpecialPower(out power))
return power;
if (TrySecondaryPower(out power))
return power;
if (TryPrimaryPower(out power))
return power;
if (IsNoPrimary)
return Walk(CurrentTarget);
return null;
}
Alternatively, you could write this without the helper methods as:
Code:
public TrinityPower GetOffensivePower()
{
TrinityActor target;
TrinityPower power;
Vector3 position;
if (ShouldCycloneStrike())
return CycloneStrike();
if (ShouldExplodingPalm(out target))
return ExplodingPalm(target);
if (ShouldTempestRush(out position))
return TempestRush(position);
if (ShouldDashingStrike(out position))
return DashingStrike(position);
if (ShouldSevenSidedStrike(out target))
return SevenSidedStrike(target);
if (ShouldWaveOfLight(out target))
return WaveOfLight(target);
if (ShouldLashingTailKick(out target))
return LashingTailKick(target);
if (ShouldFistsOfThunder(out target))
return FistsOfThunder(target);
if (ShouldDeadlyReach(out target))
return DeadlyReach(target);
if (ShouldCripplingWave(out target))
return CripplingWave(target);
if (ShouldWayOfTheHundredFists(out target))
return WayOfTheHundredFists(target);
if (IsNoPrimary)
return Walk(CurrentTarget);
return null;
}
How you structure your routine and how much of the base resources you leverage is entirely up to you. You could skip all the helper stuff and simply write old style routine code.
Code:
public TrinityPower GetOffensivePower()
{
if (Skills.WitchDoctor.SpiritWalk.CanCast())
{
return SpiritWalk();
}
if (Player.CurrentHealthPct < EmergencyHealthPct)
{
return new TrinityPower(SNOPower.Walk, 7f, TargetUtil.BestWalkLocation(45f, true));
}
return null;
}
Lets assume that you want to use "TrySecondaryPower" but also change the condition for when SevenSidedStrike is cast, or who its cast on. How would that work?
What you would do is 'override' the base method for "ShouldSevenSidedStrike" causing TrySecondaryPower to use your version instead of the default one.
You can go and look at the default in MonkBase.cs and copy/paste it into your routine, and add the 'override' keyword.
Code:
protected override bool ShouldSevenSidedStrike(out TrinityActor target)
{
target = null;
if (!Skills.Monk.SevenSidedStrike.CanCast())
return false;
if (!TargetUtil.AnyMobsInRange(45f) && !CurrentTarget.IsTreasureGoblin)
return false;
target = TargetUtil.GetBestClusterUnit() ?? CurrentTarget;
return target != null;
}
This method does two things, it returns a true/false answer to the question, Should we cast SevenSidedStrike, and also specifies which target it should be cast on.
How do i learn some basic C#?
Take a look at this video tutorial series: https://app.pluralsight.com/player?...ive&clip=0&course=csharp-fundamentals-csharp5
There is also this YouTube series that comes highly recommended How to program in C# - Beginner Course - YouTube
Some free e-book/PDF by Rob Miles on c# http://www.robmiles.com/s/CSharp-Book-2016-Rob-Miles-82.pdf
To be continued.
Last edited: