diff --git a/docs/blocks/index.md b/docs/blocks/index.md index 6c86e23e..c3750b3e 100644 --- a/docs/blocks/index.md +++ b/docs/blocks/index.md @@ -25,8 +25,8 @@ After registering the block, all references to the new `my_block` should use thi ```java level.getBlockState(position) // returns the blockstate placed in the given level (world) at the given position - //highlight-next-line - .is(MyBlockRegistrationClass.MY_BLOCK); + //highlight-next-line + .is(MyBlockRegistrationClass.MY_BLOCK); ``` This approach also has the convenient effect that `block1 == block2` works and can be used instead of Java's `equals` method (using `equals` still works, of course, but is pointless since it compares by reference anyway). @@ -69,16 +69,16 @@ So for example, a simple implementation would look something like this: ```java //BLOCKS is a DeferredRegister.Blocks public static final DeferredBlock MY_BETTER_BLOCK = BLOCKS.register( - "my_better_block", - registryName -> new Block(BlockBehaviour.Properties.of() - //highlight-start - .setId(ResourceKey.create(Registries.BLOCK, registryName)) - .destroyTime(2.0f) - .explosionResistance(10.0f) - .sound(SoundType.GRAVEL) - .lightLevel(state -> 7) - //highlight-end - )); + "my_better_block", + registryName -> new Block(BlockBehaviour.Properties.of() + //highlight-start + .setId(ResourceKey.create(Registries.BLOCK, registryName)) + .destroyTime(2.0f) + .explosionResistance(10.0f) + .sound(SoundType.GRAVEL) + .lightLevel(state -> 7) + //highlight-end + )); ``` For further documentation, see the source code of `BlockBehaviour.Properties`. For more examples, or to look at the values used by Minecraft, have a look at the `Blocks` class. @@ -104,7 +104,6 @@ If the block subclass only takes in the `BlockBehaviour.Properties`, then `Block ```java // For some block subclass public class SimpleBlock extends Block { - public SimpleBlock(BlockBehavior.Properties properties) { // ... } @@ -129,7 +128,6 @@ If the block subclass contains more parameters, then [`RecordCodecBuilder#mapCod ```java // For some block subclass public class ComplexBlock extends Block { - public ComplexBlock(int value, BlockBehavior.Properties properties) { // ... } @@ -170,19 +168,19 @@ We already discussed how to create a `DeferredRegister.Blocks` [above], as well public static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks("yourmodid"); public static final DeferredBlock EXAMPLE_BLOCK = BLOCKS.register( - "example_block", registryName -> new Block( - BlockBehaviour.Properties.of() - // The ID must be set on the block - .setId(ResourceKey.create(Registries.BLOCK, registryName)) - ) + "example_block", registryName -> new Block( + BlockBehaviour.Properties.of() + // The ID must be set on the block + .setId(ResourceKey.create(Registries.BLOCK, registryName)) + ) ); // Same as above, except that the block properties are constructed eagerly. // setId is also called internally on the properties object. public static final DeferredBlock EXAMPLE_BLOCK = BLOCKS.registerBlock( - "example_block", - Block::new, // The factory that the properties will be passed into. - BlockBehaviour.Properties.of() // The properties to use. + "example_block", + Block::new, // The factory that the properties will be passed into. + BlockBehaviour.Properties.of() // The properties to use. ); ``` @@ -190,8 +188,8 @@ If you want to use `Block::new`, you can leave out the factory entirely: ```java public static final DeferredBlock EXAMPLE_BLOCK = BLOCKS.registerSimpleBlock( - "example_block", - BlockBehaviour.Properties.of() // The properties to use. + "example_block", + BlockBehaviour.Properties.of() // The properties to use. ); ``` @@ -282,32 +280,32 @@ The following subsections further break down these stages into actual method cal #### Mining Speed -The mining speed is calculated as f from the block's hardness, the used [tool]'s speed, and several entity [attributes] according to the following rules. +The mining speed is calculated as f from the block's hardness, the used [tool]'s speed, and several entity [attributes] according to the following rules: ```java // This will return the tool's mining speed, or 1 if the held item is either empty, not a tool, // or not applicable for the block being broken. -float f = item.getDestroySpeed(); +float destroySpeed = item.getDestroySpeed(); // If we have an applicable tool, add the minecraft:mining_efficiency attribute as an additive modifier. -if (f > 1) { - f += player.getAttributeValue(Attributes.MINING_EFFICIENCY); +if (destroySpeed > 1) { + destroySpeed += player.getAttributeValue(Attributes.MINING_EFFICIENCY); } // Apply effects from haste, conduit power, and slowness multiplicatively. -if (player.hasEffect(MobEffects.DIG_SPEED)) { f *= ...; } -if (player.hasEffect(MobEffects.CONDUIT_POWER)) { f *= ...; } -if (player.hasEffect(MobEffects.DIG_SLOWDOWN)) { f *= ...; } +if (player.hasEffect(MobEffects.DIG_SPEED)) { destroySpeed *= ...; } +if (player.hasEffect(MobEffects.CONDUIT_POWER)) { destroySpeed *= ...; } +if (player.hasEffect(MobEffects.DIG_SLOWDOWN)) { destroySpeed *= ...; } // Add the minecraft:block_break_speed attribute as a multiplicative modifier. -f *= player.getAttributeValue(Attributes.BLOCK_BREAK_SPEED); +destroySpeed *= player.getAttributeValue(Attributes.BLOCK_BREAK_SPEED); // If the player is underwater, apply the underwater mining speed penalty multiplicatively. if (player.isEyeInFluid(FluidTags.WATER)) { - f *= player.getAttributeValue(Attributes.SUBMERGED_MINING_SPEED); + destroySpeed *= player.getAttributeValue(Attributes.SUBMERGED_MINING_SPEED); } // If the player is trying to break a block in mid-air, make the player mine 5 times slower. if (!player.onGround()) { - f /= 5; + destroySpeed /= 5; } -f = /* The PlayerEvent.BreakSpeed event is fired here, allowing modders to further modify this value. */; -return f; +destroySpeed = /* The PlayerEvent.BreakSpeed event is fired here, allowing modders to further modify this value. */; +return destroySpeed; ``` The exact code for this can be found in `Player#getDigSpeed` for reference. diff --git a/docs/entities/attributes.md b/docs/entities/attributes.md index 16ecfdeb..b2b282e1 100644 --- a/docs/entities/attributes.md +++ b/docs/entities/attributes.md @@ -99,13 +99,21 @@ The `AttributeMap` of an entity can be retrieved by calling `LivingEntity#getAtt // Get the attribute map. AttributeMap attributes = livingEntity.getAttributes(); // Get an attribute instance. This may be null if the entity does not have the attribute. -AttributeInstance instance = attributes.get(Attributes.ARMOR); +AttributeInstance instance = attributes.getInstance(Attributes.ARMOR); // Get the value for an attribute. Will fallback to the default for the entity if needed. double value = attributes.getValue(Attributes.ARMOR); // Of course, we can also check if an attribute is present to begin with. if (attributes.hasAttribute(Attributes.ARMOR)) { ... } + +// Alternatively, LivingEntity also offers shortcuts: +AttributeInstance instance = livingEntity.getAttribute(Attributes.ARMOR); +double value = livingEntity.getAttributeValue(Attributes.ARMOR); ``` +:::info +When handling attributes, you will almost exclusively use `Holder`s instead of `Attribute`s. This is also why with custom attributes (see below), we explicitly store the `Holder`. +::: + ## Attribute Modifiers In contrast to querying, changing the attribute values is not as easy. This is mainly because there may be multiple changes required to an attribute at the same time. @@ -191,7 +199,7 @@ For the attributes themselves, there are three classes you can choose from: Using `RangedAttribute` as an example (the other two work similarly), registering an attribute would look like this: ```java -public static final Holder MY_ATTRIBUTE = ATTRIBUTES.register("my_attribute", new RangedAttribute( +public static final Holder MY_ATTRIBUTE = ATTRIBUTES.register("my_attribute", () -> new RangedAttribute( // The translation key to use. "attributes.yourmodid.my_attribute", // The default value. diff --git a/docs/entities/index.md b/docs/entities/index.md index 8d5a2971..8f86a8af 100644 --- a/docs/entities/index.md +++ b/docs/entities/index.md @@ -62,6 +62,8 @@ public static final Supplier> MY_ENTITY = ENTITY_TYPES.regi .canSpawnFarFromPlayer() // The range in which the entity is kept loaded by the client, in chunks. // Vanilla values for this vary, but it's often something around 8 or 10. Defaults to 5. + // Be aware that if this is greater than the client's chunk view distance, + // then that chunk view distance is effectively used here instead. .clientTrackingRange(8) // How often update packets are sent for this entity, in once every x ticks. This is set to higher values // for entities that have predictable movement patterns, for example projectiles. Defaults to 3. @@ -162,9 +164,11 @@ If we now boot up the game now and enter a world, we have exactly one way of spa Obviously, we want to add our entities some other way. The easiest way to do so is through the `LevelWriter#addFreshEntity` method. This method simply accepts an `Entity` instance and adds it to the world, like so: ```java -// In some method that has a level available -MyEntity entity = new MyEntity(level, 100.0, 200.0, 300.0); -level.addFreshEntity(entity); +// In some method that has a level available, only on the server +if (!level.isClientSide()) { + MyEntity entity = new MyEntity(level, 100.0, 200.0, 300.0); + level.addFreshEntity(entity); +} ``` This will be used for pretty much all non-living entities. Players should obviously not be spawned yourself, and `Mob`s have [their own ways of spawning][mobspawn] (though they can also be added via `#addFreshEntity`). @@ -233,7 +237,7 @@ public ItemStack getPickResult() { } ``` -Your entity can also be disabled from picking entirely like so: +Your entity can also be disabled from picking entirely. The primary use case for this would be multipart entities, such as the ender dragon, where the parent entity has picking disabled, but the parts have it enabled again, for finer hitbox tuning. Picking can be disabled like so: ```java @Override @@ -266,6 +270,8 @@ Vanilla defines the following four `EntityAttachment`s: `PASSENGER` and `VEHICLE` are related in that they are used in the same context. First, `PASSENGER` is applied to position the rider. Then, `VEHICLE` is applied on the rider. ::: +Every attachment can be thought of as a mapping from `EntityAttachment` to `List`. The amount of points actually used depends on the consuming system. For example, boats and camels will use two `PASSENGER` points, while entities like horses or minecarts will only use one `PASSENGER` point. + `EntityType.Builder` also has some helpers related to `EntityAttachment`s: - `#passengerAttachment()`: Used to define `PASSENGER` attachments. Comes in two variants. @@ -311,7 +317,15 @@ There are three big subgroups of projectiles: Other projectiles that directly extend `Projectile` include fireworks, fishing bobbers and shulker bullets. -A new projectile can be created by extending `Projectile` or a fitting subclass, and then overriding the methods required for adding your functionality. Common methods to override would be `#shoot`, which calculates and sets the correct velocity on the projectile; `#onHit`, `#onHitEntity` and `#onHitBlock`, which do exactly what you'd expect; and `#getOwner` and `#setOwner`, which get and set the owning entity, respectively. +A new projectile can be created by extending `Projectile` or a fitting subclass, and then overriding the methods required for adding your functionality. Common methods to override include: + +- `#shoot`: Calculates and sets the correct velocity on the projectile. +- `#onHit`: Called when something is hit. + - `#onHitEntity`: Called when that something is an [entity]. + - `#onHitBlock`: Called when that something is a [block]. +- `#getOwner` and `#setOwner`, which get and set the owning entity, respectively. +- `#deflect`, which deflects the projectile based on the passed `ProjectileDeflection` enum value. +- `#onDeflection`, which is called from `#deflect` for any post-deflection behavior. [block]: ../blocks/index.md [damageevents]: livingentity.md#damage-events diff --git a/docs/entities/livingentity.md b/docs/entities/livingentity.md index 5eea2f7d..21977ed8 100644 --- a/docs/entities/livingentity.md +++ b/docs/entities/livingentity.md @@ -86,10 +86,14 @@ This event should be pretty self-explanatory. It is fired when armor damage from This event is called immediately before the damage is done. The `DamageContainer` is fully populated, the final damage amount is available, and the event can no longer be canceled as the attack is considered successful by this point. +At this point, all kinds of modifiers are available, allowing you to finely modify the damage amount. Be aware that things like armor damage are already done by this point. + #### `LivingDamageEvent.Post` This event is called after the damage has been done, absorption has been reduced, the combat tracker has been updated, and stats and game events have been handled. It is not cancellable, as the attack has already happened. This event would commonly be used for post-attack effects. Note that the event is fired even if the damage amount is zero, so check that value accordingly if needed. +If you are calling this on your own entity, you should consider overriding `ILivingEntityExtension#onDamageTaken()` instead. Unlike `LivingDamageEvent.Post`, this is only called if the damage is greater than zero. + ## Mob Effects _See [Mob Effects & Potions][mobeffects]._ @@ -134,7 +138,7 @@ It is common (though not required) to [register] a spawn egg for mobs. This is d ```java // Assume we have a DeferredRegister.Items called ITEMS -DeferredItem MY_ENTITY_SPAWN_EGG = ITEMS.register("my_entity_spawn_egg", +DeferredItem MY_ENTITY_SPAWN_EGG = ITEMS.registerItem("my_entity_spawn_egg", properties -> new SpawnEggItem( // The entity type to spawn. MY_ENTITY_TYPE.get(), diff --git a/docs/items/interactions.md b/docs/items/interactions.md index f674d634..bca11538 100644 --- a/docs/items/interactions.md +++ b/docs/items/interactions.md @@ -113,7 +113,8 @@ Returning `InteractionResult#FAIL` here while considering the main hand will pre - `InputEvent.InteractionKeyMappingTriggered` is fired with the left mouse button and the main hand. If the [event][event] is [canceled][cancel], the pipeline ends. - Depending on what you are looking at (using the `HitResult` in `Minecraft.getInstance().hitResult`), different things happen: - If you are looking at an [entity] that is within your reach: - - If the player is not in creative, the pipeline ends. + - If `Entity#isPickable` returns false, the pipeline ends. + - If you are not in creative, the pipeline ends. - `IEntityExtension#getPickedResult` is called. The resulting `ItemStack` is added to the player's inventory. - By default, this method forwards to `Entity#getPickResult`, which can be overridden by modders. - If you are looking at a [block] that is within your reach: