For Modders - yeelp/Distinct-Damage-Descriptions GitHub Wiki
This section is for modders who want to write compatibility with DDD, or an addon for DDD, or well, anything else really that requires Java and DDD!
Getting DDD working in your project is the same as what you would do if you wanted to contribute to DDD directly; you add it as a dependency. You can follow the same steps outlined in the README to add DDD as a dependency using the CurseMaven plugin.
Pretty much everything is accessible if you're working with DDD using Java since you have the whole project as a dependency. But here's a collection of things a modder is most likely to interact with: DDD's API, Events, Configurations and Registries.
DDD's API is primarily how you access all of DDD's capabilities. DDD's API is broken down into two components: and Accessor and a Mutator. The Accessor is the primary part you'd use; that's the one that has all the methods to access capabilities. The Mutator is used for registering custom capabilities.
DDD's API is accessed through its DDDAPI class, located in the yeelp.distinctdamagedescriptions.api package. The API is initialized during pre init. You then select which part of the API you're using. For example, accessing an IDamageDistribution using the API would look like
DDDAPI.accessor.getDamageDistribution(thing);DDD's Accessor API is how you get access to the different capabilities. But note that not everything has every capability. For example, only shields will have a ShieldDistribution. Thus, API calls typically return their result wrapped in Java's Optional for convenience to avoid having an if null style check every time you make an API call. Instead, you can make a call like:
DDDAPI.accessor.getDamageDistribution(thing).ifPresent(...);Optional::ifPresent takes a Consumer that acts upon the result but only if it exists, so you can avoid if statements that check if the result is null. If you aren't familiar with functional programming in Java, you'd set it up like this:
DDDAPI.accessor.getDamageDistribution(thing).ifPresent((distribution) -> {
//distribution will be the IDamageDistribution and will always be nonnull since this is only executed if it is present
});Of course there's a bunch of other ways to use Java Optionals, so read the documentation if you're unfamiliar with it. DDD's API calls make heavy use of Optionals.
Alternatively, if you know for sure that something has a damage distribution, you can use getDamageDistributionUnsafe instead. This is only available for API calls that retrieve an IDamageDistribution. This API call will not return null if the capability isn't present, it will instead throw an unchecked NoSuchElementException since the getDamageDistributionUnsafe call just does a getDamageDistribution call and does an Optional::get on the result. The one thing the unsafe version does is check if the input argument is null first and throws a NullPointerException if it is. The "safe" versions of these calls allow null inputs. A null input is treated the same as an input that doesn't have the capability; in both instances, you get an Optional with a default capability, be it a default damage distribution, default shield distribution, etc. You get these results even if default distributions are disabled in the config. You can always check if a distribution is default or not with a simple instanceof IDefaultDistribution. Typically, you won't need this check since you'll want to operate on whatever distribution you get and default distributions work normally but throw UnsupportedOperationException on any kind of modification methods.
DDD's Mutator API is for registering custom capabilities. This would be for creating and registering your own implementations of IDistribution or IDamageResistances in instances where you want to create a DDD capability that is more dynamic than the usual capabilities. Capabilities can be updated with modifiers, but only in specific static ways. If you need dynamic updating of a capability, such as the way DDD's Tinker's Construct integration works, use DDD's Mutator API to register it. You will also need to register a capability distributor and register it in DDDCapabilityDistributors. That class has 3 methods that are relevant:
-
addItemCap: For adding capabilities that attach to items. -
addProjCap: For adding capabilities that attach to projectiles. -
addMobCap: For adding capabilities that attach to entities.
All methods take the capability distributor as the argument. You can write a capability distributor by extending AbstractCapabilityDistributor. This class is parameterized by 3 parameters: the type of things that get the capability, the type of configuration for the capability and the type of capability. You can see examples here (Look at the DamageDistribution, MobResistances, ArmorDistribution and ShieldDistribution distributors).
Note that DDD first applies modded capabilities before applying base capabilities. If an item gets an IDamageDistribution from some distributor, it won't get the base DamageDistribution implementation. The takeaway here is that your capability distributor just needs to isolate the items (or mobs or projectiles) it wants to attach to. DDD will attach your capability to the appropriate things first and you don't need to worry about anything else. All of that isolating stuff is done in the isApplicable method.
To illustrate, look at DDD's Tinker's Construct integration. For tools, DDD uses its own TinkerDamageDistribution which it applies to the tools. This Damage Distribution updates depending on the head pieces of the tool. Part of the way this distribution works is shared with Tetra integration, so it extends a subclass of ModUpdatingDamageDistribution (more on that in a bit). At the end, the capability has a on register callback where it uses DDD's Mutator API to register the capability for items and for projectiles. It also registers a capability distributor elsewhere that is parameterized with ItemStack, IDamageDistribution, IDamageDistribution. The isApplicableMethod checks that whatever item stack we apply to has to extends Tinker's Construct's TinkerToolCore. Nothing else will get this capability.
If you're writing a custom IDamageDistribution, you may wish to consider extending ModUpdatingDamageDistribution, which has most of the framework already implemented. You just need to write implementations for the overloaded getUpdatedWeights method, where you should return the updated weights if they are different. If they are the same or no changes are to be made to the weights of the distribution, return an empty Optional. You should return empty Optionals for the overloaded versions of the method that take capability providers you aren't using; e.g. if your capability is only being attached to items, return an empty Optional for the overloaded getUpdatedWeights that take an EntityLivingBase or IProjectile.
The following sections describe the different capabilities DDD has and what can be done with them. They are all found in yeelp.distinctdamagedescriptions.capability.
IDistribution is the root capability for IDamageDistribution, IArmorDistribution and ShieldDistribution. All those capabilities extend this.
An important thing to note is the difference between certain methods. Each method like getWeight has a version called getBaseWeight. The "base" version gets the base weights before any modifiers.
IDamageDistribution is what items, mobs and projectiles use to determine what damage they do.
When retrieved through DDD's API, an IDamageDistribution will call its update method. This method is overloaded for ItemStack, EntityLivingBase and IProjectile arguments and allows the distribution to update based on whatever conditions you want. Distributions attached to items only call the ItemStack version, mobs call only the EntityLivingBase version and projectiles only call the IProjectile version. Calling update deliberately while not passing in whatever the distribution is attached to may lead to undefined behaviour! The base implementation of these methods checks if any modifiers should be applied. Custom versions, like the damage distributions applied to Tinker's Construct tools or Lycanites Mobs equipment will update their damage distributions based on properties of the item itself in the update method, then pass the call to the parent's update to check for modifiers.
The IDamageDistribution has a method that distributes damage into different types based on this distribution's weights. It returns a DamageMap; a map that maps damage types to the amount of damage inflicted of that type. DamageMap is really just Map<DDDDamageType, Float> under the hood. Having it as its own type cleans up code and makes it more readable.
If you have the whole of DDD in your workspace, please note that First Aid (which DDD integrates with) has it's own IDamageDistribution. Make sure you're using DDD's IDamageDistribution, found in yeelp.distinctdamagedescriptions.capability.IDamageDistribution.
IArmorDistribution is what DDD attaches to armor for the armor to determine how effective it is against certain types. It basically has the same methods as the damage distributions, the only notable difference is its distributeArmor method, which differs from the IDamageDistribution distributeDamage method. The distributeArmor method distributes to an ArmorMap; a map that maps damage types to (armor, toughness) tuples. It uses a container class for armor called ArmorValues. ArmorValues implement Comparable and Iterable. Comparing armor values compares armor first and then toughness; so if two armor tuples have the same armor values, the one with the higher toughness will be considered "bigger". Iterating over ArmorValues will iterate over the tuple's armor values first, and then its toughness value. The ArmorMap itself is a Map<DDDDamageType, ArmorValues>.
ShieldDistribution is just a special implementation of IDistribution. It has a special block method that reduces a DamageMap's damage based on the weights of this ShieldDistribution. The block method returns the same DamageMap you pass in and it does modify that map. It only returns the DamageMap to chain calls together more easily.
To illustrate, the two following code snippets are functionally identical.
ItemStack shield = getShieldFromSomewhere();
DamageMap map = getSomeDamageMap();
DDDAPI.accessor.getShieldDistribution(shield).ifPresent((dist) -> {
dist.block(map).forEach(...);
});ItemStack shield = getShieldFromSomewhere();
DDDAPI.accessor.getShieldDistribution(shield).ifPresent((dist) -> {
DamageMap map = getSomeDamageMap();
dist.block(map);
map.forEach(...);
});The base of all the mob resistances capabilities. This base version doesn't have anything about adaptability. The extended IMobResistances has all the information about adaptability. This root capability only store resistances and immunities. Just like IDistribution, each method has a "base" version that gets the resistance or immunity before any modifiers are applied. It also updates when retrieved through the API and the base implementation checks for modifiers that should apply or not.
When dealing with resistances, this is the interface you will usually be dealing with. This has the additional functionality of mob adaptability. Much like resistances, this has separate methods for getting if a mob was originally adaptive, or getting their base and current adaptability amount.
Used mainly in CraftTweaker scripts, creature types can group certain mobs together and give them certain properties. On its own, it can grant an immunity to being set on fire, immunity to certain potions effects, and immunity to critical hits.
This is where the individual combat calculations are done that are entity specific. Combat calculations are shared with DDDCombatCalculations which serves as an event handler to listing to LivingAttack, LivingHurt and LivingDamage events, pass specific info to the combat tracker and then operate on what the tracker does afterwards. Unless you're drastically trying to alter how DDD does calculations or are trying to simulate combat in an unusual way, you probably aren't going to need this.
IPriority is an parent interface extended by many of DDD's other interfaces. IPriority is an important interface as it imposes an ordering on predefined distributions, similar to Comparable. In fact, IPriority merely extends Comparable<IPriority>. The only thing that IPriority is used for over just using Comparable is how it defines a priority system; objects with a higher priority are sorted to be first in an ordered Collection compared to objects of a lower priority. It is effectively reverse ordering on Integer. IPriority has a default implementation for its priority method which returns 0. If you need your predefined distribution to be checked before certain other predefined distributions, return a number that is larger than the priority of whichever distributions you want yours to be checked before.
IHasCreationSource is mainly used for debugging; to know what distribution, damage type, creature type or modifier came from what source. The interface includes an enum of the sources DDD uses internally but you may find it more useful to use your own. You can, because you aren't limited to using just the sources laid out in the Source enum. Just return Source.OTHER in getCreationSource and then return whatever String you like in getCreationSourceString getCreationSourceString is the method that will be called when retrieving sources.
Stored in the api package since it is a way to interact with DDD. Typically, you'll be getting specific damage types through the registries. You usually won't need to do anything specific with a DDDDamageType, it stores information about formatting, death messages and if it is a custom registered type (like through CraftTweaker). You can hide or unhide types so they don't show up on tooltips here. Types also store if they are physical or special here. This category isn't used that much with DDD but it is an option. In particular, DDD only uses it to display physical types (which by default are slashing, piercing and bludgeoning) first in tooltips.
If you wish to add more damage types, you're best off creating instances of DDDCustomDamageType. Just provide the arguments to the constructor. BE SURE TO USE THE CONSTRUCTOR THAT REQUIRES A Source! If you don't, your damage type won't be usable if you have useCustomDamageTypesFromJSON set to false as the default Source for custom damage types is JSON.
If you need more control, you can of course extend DDDCustomDamageType for your own purposes.
Predefined Distributions are how most mod compat usually works. A mod introduces a unique damage source or unique system that deals damage, and DDD needs a way to classify it. This would be for situations not tied to a specific item or mob i.e. Lycanites Mobs' Acid and Sharacid would fall into this category.
A Predefined Distribution has a few components that make it up. The first is a quick switch to determine if the distribution is enabled. If you have some configuration to disable certain features, you may wish to tie the distribution to those switches so that it won't be used to classify anything if disabled. The second is an internal name used for debugging purposes. The last is the actual distribution that makes it up.
A Predefined Distribution has two methods that concern the actual distribution. The first is getDamageDistribution and the second is getTypes. Both of these methods take a non null DamageSource and non null EntityLivingBase (the target taking damage) and return a damage distribution (or a set of types) if the damage source and target match whatever context in which the distribution should apply. Note that these methods should be consistent with one another. That is to say:
- If
getDamageDistributionreturns an emptyOptional, thengetTypesshould return an emptySet. - Otherwise,
getDamageDistribution(src, target).get().getCategories()should contain the same damage types asgetTypes(src, target).
Both methods are provided in case some implementations differ slightly in getting applicable types versus getting a full damage distribution; i.e. some Predefined Distributions that build IDamageDistribution implementations on the fly - those would much rather return just the set of applicable types whenever that is the only information needed instead of building a IDamageDistribution implementation for the caller just for the caller to only get the types. However, in most instances, you may find your implementation looking like:
@Override
public Set<DDDDamageType> getTypes(@Nonnull DamageSource src, @Nonnull EntityLivingBase target) {
return this.getDamageDistribution(src, target).map(IDamageDistribution::getCategories).orElse(Collections.emptySet());
}This will return the categories of the damage distribution if it exists and an empty set otherwise, which is consistent with the contract for DDDPredefinedDistributions.
DDD provides two abstract implementations for DDDPredefinedDistributions you can extend which does some of the leg work for you.
The first is AbstractSingleTypeDist. This predefined distribution does what it says on the tin. If your distribution only uses a single type, you can extend this class to abstract away the whole idea of managing distributions and sets of types. Instead, just have getType return the type you want to use, and have useType return true when the distribution (which is a single type) should be used. useType basically replaces the getDamageDistribution method and simplifies the implementation to return true when it applies and false when it doesn't.
The second implementation just lays most of the ground work: DDDAbstractPredefinedDistribution. This skeletal implementation just abstracts away the internal name and creation source into the constructor. But, chances are you want a different creation source than the built in enum options and want to override the getCreationSourceString method in IHasCreationSource, so your mileage with this class may not be much.
These are modifiers that modify distributions or resistances. These modifiers are just Map<DDDDamageType, Float> that represents the modifications to make alongside additional information that defines how the modifications are made. The main methods are to determine if the modifier is applicable to the specified object (the class is parameterized so the generic here is anything that extends ICapabilityProvider), a method to determine what capability the modifier applies to using an enum, and a boolean to determine if the modifier reallocates weights to "make room" for the modifications it intends to make or it it makes modifications and then scales the weights to add to 100%. An example of scaling is if an original distribution of 50% slashing and 50% piercing received a modifier for 50% poison. 50% poison would be added to the distribution to be 50% slashing, 50% piercing and 50% poison. Then the weights would be scaled to add to 100% (which would be 33.33% slashing, 33.33% piercing and 33.33% poison). This is different from reallocating where 50% would be equally subtracted from the 50% slashing and 50% piercing before adding the 50% poison, giving 25% slashing, 25% piercing and 50% poison. Just note in the case of reallocation, the weights on the modifier are the weights the final capability will have (plus any weights from the original capability). Now this reallocation only really makes the most sense in the context of damage distributions, but it does work on the other capabilities too, it just reallocates based off the total weights, even if they do exceed 100%.
Resistance modifiers are an extension of regular modifiers that add additional methods for modifying adaptability and immunities. It has two sets of immunities to add and immunities to remove and methods to add and remove types from both of those sets. This does make some of the method names a little confusing, but they are documented.
One other important note is the setAdaptability method, which takes a Boolean, and not a boolean. It takes a Boolean object so that you can pass null. Passing null will leave the mob's adaptability unchanged, whereas true or false will specifically ensure the mob either has or doesn't have adaptability, regardless if the mob was originally adaptive or not. Because of autoboxing, you can just pass the boolean primitives into this method just fine. You just also have the ability to pass null.
DDD fires events during damage calculation to allow those calculations to be influenced. All events are fired on the Forge Event Bus.
This abstract type of event is associated with classification events during DDD's calculations. None of these events are cancelable nor do they have any sort of result. These events have the context associated with the classification; the attacking entity (if there is one), the true attacking entity (if it exists; an example of this would be the skeleton that shot the arrow, not the arrow itself) the defending entity taking damage, and the DamageSource object. The DamageSource object has its own methods to get the immediate source and the true source; these will return the same object as getImmediateAttacker and getTrueAttacker respectively. The getImmediateAttacker and getTrueAttacker are just syntactic sugar.
Fired as soon DDD classifies damage. It passes to the event the DamageMap (which recall from the IDamageDistribution section is just a Map<DDDDamageType, Float>), which contains the damage being inflicted. Note this is not the distribution; but the damage that was distributed. You can work backwards to find the weights of the distribution that was used, provided no other event listener acted before and changed the map, but the distribution itself is not passed to the event. The DamageMap itself is also not accessible, but methods to get and set members of the map are. You can use the registries to get an Iterable of all the registered damage types and use that to query the map if you really must iterate over all values in the map.
This event is fired when DDD classifies the resistances and immunities a mob has. It fires this event so the values (resistance or immunities) can be altered for this damage calculation only. For permanent changes, consider using a modifier. It uses a ResistMap which maps damage types to the resistances the mob has to that type. It, much like the DamageMap is just a Map<DDDDamageType, Float>.
This abstract type of event is fired during DDD's calculations, typically after classification, but not always. These events are for interacting with the way DDD actually performs calculations; some events may have results or be cancelable. It has a lot of the same methods as DDDClassificationEvent.
This event is fired when DDD determines that a shield is being used to block damage that can actually be blocked (this event won't fire for unblockable damage). It has two unique methods; one to get the ItemStack that is the shield being used, and another to get the ShieldDistribution that is being used. This is a copy of the shield's ShieldDistribution, and changes made here will affect this calculation only.
This event can be canceled, and has no result. If canceled, DDD will skip shield calculations.
The UpdateAdaptiveResistanceEvent is fired at the end of calculations to determine if a mob should update its adaptive resistances. It is fired even if a mob doesn't have adaptive resistance. It contains a lot of information; the resistances and immunities the mob has for this calculation and the adaptive amount the mob is using. The adaptive amount can be changed here and that will change the adaptive amount the mob uses for this calculation only. Resistances and immunities can't be changed at this point, as DDD has already finished calculations; further changes wouldn't do anything. The DamageMap is also available to see what damage the mob is adapting to, but note that the DamageMap here is a copy; changes to this map will not change the amount of damage inflicted. Notably, you can also remove types from the DamageMap which will prevent the mob from adapting to that type. You can do so with the ignoreType method but since getDamageToAdaptTo returns the whole DamageMap, changes can be made there. If you have adaptive weakness enabled, changing the values in the DamageMap here will change how the mob adapts to the damage if adaptive weakness applies.
Another thing of note is that this is fired at the end of DDD's calculations (minus adaptability calculations). This lets you see the final amount of damage being inflicted alongside the resistances and immunities the mob had during calculations. While these values can't be changed to alter the damage dealt, it can be used if you need to check to see if a mob took damage of a specific type for example.
This event is not cancelable, but does have a result:
-
ALLOW: Will force the mob to adapt to the damage, regardless if it is normally adaptive. Note that if a mob is not adaptive and is force to adapt, it will use its adaptability amount, which is set to 0 for mobs that aren't adaptive, so you'll need to callsetAdaptiveAmountto give the mob an adaptive amount if it isn't adaptive. The event doesn't include a method to determine if the mob was originally adaptive or not, but since the mob is available, you can determine it withDDDAPI.accessor.getMobResistances(event.getDefender()).filter(IMobResistances::hasAdpativeResistance).isPresent(). However, if forcing an adaptability update, it may be better to instead usegetAdaptiveAmountto check if it is zero and can reasonably assume and an adaptability amount of zero indicates the mob is very likely not adaptive. Lastly, a mob that is forced to adapt when it does not have adaptability will keep it's adapted resistances until it is forced to adapt again. -
DEFAULT: Will make no changes; a mob will only adapt if it has adaptive resistance. -
DENY: Will prevent the mob from adapting this time, even if the mob is normally adaptive.