Skip to content

Commit

Permalink
Implement reflecting modifier for shields
Browse files Browse the repository at this point in the history
Grants a 2 second window after starting to block in which any projectile is redirected in the direction you look
Recipe is the same as bouncy, as its basic
  • Loading branch information
KnightMiner committed Feb 11, 2023
1 parent 458ed8f commit 00d73db
Show file tree
Hide file tree
Showing 30 changed files with 257 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"replace": false,
"values": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"replace": false,
"values": [
"minecraft:fishing_bobber"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"replace": false,
"values": [
"#tconstruct:modifiable/held"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,20 @@
]
}
},
"reflecting": {
"trigger": "minecraft:inventory_changed",
"conditions": {
"items": [
{
"type": "tconstruct:tool",
"modifiers": {
"name": "tconstruct:reflecting",
"level": 1
}
}
]
}
},
"autosmelt": {
"trigger": "minecraft:inventory_changed",
"conditions": {
Expand Down Expand Up @@ -579,6 +593,9 @@
[
"long_fall"
],
[
"reflecting"
],
[
"autosmelt"
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"type": "tconstruct:modifier",
"inputs": [
{
"item": "tconstruct:sky_congealed_slime",
"amount_needed": 4
},
{
"item": "tconstruct:ichor_congealed_slime",
"amount_needed": 4
},
{
"item": "tconstruct:sky_congealed_slime",
"amount_needed": 4
},
{
"item": "tconstruct:earth_congealed_slime",
"amount_needed": 4
},
{
"item": "tconstruct:earth_congealed_slime",
"amount_needed": 4
}
],
"tools": {
"tag": "tconstruct:modifiable/shields"
},
"slots": {
"abilities": 1
},
"allow_crystal": true,
"result": {
"name": "tconstruct:reflecting",
"level": 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"type": "tconstruct:modifier_salvage",
"tools": {
"tag": "tconstruct:modifiable/shields"
},
"slots": {
"abilities": 1
},
"modifier": "tconstruct:reflecting",
"min_level": 1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"replace": false,
"values": [
{
"id": "parry:rebound",
"required": false
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,6 @@
"#tconstruct:modifier_like/luck": "tconstruct:luck",
"#tconstruct:modifier_like/multishot": "tconstruct:multishot",
"#tconstruct:modifier_like/reach": "tconstruct:reach",
"#tconstruct:modifier_like/tilling": "tconstruct:tilling"
"#tconstruct:modifier_like/tilling": "tconstruct:tilling",
"#tconstruct:modifier_like/reflecting": "tconstruct:reflecting"
}
5 changes: 5 additions & 0 deletions src/main/java/slimeknights/tconstruct/common/TinkerTags.java
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,11 @@ private static void init() {}
/** Mobs that get the 4x protection boost due to only 1 armor piece */
public static final TagKey<EntityType<?>> SMALL_ARMOR = forgeTag("small_armor");

/** Projectiles with this tag cannot be reflected */
public static final TagKey<EntityType<?>> REFLECTING_BLACKLIST = forgeTag("reflecting/blacklist");
/** Projectiles with this tag cannot be reflected */
public static final TagKey<EntityType<?>> REFLECTING_PRESERVE_OWNER = forgeTag("reflecting/preserve_owner");

private static TagKey<EntityType<?>> tag(String name) {
return TagKey.create(Registry.ENTITY_TYPE_REGISTRY, TConstruct.getResource(name));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ protected void generate() {
withL.accept(TinkerModifiers.ambidextrous);
withL.accept(TinkerModifiers.zoom);
withL.accept(TinkerModifiers.longFall);
withL.accept(TinkerModifiers.reflecting);
// harvest
withL.accept(TinkerModifiers.autosmelt);
withL.accept(TinkerModifiers.exchanging);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ protected void addTags() {
modifierTag(TinkerModifiers.multishot.getId(), "cyclic:multishot", "ensorcellation:volley");
modifierTag(ModifierIds.reach, "cyclic:reach", "ensorcellation:reach");
modifierTag(TinkerModifiers.tilling.getId(), "ensorcellation:tilling");
modifierTag(TinkerModifiers.reflecting.getId(), "parry:rebound");
}

/** Creates a builder for a tag for the given modifier */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public void addTags() {
this.tag(TinkerTags.EntityTypes.KILLAGERS).addTags(TinkerTags.EntityTypes.VILLAGERS, TinkerTags.EntityTypes.ILLAGERS).add(EntityType.IRON_GOLEM, EntityType.RAVAGER);

this.tag(TinkerTags.EntityTypes.SMALL_ARMOR).addTag(TinkerTags.EntityTypes.SLIMES);
this.tag(TinkerTags.EntityTypes.REFLECTING_BLACKLIST);
this.tag(TinkerTags.EntityTypes.REFLECTING_PRESERVE_OWNER).add(EntityType.FISHING_BOBBER);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ private void addTools() {
.addTag(MULTIPART_TOOL).addTag(DURABILITY)
.addTag(MELEE_OR_HARVEST).addTag(AOE)
.addTag(HELD);
// disable parry mod on our items, we have our own modifier for that
this.tag(TagKey.create(Registry.ITEM_REGISTRY, new ResourceLocation("parry", "excluded_shields"))).addTag(HELD);

// kamas are a shear type, when broken we don't pass it to loot tables
this.tag(Tags.Items.SHEARS).add(TinkerTools.kama.get());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.EquipmentSlot.Type;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.AttributeModifier.Operation;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
Expand All @@ -25,6 +23,7 @@
import slimeknights.tconstruct.library.modifiers.ModifierEntry;
import slimeknights.tconstruct.library.modifiers.ModifierId;
import slimeknights.tconstruct.library.modifiers.TinkerHooks;
import slimeknights.tconstruct.library.modifiers.hook.ConditionalStatModifierHook;
import slimeknights.tconstruct.library.modifiers.hook.LootingModifierHook;
import slimeknights.tconstruct.library.tools.capability.TinkerDataCapability;
import slimeknights.tconstruct.library.tools.capability.TinkerDataCapability.TinkerDataKey;
Expand All @@ -35,6 +34,7 @@
import slimeknights.tconstruct.library.tools.nbt.IToolStackView;
import slimeknights.tconstruct.library.tools.nbt.ModifierNBT;
import slimeknights.tconstruct.library.tools.nbt.ToolStack;
import slimeknights.tconstruct.library.tools.stat.ToolStats;

import javax.annotation.Nullable;
import java.util.HashMap;
Expand Down Expand Up @@ -374,4 +374,9 @@ public static void finishUsingItem(LivingEntity living, IToolStackView tool) {
public static void finishUsingItem(IToolStackView tool) {
tool.getPersistentData().remove(ACTIVE_MODIFIER);
}

/** Calculates inaccuracy from the conditional tool stat */
public static float getInaccuracy(IToolStackView tool, LivingEntity living, float velocity) {
return 3 * (1 / ConditionalStatModifierHook.getModifiedStat(tool, living, ToolStats.ACCURACY) - 1) * velocity;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
import slimeknights.tconstruct.tools.modifiers.ability.armor.DoubleJumpModifier;
import slimeknights.tconstruct.tools.modifiers.ability.armor.LongFallModifier;
import slimeknights.tconstruct.tools.modifiers.ability.armor.ProtectionModifier;
import slimeknights.tconstruct.tools.modifiers.ability.armor.ReflectingModifier;
import slimeknights.tconstruct.tools.modifiers.ability.armor.ShieldStrapModifier;
import slimeknights.tconstruct.tools.modifiers.ability.armor.SlurpingModifier;
import slimeknights.tconstruct.tools.modifiers.ability.armor.ToolBeltModifier;
Expand Down Expand Up @@ -302,6 +303,7 @@ public TinkerModifiers() {
public static final StaticModifier<PaddedModifier> padded = MODIFIERS.register("padded", PaddedModifier::new);
public static final StaticModifier<FieryModifier> fiery = MODIFIERS.register("fiery", FieryModifier::new);
public static final StaticModifier<SeveringModifier> severing = MODIFIERS.register("severing", SeveringModifier::new);
public static final StaticModifier<ReflectingModifier> reflecting = MODIFIERS.register("reflecting", ReflectingModifier::new);

// damage boost
public static final StaticModifier<PiercingModifier> piercing = MODIFIERS.register("piercing", PiercingModifier::new);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ protected void addEnchantmentMappings() {
addCompat(TinkerModifiers.multishot.getId());
addCompat(ModifierIds.reach);
addCompat(TinkerModifiers.tilling.getId());
addCompat(TinkerModifiers.reflecting.getId());
}

/** Adds a compat enchantment */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1332,6 +1332,16 @@ private void addModifierRecipes(Consumer<FinishedRecipe> consumer) {
.setSlots(SlotType.ABILITY, 1)
.saveSalvage(consumer, prefix(TinkerModifiers.blocking, abilitySalvage))
.save(consumer, prefix(TinkerModifiers.blocking, abilityFolder));
ModifierRecipeBuilder.modifier(TinkerModifiers.reflecting)
.setTools(TinkerTags.Items.SHIELDS)
.addInput(TinkerWorld.congealedSlime.get(SlimeType.SKY), 4)
.addInput(TinkerWorld.congealedSlime.get(SlimeType.ICHOR), 4)
.addInput(TinkerWorld.congealedSlime.get(SlimeType.SKY), 4)
.addInput(TinkerWorld.congealedSlime.get(SlimeType.EARTH), 4)
.addInput(TinkerWorld.congealedSlime.get(SlimeType.EARTH), 4)
.setSlots(SlotType.ABILITY, 1)
.saveSalvage(consumer, prefix(TinkerModifiers.reflecting, abilitySalvage))
.save(consumer, prefix(TinkerModifiers.reflecting, abilityFolder));

/*
* extra modifiers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ public void releaseUsing(ItemStack bow, Level level, LivingEntity living, int ti

// prepare the arrows
ArrowItem arrowItem = ammo.getItem() instanceof ArrowItem arrow ? arrow : (ArrowItem)Items.ARROW;
float inaccuracy = 3 * (1 / ConditionalStatModifierHook.getModifiedStat(tool, living, ToolStats.ACCURACY) - 1) * velocity;
float inaccuracy = ModifierUtil.getInaccuracy(tool, living, velocity);
float startAngle = getAngleStart(ammo.getCount());
int primaryIndex = ammo.getCount() / 2;
for (int arrowIndex = 0; arrowIndex < ammo.getCount(); arrowIndex++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ public static void fireCrossbow(IToolStackView tool, Player player, InteractionH

// don't need to calculate these multiple times
float velocity = ConditionalStatModifierHook.getModifiedStat(tool, player, ToolStats.VELOCITY);
float inaccuracy = 3 * (1 / ConditionalStatModifierHook.getModifiedStat(tool, player, ToolStats.ACCURACY) - 1) * velocity;
float inaccuracy = ModifierUtil.getInaccuracy(tool, player, velocity);
boolean creative = player.getAbilities().instabuild;

// the ammo has a stack size that may be greater than 1 (meaning multishot)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import slimeknights.tconstruct.library.tools.stat.ToolStats;
import slimeknights.tconstruct.library.utils.Util;

import javax.annotation.Nullable;
import java.util.List;
import java.util.function.Function;

Expand Down Expand Up @@ -410,21 +411,19 @@ static void leftClickBlock(LeftClickBlock event) {
}

/** Checks if the shield block angle allows blocking this attack */
private static boolean canBlock(ShieldBlockEvent event, IToolStackView tool) {
public static boolean canBlock(LivingEntity holder, @Nullable Vec3 sourcePosition, IToolStackView tool) {
// source position should never be null (checked by livingentity) but safety as its marked nullable
Vec3 sourcePosition = event.getDamageSource().getSourcePosition();
if (sourcePosition == null) {
return false;
}
// no work to do if 90 degrees, that is vanilla logic
LivingEntity entity = event.getEntityLiving();
float blockAngle = ConditionalStatModifierHook.getModifiedStat(tool, entity, ToolStats.BLOCK_ANGLE);
float blockAngle = ConditionalStatModifierHook.getModifiedStat(tool, holder, ToolStats.BLOCK_ANGLE);
if (blockAngle >= 90) {
return true;
}
// want the angle between the view vector and the
Vec3 viewVector = entity.getViewVector(1.0f);
Vec3 entityPosition = entity.position();
Vec3 viewVector = holder.getViewVector(1.0f);
Vec3 entityPosition = holder.position();
Vec3 direction = new Vec3(entityPosition.x - sourcePosition.x, 0, entityPosition.z - sourcePosition.z);
double length = viewVector.length() * direction.length();
// prevent zero vector from messing with us
Expand All @@ -444,8 +443,8 @@ static void onBlock(ShieldBlockEvent event) {
if (!activeStack.isEmpty() && activeStack.is(TinkerTags.Items.MODIFIABLE)) {
ToolStack tool = ToolStack.from(activeStack);
// first check block angle
if (!tool.isBroken() && canBlock(event, tool)) {
// TOOD: hook for conditioning block amount based on on damage type
if (!tool.isBroken() && canBlock(event.getEntityLiving(), event.getDamageSource().getSourcePosition(), tool)) {
// TODO: hook for conditioning block amount based on on damage type
event.setBlockedDamage(Math.min(event.getBlockedDamage(), tool.getStats().get(ToolStats.BLOCK_AMOUNT)));
// TODO: consider handling the item damage ourself
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package slimeknights.tconstruct.tools.modifiers.ability.armor;

import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.projectile.AbstractHurtingProjectile;
import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.UseAnim;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.HitResult.Type;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.ProjectileImpactEvent;
import slimeknights.mantle.util.RegistryHelper;
import slimeknights.tconstruct.common.TinkerTags;
import slimeknights.tconstruct.library.modifiers.Modifier;
import slimeknights.tconstruct.library.modifiers.ModifierEntry;
import slimeknights.tconstruct.library.modifiers.TinkerHooks;
import slimeknights.tconstruct.library.modifiers.hook.ConditionalStatModifierHook;
import slimeknights.tconstruct.library.modifiers.hook.interaction.GeneralInteractionModifierHook;
import slimeknights.tconstruct.library.tools.helper.ModifierUtil;
import slimeknights.tconstruct.library.tools.nbt.ToolStack;
import slimeknights.tconstruct.library.tools.stat.ToolStats;
import slimeknights.tconstruct.tools.logic.InteractionHandler;

public class ReflectingModifier extends Modifier {
public ReflectingModifier() {
MinecraftForge.EVENT_BUS.addListener(this::projectileImpact);
}

private void projectileImpact(ProjectileImpactEvent event) {
Entity entity = event.getEntity();
// first, need a projectile that is hitting a living entity
if (!entity.level.isClientSide) {
Projectile projectile = event.getProjectile();

// handle blacklist for projectiles
// living entity must be using one of our shields
HitResult hit = event.getRayTraceResult();
if (!RegistryHelper.contains(TinkerTags.EntityTypes.REFLECTING_BLACKLIST, projectile.getType())
&& hit.getType() == Type.ENTITY && ((EntityHitResult) hit).getEntity() instanceof LivingEntity living && living.isUsingItem()) {
ItemStack stack = living.getUseItem();
if (stack.is(TinkerTags.Items.SHIELDS)) {
ToolStack tool = ToolStack.from(stack);

// only support the new hook for blocking, the old hook is a pain
ModifierEntry activeModifier = ModifierUtil.getActiveModifier(tool);
if (activeModifier != null) {
GeneralInteractionModifierHook hook = activeModifier.getHook(TinkerHooks.CHARGEABLE_INTERACT);
int time = hook.getUseDuration(tool, activeModifier) - living.getUseItemRemainingTicks();
// must be blocking, started blocking within the last 2*level seconds, and be within the block angle
if (hook.getUseAction(tool, activeModifier) == UseAnim.BLOCK
&& (time >= 5 && time < 40 * activeModifier.getLevel())
&& InteractionHandler.canBlock(living, projectile.position(), tool)) {

// time to actually reflect, this code is strongly based on code from the Parry mod
// take ownership of the projectile so it counts as a player kill, except in the case of fishing bobbers
if (!RegistryHelper.contains(TinkerTags.EntityTypes.REFLECTING_PRESERVE_OWNER, projectile.getType())) {
projectile.setOwner(living);
projectile.leftOwner = true;
}

Vec3 reboundAngle = living.getLookAngle();
// use the shield accuracy and velocity stats when reflecting
float velocity = ConditionalStatModifierHook.getModifiedStat(tool, living, ToolStats.VELOCITY) * 1.1f;
projectile.shoot(reboundAngle.x, reboundAngle.y, reboundAngle.z, velocity, ModifierUtil.getInaccuracy(tool, living, (float)(velocity * projectile.getDeltaMovement().length())));
if (projectile instanceof AbstractHurtingProjectile hurting) {
hurting.xPower = reboundAngle.x * 0.1;
hurting.yPower = reboundAngle.y * 0.1;
hurting.zPower = reboundAngle.z * 0.1;
}
living.level.playSound(null, living.blockPosition(), SoundEvents.SHIELD_BLOCK, SoundSource.PLAYERS, 1.0F, 1.5F + living.level.random.nextFloat() * 0.4F);
event.setCanceled(true);
}
}
}
}
}
}
}

0 comments on commit 00d73db

Please sign in to comment.