Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions docs/entity_overrides/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
---
title: Entity Overrides
lang: en-US
next:
text: a
prev:
text: o
---

# Entity Overrides

catharsis has a way to change entities!!!!

simply provide a file in `{your_name_space}:catharsis/entity_definitions/{entity_name}.json`

that loosk a bit liek this

```json
{
"conditions": [
{
"type": "condition_type",
...
}
],
"replacement": "{your_name_space}:{entity_replacement_name}.json"
}
```

and another file in `{your_name_here}:catharsis/entities/{entity_replacement_name}.json`

that looks like this

```json
{
"texture": "{your_name_space}:textures/{texture path here}.png",
"model": "{your_name_space}:models/{model_path_here}.geo.json"
}
```

and put a `.png` fiel where the texture path leads to and a bedrock entity geometry (`.geo.json`) where that one points to. also if you name the bones right the model will use the main entity's animations!!!! the mod will log if a bone is named wrongly so dont worry about naming things right straight away.



## Condition Types

there are a lot of condition types

### `"type": "player"`
lets you access state about a player entity
keys:
`"skin"`: a reference to a skin url, if the entity matches this skin _exactly_ it is used
`"only_npc"`: the entity matches only if it isnt a real player (useful for npcs)

### `"type": "identity"`
lets you access state about regular entities
keys:
`"type"`: matches the entity's type (eg: `minecraft:zombie` for zombies)
`"uuid"`: matches the entity's uuid. skyblock mostly uses random uuids
`"name"`: matches the entity's name

### `"type": "attribute"`
allows access to entity attributes (such as max health)
keys:
`"attribute"`: which attribute to check
`"value"` or `"values"`: matches one or multiple values

### `"type": "island"`
matches if you are on an island
keys:
`"island"` or `"islands"`: matches one or multiple islands

### `"type": "equipment"`
matches the entity's equipment
keys:
`"slot"`: which slot to check
`"property"`: what BOOLEAN property to check (this can add more keys)

## Model quirks
Cannot handle per-face uv. will break. Everything else _should_ work fine
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"conditions": [
{
"type": "identity",
"entity_type": "minecraft:zombie"
},
{
"type": "attribute",
"attribute": "minecraft:max_health",
"value": 100
}
],
"replacement": "your_name_space:graveyard_zombie"
}
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
fabric-loader = "0.18.3"
fabric-language-kotlin = "1.13.6+kotlin.2.2.20"

skyblockapi = "4.0.1"
skyblockapi = "4.0.2"
repo-lib = "3.0.2"
devauth = "1.2.1"
hypixelapi = "1.0.1+build.1+mc1.21"
Expand Down
6 changes: 6 additions & 0 deletions gradle/replacements.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,9 @@ regex = "import net.minecraft.world.entity.ItemOwner(?!;)"
to = "import net.minecraft.world.entity.LivingEntity as ItemOwner"
reverseRegex = "import net.minecraft.world.entity.LivingEntity as ItemOwner"
reverse = "import net.minecraft.world.entity.ItemOwner"

[avatar_entity]
type = "string"
condition = "<=1.21.8"
from = "import net.minecraft.client.entity.ClientAvatarEntity"
to = "import net.minecraft.client.player.AbstractClientPlayer as ClientAvatarEntity"
6 changes: 6 additions & 0 deletions src/catharsis.accesswidener
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@ classTweaker v1 named
accessible method net/minecraft/client/renderer/GameRenderer pick (Lnet/minecraft/world/entity/Entity;DDF)Lnet/minecraft/world/phys/HitResult;
accessible field net/minecraft/client/model/PlayerCapeModel cape Lnet/minecraft/client/model/geom/ModelPart;

extendable class net/minecraft/client/model/geom/ModelPart
accessible field net/minecraft/client/model/geom/ModelPart children Ljava/util/Map;
accessible field net/minecraft/client/model/geom/ModelPart cubes Ljava/util/List;

inject-interface net/minecraft/client/gui/screens/packs/PackSelectionModel$EntryBase me/owdding/catharsis/hooks/pack/PackEntryHook
inject-interface net/minecraft/client/renderer/item/properties/select/SelectItemModelProperty$Type me/owdding/catharsis/hooks/armor/SelectItemModelPropertyTypeHook
inject-interface net/minecraft/client/renderer/entity/state/EntityRenderState me/owdding/catharsis/hooks/entity/EntityRenderStateHook
inject-interface net/minecraft/client/renderer/entity/state/LivingEntityRenderState me/owdding/catharsis/hooks/armor/LivingEntityRenderStateHook
inject-interface net/minecraft/client/renderer/item/ItemStackRenderState me/owdding/catharsis/hooks/items/ItemStackRenderStateHook
inject-interface net/minecraft/client/resources/model/ModelManager me/owdding/catharsis/hooks/items/ModelManagerHook
inject-interface net/minecraft/server/packs/repository/Pack me/owdding/catharsis/hooks/pack/PackMetadataHook
inject-interface net/minecraft/server/packs/repository/Pack$Metadata me/owdding/catharsis/hooks/pack/PackMetadataHook
inject-interface net/minecraft/world/inventory/Slot me/owdding/catharsis/hooks/gui/SlotHook
inject-interface net/minecraft/world/item/component/ItemLore me/owdding/catharsis/hooks/text/TooltipProviderHook
inject-interface net/minecraft/world/entity/Entity me/owdding/catharsis/hooks/entity/EntityHook
14 changes: 14 additions & 0 deletions src/main/java/me/owdding/catharsis/hooks/entity/EntityHook.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package me.owdding.catharsis.hooks.entity;

import me.owdding.catharsis.features.entity.models.CustomEntityModel;

public interface EntityHook {

default void catharsis$resetCustomModel() {
throw new UnsupportedOperationException();
}

default CustomEntityModel catharsis$getCustomEntityModel() {
throw new UnsupportedOperationException();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package me.owdding.catharsis.hooks.entity;

import me.owdding.catharsis.features.entity.models.CustomEntityModel;
import org.jetbrains.annotations.Nullable;

public interface EntityRenderStateHook {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fabric has a generic render state injection so maybe that can be used instead of this? idk if its in 1.21.8 though

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is not :c


default void catharsis$setCustomEntityModel(@Nullable CustomEntityModel textureLocation) {
throw new UnsupportedOperationException();
}

default @Nullable CustomEntityModel catharsis$getCustomEntityModel() {
throw new UnsupportedOperationException();
}
}
45 changes: 45 additions & 0 deletions src/main/java/me/owdding/catharsis/mixins/entity/EntityMixin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package me.owdding.catharsis.mixins.entity;

import me.owdding.catharsis.features.entity.CustomEntityDefinitions;
import me.owdding.catharsis.features.entity.models.CustomEntityModel;
import me.owdding.catharsis.features.entity.models.CustomEntityModels;
import me.owdding.catharsis.hooks.entity.EntityHook;
import net.minecraft.world.entity.Entity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;

@Mixin(Entity.class)
public class EntityMixin implements EntityHook {

@Unique
private boolean catharsis$hasComputedModel = false;
@Unique
private CustomEntityModel catharsis$computedReplacement = null;

@Override
public void catharsis$resetCustomModel() {
catharsis$hasComputedModel = false;
}

@Override
public CustomEntityModel catharsis$getCustomEntityModel() {
if (catharsis$hasComputedModel) {
return catharsis$computedReplacement;
}

var customEntity = CustomEntityDefinitions.getFor((Entity) (Object) this);

catharsis$hasComputedModel = true;

CustomEntityModel customModel = null;

if (customEntity != null) {
customModel = CustomEntityModels.getModel(customEntity.getReplacement());
}


catharsis$computedReplacement = customModel;

return customModel;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package me.owdding.catharsis.mixins.entity;

import me.owdding.catharsis.features.entity.models.CustomEntityModel;
import me.owdding.catharsis.hooks.entity.EntityRenderStateHook;
import net.minecraft.client.renderer.entity.state.EntityRenderState;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;

@Mixin(EntityRenderState.class)
public class EntityRenderStateMixin implements EntityRenderStateHook {
@Unique
private CustomEntityModel catharsis$customTexture = null;

@Override
public void catharsis$setCustomEntityModel(CustomEntityModel textureLocation) {
catharsis$customTexture = textureLocation;
}

@Override
public CustomEntityModel catharsis$getCustomEntityModel() {
return catharsis$customTexture;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package me.owdding.catharsis.mixins.entity;

import me.owdding.catharsis.features.entity.models.CustomEntityModel;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.state.EntityRenderState;
import net.minecraft.world.entity.Entity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

@Mixin(EntityRenderer.class)
public class EntityRendererMixin<T extends Entity, S extends EntityRenderState> {

@Inject(
method = "extractRenderState",
at = @At("TAIL")
)
public void extractRenderState(T entity, S reusedState, float partialTick, CallbackInfo ci) {
CustomEntityModel customTexture = entity.catharsis$getCustomEntityModel();
reusedState.catharsis$setCustomEntityModel(customTexture);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package me.owdding.catharsis.features.entity

import me.owdding.catharsis.features.entity.conditions.EntityCondition
import me.owdding.ktcodecs.GenerateCodec
import net.minecraft.resources.Identifier
import net.minecraft.world.entity.Entity


@GenerateCodec
data class CustomEntityDefinition(
val conditions: List<EntityCondition>,
val replacement: Identifier
) {
fun matches(entity: Entity): Boolean {
if (conditions.isEmpty()) return false

for (condition in conditions) {
if (!condition.matches(entity)) return false
}
return true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package me.owdding.catharsis.features.entity

import com.google.gson.GsonBuilder
import com.google.gson.JsonElement
import me.owdding.catharsis.Catharsis
import me.owdding.catharsis.generated.CatharsisCodecs
import me.owdding.ktmodules.Module
import net.minecraft.resources.FileToIdConverter
import net.minecraft.server.packs.resources.ResourceManager
import net.minecraft.server.packs.resources.SimplePreparableReloadListener
import net.minecraft.util.profiling.ProfilerFiller
import net.minecraft.world.entity.Entity
import tech.thatgravyboat.skyblockapi.helpers.McClient
import tech.thatgravyboat.skyblockapi.utils.json.Json.toDataOrThrow

@Module
object CustomEntityDefinitions : SimplePreparableReloadListener<List<CustomEntityDefinition>>() {

private val logger = Catharsis.featureLogger("EntityDefinitions")
private val converter = FileToIdConverter.json("catharsis/entity_definitions")
private val gson = GsonBuilder().create()
private val codec = CatharsisCodecs.getCodec<CustomEntityDefinition>()

private val definitions = mutableListOf<CustomEntityDefinition>()

override fun prepare(
resourceManager: ResourceManager,
profiler: ProfilerFiller,
): List<CustomEntityDefinition> {
return converter.listMatchingResources(resourceManager)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be the same as gui definitions with the logger and the extensions

.mapNotNull { (id, resource) ->
logger.runCatching("Error loading entity definition $id") {
resource.openAsReader().use { bufferedReader ->
gson.fromJson(bufferedReader, JsonElement::class.java).toDataOrThrow(codec)
}
}
}
}

override fun apply(
definitions: List<CustomEntityDefinition>,
resourceManager: ResourceManager,
profiler: ProfilerFiller,
) {
this.definitions.clear()
this.definitions.addAll(definitions)
}

@JvmStatic
fun getFor(entity: Entity): CustomEntityDefinition? {
for (definition in definitions) {
if (definition.matches(entity)) return definition
}

return null
}

init {
McClient.registerClientReloadListener(Catharsis.id("custom_entity_definitions"), this)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package me.owdding.catharsis.features.entity.conditions

import me.owdding.catharsis.Catharsis
import me.owdding.ktcodecs.Compact
import me.owdding.ktcodecs.FieldNames
import me.owdding.ktcodecs.GenerateCodec
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.resources.Identifier
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.LivingEntity
import tech.thatgravyboat.skyblockapi.utils.extentions.serverValue
import kotlin.jvm.optionals.getOrNull

@GenerateCodec
data class AttributeEntityCondition(
val attribute: Identifier,
@FieldNames("value", "values") @Compact val values: List<Float>,
) : EntityCondition {
private val actualAttribute = BuiltInRegistries.ATTRIBUTE.get(attribute).getOrNull()

init {
if (actualAttribute == null) Catharsis.error("Unknown attribute $attribute")
}

override fun matches(entity: Entity): Boolean {
if (entity !is LivingEntity) return false

val attributeInstance = entity.getAttribute(actualAttribute ?: return false) ?: return false

val attributeValue = attributeInstance.serverValue

return values.any { it == attributeValue }
}
}
Loading