TogAr2 / MinestomPvP

Combat library for Minestom
Apache License 2.0
76 stars 32 forks source link
combat legacy-minecraft minecraft minecraft-combat minestom minestom-library pvp

MinestomPvP

license platform

MinestomPvP is an extension for Minestom. It tries to mimic vanilla (modern and pre-1.9) PvP as good as possible, while also focusing on customizability and usability.

But, MinestomPvP does not only provide PvP, it also provides everything around it (e.g., status effects and food). You can easily pick which features you want to use.

The maven repository is available on jitpack.

MinestomPvP has been rewritten, see Important changes

Table of Contents

Important changes

MinestomPvP has recently been rewritten. Most features are now independent of each other and can be used separately.

Major changes include:

Features

Currently, most vanilla PvP features are supported.

Plans

Usage

Before doing anything else, you should call MinestomPvP.init(). This will make sure everything is registered correctly.

After you've initialized the library, you can start using combat features. For the most basic setup you can use the following:

MinestomPvP.init();

CombatFeatureSet modernVanilla = CombatFeatures.modernVanilla();
MinecraftServer.getGlobalEventHandler().addChild(modernVanilla.createNode());

This will give you a full vanilla experience without any customization.

Every combat feature has a createNode() method, which returns an EventNode with all listeners of the feature attached. This event node can be added to another event node to enable the feature within that scope. In the example above, it is being added to the global event handler, which means the feature will work everywhere.

The combat feature used in this example is a CombatFeatureSet. This is essentially a container for a list of combat features. There are two feature sets already defined by MinestomPvP:

Customization

The CombatFeatures class contains a field for every individual combat feature which has been defined by MinestomPvP itself. For example, you can add fall damage to your instance like so:

Instance instance;

CombatFeatureSet featureSet = CombatFeatures.empty()
        .version(CombatVersion.MODERN)
        .add(CombatFeatures.VANILLA_FALL)
        .add(CombatFeatures.VANILLA_PLAYER_STATE)
        .build();
instance.eventNode().addChild(featureSet.createNode());

As you can see, CombatFeatures.empty() provides you with a builder-like structure (CombatConfiguration) to which features can be added.

This combat configuration also contains convenience methods:

In the example above, a PLAYER_STATE feature is added alongside the FALL feature, because the fall feature depends on it. CombatConfiguration takes care of handling these dependencies for you. The order in which the features are added does not matter. It is also possible to leave out the PLAYER_STATE feature: a NO_OP feature will then be used, which in this case will always signal to the fall feature that the player is not climbing.

Upon calling CombatConfiguration#build(), the combat configuration resolves all these dependencies and creates a CombatFeatureSet in which all the features are instantiated.

Features defined inside the CombatFeatures class are not yet instantiated, but are a DefinedFeature. The CombatConfiguration will instantiate the features for you, which will turn them into CombatFeature instances. An instantiated feature always knows its dependencies.

Legacy PvP

Earlier minecraft versions (pre-1.9) used a different PvP system, which to this day is still preferred by some. Legacy is the term used to describe this type of PvP throughout the library. You can get the CombatFeatureSet for legacy PvP using CombatFeatures.legacyVanilla().

To disable attack cooldown for a player, use MinestomPvP.setLegacyAttack(player, true). To enable the cooldown again, use false instead of true.

Knockback

A lot of servers like to customize their 1.8 knockback. It is also possible to do so with this extension. In EntityKnockbackEvent, you can set a LegacyKnockbackSettings object. It contains information about how the knockback is calculated. A builder is obtainable by using LegacyKnockbackSettings.builder(). For more information, check the config of BukkitOldCombatMechanics.

Integration

To integrate this extension into your minestom server, you may have to tweak a little bit to make sure everything works correctly.

The extension uses a custom player implementation, if you use one, it is recommended to extend CombatPlayerImpl. If you for some reason can't, make sure to implement CombatPlayer in a similar fashion to CombatPlayerImpl. The implementation of MinestomPvP is registered inside MinestomPvP.init(), so register yours after initializing the library.

To allow explosions, you have to register an explosion supplier to every instance in which they are used. Implementations of ExplosionFeature might provide an explosion supplier.

CombatFeatureSet featureSet;
Instance instance;

instance.setExplosionSupplier(featureSet.get(FeatureType.EXPLOSION).getExplosionSupplier());

Keep in mind that the explosion supplier can be different depending on the explosion feature, so always register the one from the explosion feature which is active in the instance.

Registries

MinestomPvP has several registries, which you can also register to in order to create custom behavior:

You can use the static #register(...) method in those classes to add custom entries.

You can also use the class Tool, which contains all tools and their properties (not all properties are currently included, will change soon). The same applies to ToolMaterial (wood, stone, ...) and ArmorMaterial.

Events

The library provides several events:

Custom combat features

It is possible to create your own combat features, which can extend an existing one or be completely independent. Below is an explanation followed by an example.

In order to be compatible with the library, your combat features must implement CombatFeature. It is also possible to implement RegistrableFeature instead, which will provide you with a createNode() method. In this case, you must also implement RegistrableFeature#init(EventNode), which attaches all the listeners to the given event node.

After this, you must create a FeatureType for your custom feature. If you are implementing an existing feature, use existing feature types in the FeatureType class. Otherwise, you can create your own using FeatureType.of(String, F). The first argument will be the name, the second the NO_OP feature which will be used when no implementation is present. It is recommended to create an interface for your custom feature type which extends CombatFeature (or RegistrableFeature). This way, you can easily specify methods to expose to other features.

Lastly, it is needed to create a DefinedFeature instance for your custom implementation. This defined feature defines an implementation of your feature type, and it can be used to add your implementation to a combat configuration.

Example of a custom feature type, with 1 method which can be used by other features:

interface MyCustomFeature extends CombatFeature {
    MyCustomFeature NO_OP = new MyCustomFeature() {};

    FeatureType TYPE = FeatureType.of("MY_CUSTOM", NO_OP);

    boolean isItWorking();
}

Example of an implementation of this custom feature type, which listens for events and implements the method:

class MyCustomFeatureImpl implements MyCustomFeature, RegistrableFeature {
    public static final DefinedFeature<MyCustomFeatureImpl> DEFINED = new DefinedFeature<>(
            MyCustomFeature.TYPE, configuration -> new MyCustomFeatureImpl()
    );

    @Override
    public void init(EventNode<PlayerInstanceEvent> node) {
        node.addListener(PlayerChatEvent.class, event -> {
            // Do something...
        });
    }

    @Override
    public boolean isItWorking() {
        return true;
    }
}

Now you can use your own feature:

MinecraftServer.getGlobalEventHandler().addChild(
        CombatFeatures.single(MyCustomFeatureImpl.DEFINED)
);

As you can see, it is also possible to use CombatFeatures.single(DefinedFeature) to instantiate a single feature without dependencies.

Depending on other features

Say, you want to access a players fall distance in your own feature. You can do this by depending on FallFeature.

class MyCustomFeatureImpl implements MyCustomFeature {
    public static final DefinedFeature<MyCustomFeatureImpl> DEFINED = new DefinedFeature<>(
            MyCustomFeature.TYPE, configuration -> new MyCustomFeatureImpl(configuration),
            FeatureType.FALL
    );

    private final FeatureConfiguration configuration;
    private FallFeature fallFeature;

    public MyCustomFeatureImpl(FeatureConfiguration configuration) {
        this.configuration = configuration;
    }

    @Override
    public void initDependencies() {
        this.fallFeature = configuration.get(FeatureType.FALL);
    }

    @Override
    public boolean isItWorking() {
        Player player; // Some player
        return fallFeature.getFallDistance(player) > 3;
    }
}

Note that the FeatureConfiguration which can be used to get the FallFeature from is only ready when initDependencies() is called. This is due to complications with recursive dependencies between features.

Player init

You might want to initialize certain things for a player upon joining or whenever they get reset (respawn). This is possible by using the player init of a defined feature. Its constructor can also take a DefinedFeature.PlayerInit. This is a class whose init(Player player, boolean firstInit) method will be called upon a player join or reset.

You can for example use this player init to set tags on a player. The vanilla implementation of FallFeature uses it to set the fall distance tag on the player to 0.

There are two criteria to use the player init:

Contributing

You can contribute in multiple ways. If you have an issue or a great idea, you can open an issue. You may also open a new pull request if you have made something for this project and you think it will fit in well.

If anything does not integrate with your project, you can also open an issue (or submit a pull request). I aim towards making this extension as usable as possible!

Credits

Thanks to kiip1 for testing and finding bugs.

I used BukkitOldCombatMechanics as a resource for recreating legacy pvp.