Added a hug ability

This commit is contained in:
Sollace 2023-10-16 15:18:36 +01:00
parent 3e9a0df7a1
commit a9923c71f4
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
15 changed files with 847 additions and 20 deletions

View file

@ -36,6 +36,7 @@ public interface Abilities {
Ability<?> KICK = register(new EarthPonyKickAbility(), "kick", AbilitySlot.PRIMARY);
Ability<?> GROW = register(new EarthPonyGrowAbility(), "grow", AbilitySlot.SECONDARY);
Ability<?> STOMP = register(new EarthPonyStompAbility(), "stomp", AbilitySlot.TERTIARY);
Ability<?> HUG = register(new HugAbility(), "hug", AbilitySlot.TERTIARY);
// pegasus
Ability<?> RAINBOOM = register(new PegasusRainboomAbility(), "rainboom", AbilitySlot.PRIMARY);

View file

@ -74,6 +74,18 @@ public class CarryAbility implements Ability<Hit> {
PlayerEntity player = iplayer.asEntity();
LivingEntity rider = findRider(player, iplayer.asWorld());
dropAllPassengers(player);
if (rider != null) {
rider.startRiding(player, true);
Living.getOrEmpty(rider).ifPresent(living -> living.setCarrier(player));
}
Living.transmitPassengers(player);
return true;
}
protected void dropAllPassengers(PlayerEntity player) {
if (player.hasPassengers()) {
List<Entity> passengers = StreamSupport.stream(player.getPassengersDeep().spliterator(), false).toList();
player.removeAllPassengers();
@ -85,14 +97,6 @@ public class CarryAbility implements Ability<Hit> {
}
}
}
if (rider != null) {
rider.startRiding(player, true);
Living.getOrEmpty(rider).ifPresent(living -> living.setCarrier(player));
}
Living.transmitPassengers(player);
return true;
}
@Override

View file

@ -0,0 +1,55 @@
package com.minelittlepony.unicopia.ability;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation;
import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.mob.FriendlyCreeperEntity;
import com.minelittlepony.unicopia.entity.mob.UEntities;
import com.minelittlepony.unicopia.entity.player.Pony;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.mob.CreeperEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.particle.ParticleTypes;
/**
* Ability to hug mobs. Not all of them are receptive to your advances though, so be careful!
*/
public class HugAbility extends CarryAbility {
@Override
public boolean canUse(Race race) {
return race.canUseEarth();
}
@Override
public boolean apply(Pony pony, Hit data) {
PlayerEntity player = pony.asEntity();
LivingEntity rider = findRider(player, pony.asWorld());
dropAllPassengers(player);
pony.setAnimation(Animation.ARMS_FORWARD, Animation.Recipient.ANYONE);
if (rider instanceof CreeperEntity creeper) {
FriendlyCreeperEntity friendlyCreeper = creeper.convertTo(UEntities.FRIENDLY_CREEPER, true);
player.getWorld().spawnEntity(friendlyCreeper);
friendlyCreeper.startRiding(player, true);
Living.getOrEmpty(friendlyCreeper).ifPresent(living -> living.setCarrier(player));
} else if (rider instanceof FriendlyCreeperEntity creeper) {
creeper.startRiding(player, true);
Living.getOrEmpty(creeper).ifPresent(living -> living.setCarrier(player));
} else if (rider != null) {
rider.teleport(player.getX(), player.getY() + 0.5, player.getZ());
rider.setYaw(player.getYaw() + 180);
if (rider instanceof FriendlyCreeperEntity) {
pony.spawnParticles(ParticleTypes.HEART, 10);
}
}
Living.transmitPassengers(player);
return true;
}
}

View file

