Skip to content

Commit

Permalink
apply feedback, part 1
Browse files Browse the repository at this point in the history
  • Loading branch information
IchHabeHunger54 committed Nov 21, 2024
1 parent 44951b9 commit 2986514
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 45 deletions.
70 changes: 34 additions & 36 deletions docs/blocks/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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<Block> 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.
Expand All @@ -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) {
// ...
}
Expand All @@ -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) {
// ...
}
Expand Down Expand Up @@ -170,28 +168,28 @@ 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<Block> 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<Block> 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.
);
```

If you want to use `Block::new`, you can leave out the factory entirely:

```java
public static final DeferredBlock<Block> EXAMPLE_BLOCK = BLOCKS.registerSimpleBlock(
"example_block",
BlockBehaviour.Properties.of() // The properties to use.
"example_block",
BlockBehaviour.Properties.of() // The properties to use.
);
```

Expand Down Expand Up @@ -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.
Expand Down
12 changes: 10 additions & 2 deletions docs/entities/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Attribute>`s instead of `Attribute`s. This is also why with custom attributes (see below), we explicitly store the `Holder<Attribute>`.
:::

## 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.
Expand Down Expand Up @@ -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<Attribute> MY_ATTRIBUTE = ATTRIBUTES.register("my_attribute", new RangedAttribute(
public static final Holder<Attribute> MY_ATTRIBUTE = ATTRIBUTES.register("my_attribute", () -> new RangedAttribute(
// The translation key to use.
"attributes.yourmodid.my_attribute",
// The default value.
Expand Down
24 changes: 19 additions & 5 deletions docs/entities/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ public static final Supplier<EntityType<MyEntity>> 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.
Expand Down Expand Up @@ -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`).
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<Vec3>`. 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.
Expand Down Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion docs/entities/livingentity.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]._
Expand Down Expand Up @@ -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<SpawnEggItem> MY_ENTITY_SPAWN_EGG = ITEMS.register("my_entity_spawn_egg",
DeferredItem<SpawnEggItem> MY_ENTITY_SPAWN_EGG = ITEMS.registerItem("my_entity_spawn_egg",
properties -> new SpawnEggItem(
// The entity type to spawn.
MY_ENTITY_TYPE.get(),
Expand Down
3 changes: 2 additions & 1 deletion docs/items/interactions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit 2986514

Please sign in to comment.