@ -10,20 +10,17 @@ import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation;
import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation.Recipient;
import com.minelittlepony.unicopia.entity.player.Pony;
import net.minecraft.block.BlockState;
import com.minelittlepony.unicopia.util.ExplosionUtil;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.damage.DamageTypes;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.predicate.entity.EntityPredicates;
import net.minecraft.sound.SoundEvents;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.BlockView;
import net.minecraft.world.World.ExplosionSourceType;
import net.minecraft.world.explosion.Explosion;
import net.minecraft.world.explosion.ExplosionBehavior;
/**
* Kirin ability to transform into a nirik
@ -63,12 +60,7 @@ public class NirikBlastAbility implements Ability<Hit> {
@Override
public boolean apply(Pony player, Hit data) {
player.asWorld().createExplosion(player.asEntity(), player.damageOf(DamageTypes.FIREBALL), new ExplosionBehavior(){
@Override
public boolean canDestroyBlock(Explosion explosion, BlockView world, BlockPos pos, BlockState state, float power) {
return false;
}
}, player.getOriginVector(), 5, true, ExplosionSourceType.MOB);
player.asWorld().createExplosion(player.asEntity(), player.damageOf(DamageTypes.FIREBALL), ExplosionUtil.NON_DESTRUCTIVE, player.getOriginVector(), 5, true, ExplosionSourceType.MOB);
player.setInvulnerabilityTicks(5);
player.setAnimation(Animation.ARMS_UP, Recipient.ANYONE, 12);

View file

@ -84,6 +84,7 @@ public interface URenderers {
EntityRendererRegistry.register(UEntities.CRYSTAL_SHARDS, CrystalShardsEntityRenderer::new);
EntityRendererRegistry.register(UEntities.STORM_CLOUD, StormCloudEntityRenderer::new);
EntityRendererRegistry.register(UEntities.AIR_BALLOON, AirBalloonEntityRenderer::new);
EntityRendererRegistry.register(UEntities.FRIENDLY_CREEPER, FriendlyCreeperEntityRenderer::new);
BlockEntityRendererFactories.register(UBlockEntities.WEATHER_VANE, WeatherVaneBlockEntityRenderer::new);

View file

@ -0,0 +1,131 @@
package com.minelittlepony.unicopia.client.render.entity;
import net.minecraft.client.render.entity.feature.EnergySwirlOverlayFeatureRenderer;
import net.minecraft.client.render.entity.feature.FeatureRendererContext;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.entity.mob.FriendlyCreeperEntity;
import net.minecraft.client.model.ModelPart;
import net.minecraft.client.render.entity.EntityRendererFactory;
import net.minecraft.client.render.entity.MobEntityRenderer;
import net.minecraft.client.render.entity.model.CreeperEntityModel;
import net.minecraft.client.render.entity.model.EntityModel;
import net.minecraft.client.render.entity.model.EntityModelLayers;
import net.minecraft.client.render.entity.model.EntityModelLoader;
import net.minecraft.client.render.entity.model.EntityModelPartNames;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.MathHelper;
public class FriendlyCreeperEntityRenderer extends MobEntityRenderer<FriendlyCreeperEntity, FriendlyCreeperEntityRenderer.Model> {
private static final Identifier FRIENDLY_TEXTURE = Unicopia.id("textures/entity/creeper/friendly.png");
private static final Identifier UNFIRENDLY_TEXTURE = new Identifier("textures/entity/creeper/creeper.png");
public FriendlyCreeperEntityRenderer(EntityRendererFactory.Context context) {
super(context, new Model(context.getPart(EntityModelLayers.CREEPER)), 0.5f);
addFeature(new ChargeFeature(this, context.getModelLoader()));
}
@Override
protected void scale(FriendlyCreeperEntity creeperEntity, MatrixStack matrixStack, float f) {
float g = creeperEntity.getClientFuseTime(f);
float h = 1.0f + MathHelper.sin(g * 100.0f) * g * 0.01f;
g = MathHelper.clamp(g, 0.0f, 1.0f);
g *= g;
g *= g;
float i = (1.0f + g * 0.4f) * h;
float j = (1.0f + g * 0.1f) / h;
matrixStack.scale(i, j, i);
}
@Override
protected void setupTransforms(FriendlyCreeperEntity entity, MatrixStack matrices, float animationProgress, float bodyYaw, float tickDelta) {
super.setupTransforms(entity, matrices, animationProgress, bodyYaw, tickDelta);
if (entity.isSitting()) {
matrices.translate(0, -0.25, 0);
}
}
@Override
protected boolean isShaking(FriendlyCreeperEntity entity) {
return super.isShaking(entity) || entity.isConverting();
}
@Override
protected float getAnimationCounter(FriendlyCreeperEntity entity, float f) {
float fuseTime = entity.getClientFuseTime(f);
if ((int)(fuseTime * 10) % 2 == 0) {
return 0;
}
return MathHelper.clamp(fuseTime, 0.5f, 1.0f);
}
@Override
public Identifier getTexture(FriendlyCreeperEntity entity) {
return entity.isConverting() ? UNFIRENDLY_TEXTURE : FRIENDLY_TEXTURE;
}
public static class Model extends CreeperEntityModel<FriendlyCreeperEntity> {
private final ModelPart leftHindLeg;
private final ModelPart rightHindLeg;
private final ModelPart leftFrontLeg;
private final ModelPart rightFrontLeg;
public Model(ModelPart root) {
super(root);
this.rightHindLeg = root.getChild(EntityModelPartNames.RIGHT_HIND_LEG);
this.leftHindLeg = root.getChild(EntityModelPartNames.LEFT_HIND_LEG);
this.rightFrontLeg = root.getChild(EntityModelPartNames.RIGHT_FRONT_LEG);
this.leftFrontLeg = root.getChild(EntityModelPartNames.LEFT_FRONT_LEG);
}
@Override
public void setAngles(FriendlyCreeperEntity entity, float limbAngle, float limbDistance, float animationProgress, float headYaw, float headPitch) {
leftHindLeg.resetTransform();
rightHindLeg.resetTransform();
leftFrontLeg.resetTransform();
rightFrontLeg.resetTransform();
super.setAngles(entity, limbAngle, limbDistance, animationProgress, headYaw, headPitch);
if (entity.isSitting()) {
float legSpread = 0.001F;
leftHindLeg.pivotZ -= 3;
leftHindLeg.pitch = MathHelper.HALF_PI;
leftHindLeg.yaw = legSpread;
rightHindLeg.pivotZ -= 3;
rightHindLeg.pitch = MathHelper.HALF_PI;
rightHindLeg.yaw = -legSpread;
leftFrontLeg.pivotZ += 3;
leftFrontLeg.pitch = -MathHelper.HALF_PI;
leftFrontLeg.yaw = -legSpread;
rightFrontLeg.pivotZ += 3;
rightFrontLeg.pitch = -MathHelper.HALF_PI;
rightFrontLeg.yaw = legSpread;
}
}
}
public static class ChargeFeature extends EnergySwirlOverlayFeatureRenderer<FriendlyCreeperEntity, Model> {
private static final Identifier SKIN = new Identifier("textures/entity/creeper/creeper_armor.png");
private final CreeperEntityModel<FriendlyCreeperEntity> model;
public ChargeFeature(FeatureRendererContext<FriendlyCreeperEntity, Model> context, EntityModelLoader loader) {
super(context);
model = new Model(loader.getModelPart(EntityModelLayers.CREEPER_ARMOR));
}
@Override
protected float getEnergySwirlX(float partialAge) {
return partialAge * 0.01f;
}
@Override
protected Identifier getEnergySwirlTexture() {
return SKIN;
}
@Override
protected EntityModel<FriendlyCreeperEntity> getEnergySwirlModel() {
return this.model;
}
}
}

View file

@ -16,6 +16,7 @@ import com.minelittlepony.unicopia.ability.magic.spell.effect.TargetSelecter;
import com.minelittlepony.unicopia.entity.ai.BreakHeartGoal;
import com.minelittlepony.unicopia.entity.ai.DynamicTargetGoal;
import com.minelittlepony.unicopia.entity.ai.EatMuffinGoal;
import com.minelittlepony.unicopia.entity.ai.FleeExplosionGoal;
import com.minelittlepony.unicopia.entity.ai.WantItTakeItGoal;
import com.minelittlepony.unicopia.entity.mob.UEntityAttributes;
@ -152,6 +153,9 @@ public class Creature extends Living<LivingEntity> implements WeaklyOwned.Mutabl
eatMuffinGoal = new EatMuffinGoal(pig, targetter);
goals.add(3, eatMuffinGoal);
}
if (entity instanceof TameableEntity tameable) {
goals.add(3, new FleeExplosionGoal(tameable, 6, 1, 1.2));
}
initMinionAi(targets);
initDiscordedAi();

View file

@ -334,7 +334,7 @@ public abstract class Living<T extends LivingEntity> implements Equine<T>, Caste
if (isBeingCarried()) {
Pony carrier = Pony.of(entity.getVehicle()).orElse(null);
if (!Abilities.CARRY.canUse(carrier.getCompositeRace())) {
if (!Abilities.CARRY.canUse(carrier.getCompositeRace()) && !Abilities.HUG.canUse(carrier.getCompositeRace())) {
entity.stopRiding();
entity.refreshPositionAfterTeleport(carrier.getOriginVector());
Living.transmitPassengers(carrier.asEntity());

View file

@ -0,0 +1,106 @@
package com.minelittlepony.unicopia.entity.ai;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.entity.Creature;
import com.minelittlepony.unicopia.entity.Living;
import net.minecraft.entity.Entity;
import net.minecraft.entity.TntEntity;
import net.minecraft.entity.ai.NoPenaltyTargeting;
import net.minecraft.entity.ai.goal.Goal;
import net.minecraft.entity.ai.goal.PrioritizedGoal;
import net.minecraft.entity.ai.pathing.Path;
import net.minecraft.entity.mob.PathAwareEntity;
import net.minecraft.entity.vehicle.TntMinecartEntity;
import net.minecraft.util.math.Vec3d;
public class FleeExplosionGoal extends Goal {
private static final Predicate<Entity> SOURCE_PREDICATE = e -> e instanceof TntMinecartEntity || e instanceof TntEntity;
private final PathAwareEntity mob;
private final double slowSpeed;
private final double fastSpeed;
private final Comparator<Entity> sorting;
@Nullable
private Entity targetEntity;
@Nullable
private Path fleePath;
public FleeExplosionGoal(PathAwareEntity mob, float distance, double slowSpeed, double fastSpeed) {
this.setControls(EnumSet.of(Goal.Control.MOVE));
this.mob = mob;
this.slowSpeed = slowSpeed;
this.fastSpeed = fastSpeed;
this.sorting = Comparator.comparingDouble(e -> e.squaredDistanceTo(mob));
}
public void setFleeTarget(@Nullable Entity target) {
this.targetEntity = target;
}
@Override
public boolean canStart() {
if (targetEntity == null || targetEntity.isRemoved()) {
targetEntity = mob.getWorld().getOtherEntities(mob, mob.getBoundingBox().expand(5, 3, 5), SOURCE_PREDICATE).stream().sorted(sorting).findFirst().orElse(null);
}
if (targetEntity == null) {
return false;
}
Vec3d targetPosition = NoPenaltyTargeting.findFrom(mob, 16, 7, targetEntity.getPos());
if (targetPosition == null
|| targetEntity.squaredDistanceTo(targetPosition.x, targetPosition.y, targetPosition.z) < targetEntity.squaredDistanceTo(mob)) {
return false;
}
fleePath = mob.getNavigation().findPathTo(targetPosition.x, targetPosition.y, targetPosition.z, 0);
return fleePath != null;
}
@Override
public boolean shouldContinue() {
return !mob.getNavigation().isIdle();
}
@Override
public void start() {
mob.getNavigation().startMovingAlong(fleePath, slowSpeed);
}
@Override
public void stop() {
targetEntity = null;
}
@Override
public void tick() {
if (mob.squaredDistanceTo(targetEntity) < 49.0) {
mob.getNavigation().setSpeed(fastSpeed);
} else {
mob.getNavigation().setSpeed(slowSpeed);
}
}
public static void notifySurroundings(Entity explosionSource, float radius) {
explosionSource.getWorld().getOtherEntities(explosionSource, explosionSource.getBoundingBox().expand(radius), e -> {
return Living.getOrEmpty(e).filter(l -> l instanceof Creature c).isPresent();
}).forEach(e -> {
getGoals((Creature)Living.living(e)).forEach(goal -> goal.setFleeTarget(explosionSource));
});
}
private static Stream<FleeExplosionGoal> getGoals(Creature creature) {
return creature.getGoals().stream()
.flatMap(goals -> goals.getGoals().stream())
.map(PrioritizedGoal::getGoal)
.filter(g -> g instanceof FleeExplosionGoal)
.map(FleeExplosionGoal.class::cast);
}
}

View file

@ -0,0 +1,511 @@
package com.minelittlepony.unicopia.entity.mob;
import java.util.Collection;
import java.util.EnumSet;
import java.util.UUID;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.entity.Creature;
import com.minelittlepony.unicopia.entity.Equine;
import com.minelittlepony.unicopia.entity.ai.FleeExplosionGoal;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.entity.feature.SkinOverlayOwner;
import net.minecraft.entity.AreaEffectCloudEntity;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityStatuses;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LightningEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.ai.goal.ActiveTargetGoal;
import net.minecraft.entity.ai.goal.AttackWithOwnerGoal;
import net.minecraft.entity.ai.goal.EscapeDangerGoal;
import net.minecraft.entity.ai.goal.FleeEntityGoal;
import net.minecraft.entity.ai.goal.Goal;
import net.minecraft.entity.ai.goal.LookAroundGoal;
import net.minecraft.entity.ai.goal.LookAtEntityGoal;
import net.minecraft.entity.ai.goal.MeleeAttackGoal;
import net.minecraft.entity.ai.goal.RevengeGoal;
import net.minecraft.entity.ai.goal.SitGoal;
import net.minecraft.entity.ai.goal.SwimGoal;
import net.minecraft.entity.ai.goal.TrackOwnerAttackerGoal;
import net.minecraft.entity.ai.goal.UniversalAngerGoal;
import net.minecraft.entity.ai.goal.WanderAroundFarGoal;
import net.minecraft.entity.ai.pathing.PathNodeType;
import net.minecraft.entity.attribute.DefaultAttributeContainer;
import net.minecraft.entity.attribute.EntityAttributes;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.data.DataTracker;
import net.minecraft.entity.data.TrackedData;
import net.minecraft.entity.data.TrackedDataHandlerRegistry;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.entity.mob.AbstractSkeletonEntity;
import net.minecraft.entity.mob.Angerable;
import net.minecraft.entity.mob.CreeperEntity;
import net.minecraft.entity.mob.HostileEntity;
import net.minecraft.entity.passive.CatEntity;
import net.minecraft.entity.passive.GoatEntity;
import net.minecraft.entity.passive.OcelotEntity;
import net.minecraft.entity.passive.PassiveEntity;
import net.minecraft.entity.passive.TameableEntity;
import net.minecraft.entity.passive.WolfEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.registry.tag.ItemTags;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundEvent;
import net.minecraft.sound.SoundEvents;
import net.minecraft.text.Text;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.TimeHelper;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.intprovider.UniformIntProvider;
import net.minecraft.world.BlockView;
import net.minecraft.world.EntityView;
import net.minecraft.world.World;
import net.minecraft.world.event.GameEvent;
import net.minecraft.world.explosion.Explosion;
public class FriendlyCreeperEntity extends TameableEntity implements SkinOverlayOwner, Angerable {
private static final TrackedData<Integer> FUSE_SPEED = DataTracker.registerData(FriendlyCreeperEntity.class, TrackedDataHandlerRegistry.INTEGER);
private static final TrackedData<Boolean> CHARGED = DataTracker.registerData(FriendlyCreeperEntity.class, TrackedDataHandlerRegistry.BOOLEAN);
private static final TrackedData<Boolean> IGNITED = DataTracker.registerData(FriendlyCreeperEntity.class, TrackedDataHandlerRegistry.BOOLEAN);
private static final TrackedData<Integer> ANGER_TIME = DataTracker.registerData(FriendlyCreeperEntity.class, TrackedDataHandlerRegistry.INTEGER);
private static final UniformIntProvider ANGER_TIME_RANGE = TimeHelper.betweenSeconds(20, 39);
private int lastFuseTime;
private int currentFuseTime;
private short fuseTime = 30;
private byte explosionRadius = 3;
private int headsDropped;
private short hugTime;
private short lastHugTime;
protected FriendlyCreeperEntity(EntityType<? extends FriendlyCreeperEntity> type, World world) {
super(type, world);
setTamed(false);
setPathfindingPenalty(PathNodeType.POWDER_SNOW, -1);
setPathfindingPenalty(PathNodeType.DANGER_POWDER_SNOW, -1);
}
@Override
protected void initDataTracker() {
super.initDataTracker();
dataTracker.startTracking(FUSE_SPEED, -1);
dataTracker.startTracking(CHARGED, false);
dataTracker.startTracking(IGNITED, false);
dataTracker.startTracking(ANGER_TIME, 0);
}
@Override
protected void initGoals() {
goalSelector.add(1, new SwimGoal(this));
goalSelector.add(1, new EscapeGoal(1.5));
goalSelector.add(2, new SitGoal(this));
goalSelector.add(3, new IgniteGoal());
goalSelector.add(4, new FleeEntityGoal<>(this, OcelotEntity.class, 6, 1, 1.2));
goalSelector.add(4, new FleeEntityGoal<>(this, CatEntity.class, 6, 1, 1.2));
goalSelector.add(5, new MeleeAttackGoal(this, 1, false));
goalSelector.add(6, new WanderAroundFarGoal(this, 0.8));
goalSelector.add(7, new LookAtEntityGoal(this, PlayerEntity.class, 8));
goalSelector.add(7, new LookAroundGoal(this));
targetSelector.add(1, new TrackOwnerAttackerGoal(this));
targetSelector.add(2, new AttackWithOwnerGoal(this));
targetSelector.add(3, new RevengeGoal(this).setGroupRevenge(WolfEntity.class));
targetSelector.add(7, new ActiveTargetGoal<>(this, AbstractSkeletonEntity.class, false));
targetSelector.add(8, new UniversalAngerGoal<>(this, true));
}
public static DefaultAttributeContainer.Builder createCreeperAttributes() {
return HostileEntity.createHostileAttributes().add(EntityAttributes.GENERIC_MOVEMENT_SPEED, 0.25);
}
@Override
protected Text getDefaultName() {
return EntityType.CREEPER.getName();
}
@Override
public int getSafeFallDistance() {
return 3 + (getTarget() == null ? 0 : ((int)(getHealth() - 1)));
}
@Override
public boolean handleFallDamage(float fallDistance, float damageMultiplier, DamageSource damageSource) {
boolean bl = super.handleFallDamage(fallDistance, damageMultiplier, damageSource);
currentFuseTime += (int)(fallDistance * 1.5f);
if (currentFuseTime > fuseTime - 5) {
currentFuseTime = fuseTime - 5;
}
return bl;
}
@Override
public void writeCustomDataToNbt(NbtCompound nbt) {
super.writeCustomDataToNbt(nbt);
if (dataTracker.get(CHARGED).booleanValue()) {
nbt.putBoolean("powered", true);
}
nbt.putShort("Fuse", fuseTime);
nbt.putShort("Hugged", hugTime);
nbt.putByte("ExplosionRadius", explosionRadius);
nbt.putBoolean("ignited", isIgnited());
}
@Override
public void readCustomDataFromNbt(NbtCompound nbt) {
super.readCustomDataFromNbt(nbt);
dataTracker.set(CHARGED, nbt.getBoolean("powered"));
if (nbt.contains("Fuse", NbtElement.NUMBER_TYPE)) {
fuseTime = nbt.getShort("Fuse");
}
if (nbt.contains("Hugged", NbtElement.NUMBER_TYPE)) {
hugTime = nbt.getShort("Hugged");
}
if (nbt.contains("ExplosionRadius", NbtElement.NUMBER_TYPE)) {
explosionRadius = nbt.getByte("ExplosionRadius");
}
if (nbt.getBoolean("ignited")) {
ignite();
}
}
@Override
public void tick() {
setSitting(isInSittingPose());
if (isAlive()) {
lastFuseTime = currentFuseTime;
if (isIgnited()) {
setFuseSpeed(1);
}
int fuseSpeed = getFuseSpeed();
if (fuseSpeed > 0 && currentFuseTime == 0) {
playSound(SoundEvents.ENTITY_CREEPER_PRIMED, 1.0f, 0.5f);
emitGameEvent(GameEvent.PRIME_FUSE);
}
currentFuseTime = Math.max(0, currentFuseTime + fuseSpeed);
if (currentFuseTime >= fuseTime) {
currentFuseTime = fuseTime;
explode();
}
lastHugTime = hugTime;
if (!isTamed()) {
if (isConverting()) {
if (++hugTime >= 100) {
if (!getWorld().isClient) {
setOwner(getCreature().getCarrierId().map(getWorld()::getPlayerByUuid).orElse(null));
getWorld().sendEntityStatus(this, EntityStatuses.ADD_POSITIVE_PLAYER_REACTION_PARTICLES);
}
}
if (hugTime % 5 == 0) {
playHurtSound(getDamageSources().generic());
}
} else {
hugTime = 0;
if (!getWorld().isClient) {
getWorld().spawnEntity(convertTo(EntityType.CREEPER, true));
discard();
}
}
} else {
if (random.nextInt(30) == 0) {
spawnHeart();
}
hugTime = 0;
}
}
super.tick();
}
@Override
public void tickMovement() {
super.tickMovement();
if (!getWorld().isClient) {
tickAngerLogic((ServerWorld)getWorld(), true);
}
}
private void spawnHeart() {
getWorld().addParticle(ParticleTypes.HEART, random.nextTriangular(getX(), 0.5), getY() + getHeight(), random.nextTriangular(getZ(), 0.5), 0, 0, 0);
}
private Creature getCreature() {
return Equine.<FriendlyCreeperEntity, Creature>of(this, c -> c instanceof Creature).get();
}
public boolean isConverting() {
return !isTamed() && getCreature().isBeingCarried();
}
@Override
public void setTarget(@Nullable LivingEntity target) {
if (target instanceof GoatEntity) {
return;
}
super.setTarget(target);
}
@Override
protected SoundEvent getHurtSound(DamageSource source) {
return SoundEvents.ENTITY_CREEPER_HURT;
}
@Override
protected SoundEvent getDeathSound() {
return SoundEvents.ENTITY_CREEPER_DEATH;
}
@Override
protected void dropEquipment(DamageSource source, int lootingMultiplier, boolean allowDrops) {
super.dropEquipment(source, lootingMultiplier, allowDrops);
if (source.getAttacker() instanceof CreeperEntity c && c.shouldDropHead()) {
c.onHeadDropped();
dropItem(Items.CREEPER_HEAD);
}
}
@Override
public boolean tryAttack(Entity target) {
return true;
}
@Override
public boolean shouldRenderOverlay() {
return dataTracker.get(CHARGED);
}
public float getClientFuseTime(float timeDelta) {
return MathHelper.lerp(timeDelta, (float)lastFuseTime, (float)currentFuseTime) / (fuseTime - 2)
+ MathHelper.lerp(timeDelta, (float)lastHugTime, (float)hugTime) / 98F;
}
public int getFuseSpeed() {
return dataTracker.get(FUSE_SPEED);
}
public void setFuseSpeed(int fuseSpeed) {
dataTracker.set(FUSE_SPEED, fuseSpeed);
}
@Override
public void onStruckByLightning(ServerWorld world, LightningEntity lightning) {
super.onStruckByLightning(world, lightning);
dataTracker.set(CHARGED, true);
}
@Override
public ActionResult interactMob(PlayerEntity player, Hand hand) {
ItemStack stack = player.getStackInHand(hand);
Consumer<PlayerEntity> statusCallback = p -> p.sendToolBreakStatus(hand);
if (stack.isEmpty() && isOwner(player)) {
setSitting(!isSitting());
setInSittingPose(isSitting());
return ActionResult.success(getWorld().isClient);
}
if (stack.isIn(ItemTags.CREEPER_IGNITERS)) {
SoundEvent soundEvent = stack.isOf(Items.FIRE_CHARGE) ? SoundEvents.ITEM_FIRECHARGE_USE : SoundEvents.ITEM_FLINTANDSTEEL_USE;
getWorld().playSound(player, getX(), getY(), getZ(), soundEvent, getSoundCategory(), 1, random.nextFloat() * 0.4f + 0.8f);
if (!getWorld().isClient) {
ignite();
if (!stack.isDamageable()) {
stack.decrement(1);
} else {
stack.damage(1, player, statusCallback);
}
}
return ActionResult.success(getWorld().isClient);
}
if (stack.isOf(Items.GUNPOWDER) && getHealth() < getMaxHealth()) {
getWorld().playSound(player, getX(), getY(), getZ(), SoundEvents.ENTITY_CAT_EAT, getSoundCategory(), 1, random.nextFloat() * 0.4f + 0.8f);
currentFuseTime = fuseTime - 1;
if (!getWorld().isClient) {
heal(3);
getWorld().sendEntityStatus(this, EntityStatuses.ADD_POSITIVE_PLAYER_REACTION_PARTICLES);
if (!stack.isDamageable()) {
stack.decrement(1);
} else {
stack.damage(1, player, statusCallback);
}
}
return ActionResult.success(getWorld().isClient);
}
return super.interactMob(player, hand);
}
private void explode() {
if (!getWorld().isClient) {
dead = true;
getWorld().createExplosion(this, getX(), getY(), getZ(), getExplosionRadius(), World.ExplosionSourceType.MOB);
discard();
spawnEffectsCloud();
}
}
@Override
public boolean canExplosionDestroyBlock(Explosion explosion, BlockView world, BlockPos pos, BlockState state, float explosionPower) {
return false;
}
private float getExplosionRadius() {
return explosionRadius * (shouldRenderOverlay() ? 2 : 1);
}
private void spawnEffectsCloud() {
Collection<StatusEffectInstance> effects = getStatusEffects();
if (!effects.isEmpty()) {
AreaEffectCloudEntity cloud = new AreaEffectCloudEntity(getWorld(), getX(), getY(), getZ());
cloud.setRadius(2.5f);
cloud.setRadiusOnUse(-0.5f);
cloud.setWaitTime(10);
cloud.setDuration(cloud.getDuration() / 2);
cloud.setRadiusGrowth(-cloud.getRadius() / cloud.getDuration());
effects.forEach(effect -> cloud.addEffect(new StatusEffectInstance(effect)));
getWorld().spawnEntity(cloud);
}
}
public boolean isIgnited() {
return dataTracker.get(IGNITED);
}
public void ignite() {
dataTracker.set(IGNITED, true);
FleeExplosionGoal.notifySurroundings(this, getExplosionRadius());
}
public boolean shouldDropHead() {
return shouldRenderOverlay() && headsDropped < 1;
}
public void onHeadDropped() {
headsDropped++;
}
@Override
public EntityView method_48926() {
return getWorld();
}
@Override
public PassiveEntity createChild(ServerWorld world, PassiveEntity partner) {
FriendlyCreeperEntity child = (FriendlyCreeperEntity)getType().create(world);
UUID uUID = getOwnerUuid();
if (uUID != null) {
child.setOwnerUuid(uUID);
child.setTamed(true);
}
return child;
}
@Nullable
private UUID angerTarget;
@Override
public int getAngerTime() {
return dataTracker.get(ANGER_TIME);
}
@Override
public void setAngerTime(int time) {
dataTracker.set(ANGER_TIME, time);
}
@Nullable
@Override
public UUID getAngryAt() {
return angerTarget;
}
@Override
public void setAngryAt(UUID target) {
angerTarget = target;
}
@Override
public void chooseRandomAngerTime() {
setAngerTime(ANGER_TIME_RANGE.get(random));
}
class IgniteGoal extends Goal {
@Nullable
private LivingEntity target;
public IgniteGoal() {
this.setControls(EnumSet.of(Goal.Control.MOVE));
}
@Override
public boolean canStart() {
LivingEntity livingEntity = getTarget();
return getFuseSpeed() > 0 || livingEntity != null && squaredDistanceTo(livingEntity) < 9.0;
}
@Override
public void start() {
getNavigation().stop();
target = getTarget();
}
@Override
public void stop() {
target = null;
}
@Override
public boolean shouldRunEveryTick() {
return true;
}
@Override
public void tick() {
if (target == null) {
setFuseSpeed(-1);
return;
}
if (squaredDistanceTo(target) > 49.0) {
setFuseSpeed(-1);
return;
}
if (!getVisibilityCache().canSee(target)) {
setFuseSpeed(-1);
return;
}
setFuseSpeed(1);
}
}
class EscapeGoal extends EscapeDangerGoal {
public EscapeGoal(double speed) {
super(FriendlyCreeperEntity.this, speed);
}
@Override
protected boolean isInDanger() {
return mob.shouldEscapePowderSnow() || mob.isOnFire();
}
}
}

View file

@ -45,6 +45,10 @@ public interface UEntities {
EntityType<FairyEntity> TWITTERMITE = register("twittermite", FabricEntityTypeBuilder.create(SpawnGroup.MISC, FairyEntity::new)
.trackRangeBlocks(200)
.dimensions(EntityDimensions.fixed(0.1F, 0.1F)));
EntityType<FriendlyCreeperEntity> FRIENDLY_CREEPER = register("friendly_creeper", FabricEntityTypeBuilder.create(SpawnGroup.MISC, FriendlyCreeperEntity::new)
.trackRangeBlocks(8)
.dimensions(EntityDimensions.fixed(0.6f, 1.7f))
);
EntityType<SpellbookEntity> SPELLBOOK = register("spellbook", FabricEntityTypeBuilder.create(SpawnGroup.MISC, SpellbookEntity::new)
.trackRangeBlocks(200)
.dimensions(EntityDimensions.fixed(0.9F, 0.5F)));
@ -72,6 +76,7 @@ public interface UEntities {
FabricDefaultAttributeRegistry.register(TWITTERMITE, FairyEntity.createMobAttributes());
FabricDefaultAttributeRegistry.register(AIR_BALLOON, FlyingEntity.createMobAttributes());
FabricDefaultAttributeRegistry.register(SOMBRA, SombraEntity.createMobAttributes());
FabricDefaultAttributeRegistry.register(FRIENDLY_CREEPER, FriendlyCreeperEntity.createCreeperAttributes());
if (!Unicopia.getConfig().disableButterflySpawning.get()) {
final Predicate<BiomeSelectionContext> butterflySpawnable = BiomeSelectors.foundInOverworld()

View file

@ -0,0 +1,16 @@
package com.minelittlepony.unicopia.util;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockView;
import net.minecraft.world.explosion.Explosion;
import net.minecraft.world.explosion.ExplosionBehavior;
public interface ExplosionUtil {
ExplosionBehavior NON_DESTRUCTIVE = new ExplosionBehavior(){
@Override
public boolean canDestroyBlock(Explosion explosion, BlockView world, BlockPos pos, BlockState state, float power) {
return false;
}
};
}

View file

@ -406,6 +406,7 @@
"ability.unicopia.grow": "Nourish Earth",
"ability.unicopia.stomp": "Ground Pound",
"ability.unicopia.kick": "Crushing Blow",
"ability.unicopia.hug": "Hug",
"ability.unicopia.pummel": "Devestating Smash",
"ability.unicopia.carry": "Pickup/Drop Passenger",
"ability.unicopia.toggle_flight": "Take-off/Land",

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB