Added mimic, mind swap, and hydrophobe spells

This commit is contained in:
Sollace 2022-10-03 23:44:30 +02:00
parent 69c8ceac45
commit afda7a6ef8
32 changed files with 798 additions and 62 deletions

View file

@ -2,13 +2,12 @@ package com.minelittlepony.unicopia.ability;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.util.TraceHelper;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.packet.s2c.play.EntityPassengersSetS2CPacket;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.world.World;
/**
@ -76,9 +75,7 @@ public class CarryAbility implements Ability<Hit> {
rider.startRiding(player, true);
}
if (player instanceof ServerPlayerEntity) {
((ServerPlayerEntity)player).networkHandler.sendPacket(new EntityPassengersSetS2CPacket(player));
}
Living.transmitPassengers(player);
}
@Override

View file

@ -5,6 +5,7 @@ import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.ability.data.Pos;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.particle.MagicParticleEffect;
import com.minelittlepony.unicopia.util.Trace;
@ -17,9 +18,7 @@ import net.minecraft.block.WallBlock;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.packet.s2c.play.EntityPassengersSetS2CPacket;
import net.minecraft.predicate.entity.EntityPredicates;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.sound.SoundCategory;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
@ -142,10 +141,7 @@ public class UnicornTeleportAbility implements Ability<Pos> {
Entity mount = player.getVehicle();
player.stopRiding();
if (mount instanceof ServerPlayerEntity) {
((ServerPlayerEntity)player).networkHandler.sendPacket(new EntityPassengersSetS2CPacket(player));
}
Living.transmitPassengers(mount);
}
Vec3d offset = teleportee.getOriginVector().subtract(teleporter.getOriginVector());

View file

@ -0,0 +1,44 @@
package com.minelittlepony.unicopia.ability.magic;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquinePredicates;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.util.VecHelper;
import net.minecraft.entity.Entity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.*;
public interface CasterView {
EntityView getWorld();
default <S extends Spell> Stream<Map.Entry<Caster<?>, S>> findAllSpellsInRange(BlockPos pos, double radius, SpellPredicate<S> type) {
return findAllCastersInRange(pos, radius).flatMap(caster -> {
return caster.getSpellSlot().stream(type, false).map(spell -> {
return Map.entry(caster, spell);
});
});
}
default Stream<Caster<?>> findAllCastersInRange(BlockPos pos, double radius) {
return findAllCastersInRange(pos, radius, null);
}
default Stream<Caster<?>> findAllCastersInRange(BlockPos pos, double radius, @Nullable Predicate<Entity> test) {
return Caster.stream(findAllEntitiesInRange(pos, radius, test == null ? EquinePredicates.IS_CASTER : EquinePredicates.IS_CASTER.and(test)));
}
default Stream<Entity> findAllEntitiesInRange(BlockPos pos, double radius, @Nullable Predicate<Entity> test) {
return VecHelper.findInRange(null, getWorld(), Vec3d.ofCenter(pos), radius, test).stream();
}
default Stream<Entity> findAllEntitiesInRange(BlockPos pos, double radius) {
return findAllEntitiesInRange(pos, radius, null);
}
}

View file

@ -3,6 +3,7 @@ package com.minelittlepony.unicopia.ability.magic;
import java.util.function.Predicate;
import com.minelittlepony.unicopia.ability.magic.spell.*;
import com.minelittlepony.unicopia.ability.magic.spell.effect.MimicSpell;
import com.minelittlepony.unicopia.ability.magic.spell.effect.ShieldSpell;
import net.minecraft.entity.Entity;
@ -12,6 +13,7 @@ public interface SpellPredicate<T extends Spell> extends Predicate<Spell> {
SpellPredicate<PlaceableSpell> IS_PLACED = s -> s instanceof PlaceableSpell;
SpellPredicate<ProjectileSpell> HAS_PROJECTILE_EVENTS = s -> s instanceof ProjectileSpell;
SpellPredicate<AbstractDisguiseSpell> IS_DISGUISE = s -> s instanceof AbstractDisguiseSpell;
SpellPredicate<MimicSpell> IS_MIMIC = s -> s instanceof MimicSpell;
SpellPredicate<ShieldSpell> IS_SHIELD_LIKE = spell -> spell instanceof ShieldSpell;
SpellPredicate<TimedSpell> IS_TIMED = spell -> spell instanceof TimedSpell;

View file

@ -1,6 +1,7 @@
package com.minelittlepony.unicopia.ability.magic.spell;
import com.minelittlepony.unicopia.util.NbtSerialisable;
import com.minelittlepony.unicopia.util.Tickable;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.util.math.MathHelper;
@ -11,16 +12,17 @@ import net.minecraft.util.math.MathHelper;
public interface TimedSpell extends Spell {
Timer getTimer();
class Timer implements NbtSerialisable {
public int maxDuration;
public int duration;
public int prevDuration;
class Timer implements Tickable, NbtSerialisable {
private int maxDuration;
private int duration;
private int prevDuration;
public Timer(int initial) {
maxDuration = initial;
duration = initial;
}
@Override
public void tick() {
prevDuration = duration;
duration--;

View file

@ -37,7 +37,7 @@ public class AttractiveSpell extends ShieldSpell implements ProjectileSpell, Hom
public boolean tick(Caster<?> caster, Situation situation) {
timer.tick();
if (timer.duration <= 0) {
if (timer.getTicksRemaining() <= 0) {
return false;
}

View file

@ -37,9 +37,11 @@ public class AwkwardSpell extends AbstractSpell implements TimedSpell {
if (situation != Situation.PROJECTILE) {
timer.tick();
if (timer.duration <= 0) {
if (timer.getTicksRemaining() <= 0) {
return false;
}
setDirty();
}
if (source.isClient()) {

View file

@ -52,7 +52,7 @@ public class FeatherFallSpell extends AbstractSpell implements TimedSpell {
public boolean tick(Caster<?> caster, Situation situation) {
timer.tick();
if (timer.duration <= 0) {
if (timer.getTicksRemaining() <= 0) {
return false;
}
@ -91,7 +91,7 @@ public class FeatherFallSpell extends AbstractSpell implements TimedSpell {
ParticleUtils.spawnParticles(new MagicParticleEffect(getType().getColor()), target, 7);
});
return caster.subtractEnergyCost(timer.duration % 50 == 0 ? getCostPerEntity() * targets.size() : 0);
return caster.subtractEnergyCost(timer.getTicksRemaining() % 50 == 0 ? getCostPerEntity() * targets.size() : 0);
}
protected double getCostPerEntity() {

View file

@ -0,0 +1,162 @@
package com.minelittlepony.unicopia.ability.magic.spell.effect;
import java.util.HashSet;
import java.util.Set;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.Situation;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.block.data.Ether;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.particle.UParticles;
import com.minelittlepony.unicopia.util.NbtSerialisable;
import com.minelittlepony.unicopia.util.shape.*;
import net.minecraft.block.*;
import net.minecraft.fluid.*;
import net.minecraft.nbt.*;
import net.minecraft.sound.SoundEvents;
import net.minecraft.state.property.Properties;
import net.minecraft.tag.TagKey;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
public class HydrophobicSpell extends AbstractSpell {
public static final SpellTraits DEFAULT_TRAITS = new SpellTraits.Builder()
.with(Trait.FOCUS, 5)
.with(Trait.KNOWLEDGE, 1)
.build();
private final TagKey<Fluid> affectedFluid;
private final Set<Entry> storedFluidPositions = new HashSet<>();
protected HydrophobicSpell(CustomisedSpellType<?> type, TagKey<Fluid> affectedFluid) {
super(type);
this.affectedFluid = affectedFluid;
}
@Override
public boolean apply(Caster<?> source) {
if (getTraits().get(Trait.GENEROSITY) > 0) {
return toPlaceable().apply(source);
}
return super.apply(source);
}
@Override
public boolean tick(Caster<?> source, Situation situation) {
if (!source.isClient()) {
World world = source.getReferenceWorld();
Shape area = new Sphere(false, getRange(source)).offset(source.getOriginVector());
storedFluidPositions.removeIf(entry -> {
if (!area.isPointInside(Vec3d.ofCenter(entry.pos()))) {
entry.restore(world);
return true;
}
return false;
});
area.getBlockPositions().forEach(pos -> {
BlockState state = world.getBlockState(pos);
if (state.getFluidState().isIn(affectedFluid)) {
Block block = state.getBlock();
if (block instanceof FluidBlock) {
world.setBlockState(pos, Blocks.AIR.getDefaultState(), Block.NOTIFY_LISTENERS);
storedFluidPositions.add(new Entry(pos, state.getFluidState()));
} else if (state.contains(Properties.WATERLOGGED)) {
world.setBlockState(pos, state.cycle(Properties.WATERLOGGED), Block.NOTIFY_LISTENERS);
storedFluidPositions.add(new Entry(pos, state.getFluidState()));
}
}
});
source.subtractEnergyCost(storedFluidPositions.isEmpty() ? 0.001F : 0.02F);
source.spawnParticles(new Sphere(true, getRange(source)), 10, pos -> {
BlockPos bp = new BlockPos(pos);
if (source.getReferenceWorld().getFluidState(bp.up()).isIn(affectedFluid)) {
source.addParticle(UParticles.RAIN_DROPS, pos, Vec3d.ZERO);
}
});
if (source.getMaster().age % 200 == 0) {
source.playSound(SoundEvents.BLOCK_BEACON_AMBIENT, 0.5F);
}
}
return !isDead();
}
@Override
public void onDestroyed(Caster<?> caster) {
storedFluidPositions.removeIf(entry -> {
entry.restore(caster.getReferenceWorld());
return true;
});
}
@Override
public void toNBT(NbtCompound compound) {
super.toNBT(compound);
compound.put("storedFluidPositions", Entry.SERIALIZER.writeAll(storedFluidPositions));
}
@Override
public void fromNBT(NbtCompound compound) {
super.fromNBT(compound);
storedFluidPositions.clear();
storedFluidPositions.addAll(Entry.SERIALIZER.readAll(compound.getList("storedFluidPositions", NbtElement.COMPOUND_TYPE)).toList());
}
/**
* Calculates the maximum radius of the shield. aka The area of effect.
*/
public double getRange(Caster<?> source) {
float multiplier = 1;
float min = (source instanceof Pony ? 4 : 6) + getTraits().get(Trait.POWER);
double range = (min + (source.getLevel().getScaled(source instanceof Pony ? 4 : 40) * (source instanceof Pony ? 2 : 10))) / multiplier;
return range;
}
record Entry (BlockPos pos, FluidState fluidState) {
public static final Serializer<Entry> SERIALIZER = Serializer.of(compound -> new Entry(
NbtSerialisable.BLOCK_POS.read(compound.getCompound("pos")),
NbtSerialisable.decode(FluidState.CODEC, compound.get("state"))
), entry -> {
NbtCompound compound = new NbtCompound();
compound.put("pos", NbtSerialisable.BLOCK_POS.write(entry.pos));
compound.put("state", NbtSerialisable.encode(FluidState.CODEC, entry.fluidState));
return compound;
});
void restore(World world) {
BlockState state = world.getBlockState(pos);
if (state.isAir()) {
world.setBlockState(pos, Fluids.WATER.getDefaultState().getBlockState(), Block.NOTIFY_LISTENERS);
} else if (state.contains(Properties.WATERLOGGED)) {
world.setBlockState(pos, state.cycle(Properties.WATERLOGGED), Block.NOTIFY_LISTENERS);
}
}
}
public boolean blocksFlow(Caster<?> caster, BlockPos pos, FluidState fluid) {
if (fluid.isIn(affectedFluid) && pos.isWithinDistance(caster.getOrigin(), getRange(caster) + 1)) {
System.out.println("AHA!");
}
return fluid.isIn(affectedFluid) && pos.isWithinDistance(caster.getOrigin(), getRange(caster) + 1);
}
public static boolean blocksFluidFlow(World world, BlockPos pos, BlockState state, Fluid fluid) {
return Ether.get(world).findAllSpellsInRange(pos, 500, SpellType.HYDROPHOBIC).anyMatch(pair -> {
return pair.getValue().blocksFlow(pair.getKey(), pos, fluid.getDefaultState());
});
}
}

View file

@ -48,7 +48,7 @@ public class LightSpell extends AbstractSpell implements TimedSpell {
timer.tick();
if (timer.duration <= 0) {
if (timer.getTicksRemaining() <= 0) {
return false;
}

View file

@ -0,0 +1,53 @@
package com.minelittlepony.unicopia.ability.magic.spell.effect;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.*;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import net.minecraft.entity.Entity;
import net.minecraft.nbt.NbtCompound;
public class MimicSpell extends AbstractDisguiseSpell implements HomingSpell, TimedSpell {
private final Timer timer;
protected MimicSpell(CustomisedSpellType<?> type) {
super(type);
timer = new Timer((120 + (int)(getTraits().get(Trait.FOCUS, 0, 160) * 19)) * 20);
}
@Override
public Timer getTimer() {
return timer;
}
@Override
public boolean tick(Caster<?> caster, Situation situation) {
timer.tick();
if (timer.getTicksRemaining() <= 0) {
return false;
}
setDirty();
return super.tick(caster, situation);
}
@Override
public boolean setTarget(Entity target) {
setDisguise(target);
return true;
}
@Override
public void toNBT(NbtCompound compound) {
super.toNBT(compound);
timer.toNBT(compound);
}
@Override
public void fromNBT(NbtCompound compound) {
super.fromNBT(compound);
timer.fromNBT(compound);
}
}

View file

@ -0,0 +1,124 @@
package com.minelittlepony.unicopia.ability.magic.spell.effect;
import java.util.Optional;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.Situation;
import com.minelittlepony.unicopia.entity.EntityReference;
import com.minelittlepony.unicopia.entity.behaviour.EntitySwap;
import com.minelittlepony.unicopia.entity.behaviour.Inventory;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement;
import net.minecraft.sound.SoundEvents;
public class MindSwapSpell extends MimicSpell {
private final EntityReference<LivingEntity> counterpart = new EntityReference<>();
private Optional<Inventory> myStoredInventory = Optional.empty();
private Optional<Inventory> theirStoredInventory = Optional.empty();
private boolean initialized;
protected MindSwapSpell(CustomisedSpellType<?> type) {
super(type);
}
@Override
public void onDestroyed(Caster<?> caster) {
super.onDestroyed(caster);
if (initialized && !caster.isClient()) {
counterpart.ifPresent(caster.getReferenceWorld(), e -> {
EntitySwap.ALL.accept(e, caster.getMaster());
Inventory.swapInventories(
e, myStoredInventory.or(() -> Inventory.of(e)),
caster.getMaster(), theirStoredInventory.or(() -> Inventory.of(caster.getMaster())),
a -> {},
a -> {}
);
Caster<?> other = Caster.of(e).get();
other.getSpellSlot().removeIf(SpellType.MIMIC, true);
other.playSound(SoundEvents.ENTITY_ZOMBIE_INFECT, 1);
caster.playSound(SoundEvents.ENTITY_ZOMBIE_INFECT, 1);
});
}
}
@Override
public boolean tick(Caster<?> caster, Situation situation) {
if (!caster.isClient()) {
if (!initialized) {
initialized = true;
setDirty();
counterpart.ifPresent(caster.getReferenceWorld(), e -> {
setDisguise(e);
Caster<?> other = Caster.of(e).get();
SpellType.MIMIC.withTraits().apply(other).setDisguise(caster.getMaster());
EntitySwap.ALL.accept(caster.getMaster(), e);
Inventory.swapInventories(
caster.getMaster(), Inventory.of(caster.getMaster()),
e, Inventory.of(e),
a -> myStoredInventory = Optional.of(a),
a -> theirStoredInventory = Optional.of(a)
);
other.playSound(SoundEvents.ENTITY_ZOMBIE_INFECT, 1);
caster.playSound(SoundEvents.ENTITY_ZOMBIE_INFECT, 1);
});
}
if (counterpart.getId().isPresent() && counterpart.get(caster.getReferenceWorld()) == null) {
caster.getMaster().damage(DamageSource.MAGIC, Float.MAX_VALUE);
}
}
return super.tick(caster, situation);
}
@Override
public boolean setTarget(Entity target) {
if (target instanceof LivingEntity living && Caster.of(target).isPresent()) {
counterpart.set(living);
}
return false;
}
@Override
public void toNBT(NbtCompound compound) {
super.toNBT(compound);
compound.put("counterpart", counterpart.toNBT());
compound.putBoolean("initialized", initialized);
myStoredInventory.ifPresent(mine -> compound.put("myStoredInventory", mine.toNBT(new NbtCompound())));
theirStoredInventory.ifPresent(mine -> compound.put("theirStoredInventory", mine.toNBT(new NbtCompound())));
}
@Override
public void fromNBT(NbtCompound compound) {
super.fromNBT(compound);
counterpart.fromNBT(compound.getCompound("counterpart"));
initialized = compound.getBoolean("initialized");
myStoredInventory = Optional.ofNullable(compound.contains("myStoredInventory", NbtElement.COMPOUND_TYPE) ? Inventory.fromNBT(compound.getCompound("myStoredInventory")) : null);
theirStoredInventory = Optional.ofNullable(compound.contains("theirStoredInventory", NbtElement.COMPOUND_TYPE) ? Inventory.fromNBT(compound.getCompound("theirStoredInventory")) : null);
}
}

View file

@ -11,7 +11,6 @@ import com.minelittlepony.unicopia.Affinity;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.magic.Affine;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
import com.minelittlepony.unicopia.ability.magic.spell.AbstractDisguiseSpell;
import com.minelittlepony.unicopia.ability.magic.spell.DispersableDisguiseSpell;
import com.minelittlepony.unicopia.ability.magic.spell.RainboomAbilitySpell;
import com.minelittlepony.unicopia.ability.magic.spell.PlaceableSpell;
@ -24,6 +23,7 @@ import com.minelittlepony.unicopia.util.Registries;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.tag.FluidTags;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.Util;
@ -39,7 +39,7 @@ public final class SpellType<T extends Spell> implements Affine, SpellPredicate<
public static final SpellType<PlaceableSpell> PLACED_SPELL = register("placed", Affinity.NEUTRAL, 0, false, SpellTraits.EMPTY, PlaceableSpell::new);
public static final SpellType<ThrowableSpell> THROWN_SPELL = register("thrown", Affinity.NEUTRAL, 0, false, SpellTraits.EMPTY, ThrowableSpell::new);
public static final SpellType<AbstractDisguiseSpell> CHANGELING_DISGUISE = register("disguise", Affinity.BAD, 0x19E48E, false, SpellTraits.EMPTY, DispersableDisguiseSpell::new);
public static final SpellType<DispersableDisguiseSpell> CHANGELING_DISGUISE = register("disguise", Affinity.BAD, 0x19E48E, false, SpellTraits.EMPTY, DispersableDisguiseSpell::new);
public static final SpellType<RainboomAbilitySpell> RAINBOOM = register("rainboom", Affinity.GOOD, 0xBDBDF9, false, SpellTraits.EMPTY, RainboomAbilitySpell::new);
public static final SpellType<IceSpell> FROST = register("frost", Affinity.GOOD, 0xEABBFF, true, IceSpell.DEFAULT_TRAITS, IceSpell::new);
@ -62,6 +62,9 @@ public final class SpellType<T extends Spell> implements Affine, SpellPredicate<
public static final SpellType<LightSpell> LIGHT = register("light", Affinity.GOOD, 0xEEFFAA, true, LightSpell.DEFAULT_TRAITS, LightSpell::new);
public static final SpellType<DisplacementSpell> DISPLACEMENT = register("displacement", Affinity.NEUTRAL, 0x9900FF, true, PortalSpell.DEFAULT_TRAITS, DisplacementSpell::new);
public static final SpellType<PortalSpell> PORTAL = register("portal", Affinity.GOOD, 0x99FFFF, true, PortalSpell.DEFAULT_TRAITS, PortalSpell::new);
public static final SpellType<MimicSpell> MIMIC = register("mimic", Affinity.GOOD, 0xFFFF00, true, SpellTraits.EMPTY, MimicSpell::new);
public static final SpellType<MindSwapSpell> MIND_SWAP = register("mind_swap", Affinity.BAD, 0xF9FF99, true, SpellTraits.EMPTY, MindSwapSpell::new);
public static final SpellType<HydrophobicSpell> HYDROPHOBIC = register("hydrophobic", Affinity.NEUTRAL, 0xF999FF, true, SpellTraits.EMPTY, s -> new HydrophobicSpell(s, FluidTags.WATER));
public static void bootstrap() {}

View file

@ -3,6 +3,7 @@ package com.minelittlepony.unicopia.block.data;
import java.util.*;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.magic.CasterView;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.entity.EntityReference;
@ -14,7 +15,7 @@ import net.minecraft.util.Util;
import net.minecraft.world.PersistentState;
import net.minecraft.world.World;
public class Ether extends PersistentState {
public class Ether extends PersistentState implements CasterView {
private static final Identifier ID = Unicopia.id("ether");
public static Ether get(World world) {
@ -25,6 +26,8 @@ public class Ether extends PersistentState {
private final Object locker = new Object();
private final World world;
Ether(World world, NbtCompound compound) {
this(world);
compound.getKeys().forEach(key -> {
@ -41,7 +44,7 @@ public class Ether extends PersistentState {
}
Ether(World world) {
this.world = world;
}
@Override
@ -116,6 +119,11 @@ public class Ether extends PersistentState {
}
}
@Override
public World getWorld() {
return world;
}
public class Entry implements NbtSerialisable {
public final EntityReference<?> entity;
private boolean removed;

View file

@ -84,7 +84,7 @@ public class DismissSpellScreen extends GameGui {
DrawableUtil.drawArc(matrices, 160, 1600, 0, DrawableUtil.TAU, 0x00000020, false);
super.render(matrices, mouseX, mouseY, delta);
DrawableUtil.renderRaceIcon(matrices, pony.getActualSpecies(), 0, 0, 16);
DrawableUtil.renderRaceIcon(matrices, pony.getSpecies(), 0, 0, 16);
matrices.pop();
DrawableUtil.drawLine(matrices, mouseX, mouseY - 4, mouseX, mouseY + 4, 0xFFAAFF99);

View file

@ -309,8 +309,8 @@ public class ButterflyEntity extends AmbientEntity {
super.writeCustomDataToNbt(nbt);
nbt.putInt("ticksResting", ticksResting);
nbt.putInt("breedingCooldown", breedingCooldown);
NbtSerialisable.writeBlockPos("hoveringPosition", hoveringPosition, nbt);
NbtSerialisable.writeBlockPos("flowerPosition", flowerPosition, nbt);
NbtSerialisable.BLOCK_POS.writeOptional("hoveringPosition", nbt, hoveringPosition);
NbtSerialisable.BLOCK_POS.writeOptional("flowerPosition", nbt, flowerPosition);
NbtCompound visited = new NbtCompound();
this.visited.forEach((pos, time) -> {
visited.putLong(String.valueOf(pos.asLong()), time);
@ -323,8 +323,8 @@ public class ButterflyEntity extends AmbientEntity {
super.readCustomDataFromNbt(nbt);
ticksResting = nbt.getInt("ticksResting");
breedingCooldown = nbt.getInt("breedingCooldown");
hoveringPosition = NbtSerialisable.readBlockPos("hoveringPosition", nbt);
flowerPosition = NbtSerialisable.readBlockPos("flowerPosition", nbt);
hoveringPosition = NbtSerialisable.BLOCK_POS.readOptional("hoveringPosition", nbt);
flowerPosition = NbtSerialisable.BLOCK_POS.readOptional("flowerPosition", nbt);
NbtCompound visited = nbt.getCompound("visited");
this.visited.clear();
visited.getKeys().forEach(key -> {

View file

@ -30,7 +30,9 @@ import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.projectile.ProjectileEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.packet.s2c.play.EntityPassengersSetS2CPacket;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundEvents;
import net.minecraft.util.Hand;
import net.minecraft.util.math.BlockPos;
@ -247,4 +249,10 @@ public abstract class Living<T extends LivingEntity> implements Equine<T>, Caste
// ply.networkHandler.sendPacket(new EntityVelocityUpdateS2CPacket(ply));
//}
}
public static void transmitPassengers(Entity entity) {
if (entity.world instanceof ServerWorld sw) {
sw.getChunkManager().sendToNearbyPlayers(entity, new EntityPassengersSetS2CPacket(entity));
}
}
}

View file

@ -112,9 +112,9 @@ public interface Disguise extends FlightType.Provider, PlayerDimensions.Provider
return !isDead() && !source.getMaster().isDead();
}
static abstract class PlayerAccess extends PlayerEntity {
public static abstract class PlayerAccess extends PlayerEntity {
public PlayerAccess() { super(null, null, 0, null, null); }
static TrackedData<Byte> getModelBitFlag() {
public static TrackedData<Byte> getModelBitFlag() {
return PLAYER_MODEL_PARTS;
}
}

View file

@ -42,6 +42,7 @@ import net.minecraft.entity.mob.VexEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.projectile.ShulkerBulletEntity;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement;
import net.minecraft.util.shape.VoxelShape;
public class EntityAppearance implements NbtSerialisable, PlayerDimensions.Provider, FlightType.Provider, EntityCollisions.ComplexCollidable {
@ -132,6 +133,9 @@ public class EntityAppearance implements NbtSerialisable, PlayerDimensions.Provi
entity = InteractionManager.instance().createPlayer(source.getEntity(), profile);
entity.setCustomName(source.getMaster().getName());
((PlayerEntity)entity).readNbt(nbt.getCompound("playerNbt"));
if (nbt.contains("playerVisibleParts", NbtElement.BYTE_TYPE)) {
entity.getDataTracker().set(Disguise.PlayerAccess.getModelBitFlag(), nbt.getByte("playerVisibleParts"));
}
entity.setUuid(UUID.randomUUID());
entity.extinguish();
@ -292,7 +296,7 @@ public class EntityAppearance implements NbtSerialisable, PlayerDimensions.Provi
String newId = compound.getString("entityId");
String newPlayerName = null;
if (compound.contains("entity") && compound.getCompound("entity").contains("playerName")) {
if (compound.contains("entity", NbtElement.COMPOUND_TYPE) && compound.getCompound("entity").contains("playerName", NbtElement.STRING_TYPE)) {
newPlayerName = compound.getCompound("entity").getString("playerName");
}
@ -303,13 +307,11 @@ public class EntityAppearance implements NbtSerialisable, PlayerDimensions.Provi
remove();
}
if (compound.contains("entity")) {
if (compound.contains("entity", NbtElement.COMPOUND_TYPE)) {
entityId = newId;
entityNbt = compound.getCompound("entity");
compound.getString("entityData");
if (entity != null) {
try {
entity.readNbt(entityNbt);
@ -332,20 +334,16 @@ public class EntityAppearance implements NbtSerialisable, PlayerDimensions.Provi
private static NbtCompound encodeEntityToNBT(Entity entity) {
NbtCompound entityNbt = new NbtCompound();
if (entity instanceof PlayerEntity) {
GameProfile profile = ((PlayerEntity)entity).getGameProfile();
if (entity instanceof PlayerEntity player) {
GameProfile profile = player.getGameProfile();
entityNbt.putString("id", "player");
if (profile.getId() != null) {
entityNbt.putUuid("playerId", profile.getId());
}
entityNbt.putString("playerName", profile.getName());
NbtCompound tag = new NbtCompound();
entity.writeNbt(tag);
entityNbt.put("playerNbt", tag);
entityNbt.putByte("playerVisibleParts", player.getDataTracker().get(Disguise.PlayerAccess.getModelBitFlag()));
entityNbt.put("playerNbt", player.writeNbt(new NbtCompound()));
} else {
entity.saveSelfNbt(entityNbt);
}

View file

@ -169,6 +169,7 @@ public class EntityBehaviour<T extends Entity> {
to.prevYaw = from.prevYaw;
to.horizontalSpeed = from.horizontalSpeed;
to.prevHorizontalSpeed = from.prevHorizontalSpeed;
to.fallDistance = 0;
to.setOnGround(from.isOnGround());
to.setInvulnerable(from.isInvulnerable() || (from instanceof PlayerEntity player && player.getAbilities().creativeMode));

View file

@ -0,0 +1,57 @@
package com.minelittlepony.unicopia.entity.behaviour;
import java.util.*;
import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.util.Swap;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.mob.PathAwareEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.packet.s2c.play.EntitySetHeadYawS2CPacket;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.MathHelper;
public interface EntitySwap {
Swap<Entity> POSITION = Swap.of(Entity::getPos, (entity, pos) -> {
entity.teleport(pos.getX(), pos.getY(), pos.getZ());
if (entity instanceof PathAwareEntity pae && !(entity instanceof PlayerEntity)) {
pae.getNavigation().stop();
}
});
Swap<Entity> VELOCITY = Swap.of(Entity::getVelocity, (entity, vel) -> {
entity.setVelocity(vel);
Living.updateVelocity(entity);
});
Swap<Entity> PITCH = Swap.of(Entity::getPitch, Entity::setPitch);
Swap<Entity> YAW = Swap.of(Entity::getYaw, Entity::setYaw);
Swap<Entity> HEAD_YAW = Swap.of(Entity::getHeadYaw, (entity, headYaw) -> {
entity.setHeadYaw(headYaw);
if (entity.world instanceof ServerWorld sw) {
sw.getChunkManager().sendToNearbyPlayers(entity, new EntitySetHeadYawS2CPacket(entity, (byte)MathHelper.floor(entity.getHeadYaw() * 256F / 360F)));
}
});
Swap<Entity> BODY_YAW = Swap.of(Entity::getBodyYaw, Entity::setBodyYaw);
Swap<Entity> FIRE_TICKS = Swap.of(Entity::getFireTicks, Entity::setFireTicks);
Swap<Entity> PASSENGERS = Swap.of(entity -> new ArrayList<>(entity.getPassengerList()), (entity, passengers) -> {
entity.removeAllPassengers();
passengers.forEach(passenger -> passenger.startRiding(entity));
Living.transmitPassengers(entity);
});
Swap<LivingEntity> STATUS_EFFECTS = Swap.of(
entity -> Set.copyOf(entity.getActiveStatusEffects().values()),
(entity, effects) -> {
entity.clearStatusEffects();
effects.forEach(entity::addStatusEffect);
}
);
Swap<LivingEntity> HEALTH = Swap.of(LivingEntity::getHealth, LivingEntity::getMaxHealth, LivingEntity::setHealth, Number::floatValue);
Swap<LivingEntity> AIR = Swap.of(LivingEntity::getAir, LivingEntity::getMaxAir, LivingEntity::setAir, Number::intValue);
List<Swap<Entity>> REGISTRY = new ArrayList<>(List.of(
Swap.union(POSITION, VELOCITY, PITCH, YAW, HEAD_YAW, BODY_YAW, FIRE_TICKS, PASSENGERS),
Swap.union(STATUS_EFFECTS, HEALTH, AIR).upcast(e -> e instanceof LivingEntity)
));
Swap<Entity> ALL = Swap.union(REGISTRY);
}

View file

@ -0,0 +1,117 @@
package com.minelittlepony.unicopia.entity.behaviour;
import java.util.*;
import java.util.function.Consumer;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.*;
import net.minecraft.util.collection.DefaultedList;
public record Inventory (
Map<EquipmentSlot, ItemStack> equipment,
Optional<DefaultedList<ItemStack>> mainInventory
) {
public static Optional<Inventory> of(LivingEntity entity) {
Map<EquipmentSlot, ItemStack> equipment = new EnumMap<>(EquipmentSlot.class);
for (var slot : EquipmentSlot.values()) {
equipment.put(slot, entity.getEquippedStack(slot));
}
if (entity instanceof PlayerEntity player) {
PlayerInventory inventory = player.getInventory();
DefaultedList<ItemStack> mainInventory = DefaultedList.ofSize(inventory.size(), ItemStack.EMPTY);
for (int i = 0; i < mainInventory.size(); i++) {
mainInventory.set(i, inventory.getStack(i));
}
return Optional.of(new Inventory(equipment, Optional.of(mainInventory)));
}
return Optional.of(new Inventory(equipment, Optional.empty()));
}
/**
* Copies the inventory into another entity.
*
* @return Returns the left overs that could not be copied
*/
public Inventory copyInto(LivingEntity into) {
if (into instanceof PlayerEntity player) {
mainInventory().ifPresentOrElse(main -> {
PlayerInventory pe = player.getInventory();
int i = 0;
for (; i < pe.size(); i++) {
pe.setStack(i, i < main.size() ? main.get(i) : ItemStack.EMPTY);
}
for (; i < main.size(); i++) {
into.dropStack(main.get(i));
}
}, () -> {
PlayerInventory pe = player.getInventory();
for (int i = 0; i < pe.size(); i++) {
pe.setStack(i, ItemStack.EMPTY);
}
equipment().forEach(player::equipStack);
});
return null;
}
equipment().forEach(into::equipStack);
return this;
}
public NbtCompound toNBT(NbtCompound compound) {
NbtCompound eq = new NbtCompound();
equipment().forEach((slot, stack) -> {
eq.put(slot.getName(), stack.writeNbt(new NbtCompound()));
});
compound.put("equipment", eq);
mainInventory().ifPresent(main -> {
NbtList list = new NbtList();
main.forEach(stack -> {
list.add(stack.writeNbt(new NbtCompound()));
});
compound.put("main", list);
});
return compound;
}
public static void swapInventories(LivingEntity me, Optional<Inventory> myInv, LivingEntity them, Optional<Inventory> theirInv,
Consumer<Inventory> outOverflowConsumer,
Consumer<Inventory> inOverflowConsumer) {
Optional<Inventory> outOverflow = Inventory.copyInventoryInto(myInv, them);
Optional<Inventory> inOverflow = Inventory.copyInventoryInto(theirInv, me);
outOverflow.ifPresent(outOverflowConsumer);
inOverflow.ifPresent(inOverflowConsumer);
}
public static Optional<Inventory> copyInventoryInto(Optional<Inventory> inventory, LivingEntity to) {
return inventory.map(inv -> inv.copyInto(to));
}
public static Inventory fromNBT(NbtCompound compound) {
Map<EquipmentSlot, ItemStack> equipment = new EnumMap<>(EquipmentSlot.class);
NbtCompound eq = compound.getCompound("equipment");
eq.getKeys().forEach(key -> {
equipment.put(EquipmentSlot.byName(key), ItemStack.fromNbt(eq.getCompound(key)));
});
if (!compound.contains("main", NbtElement.LIST_TYPE)) {
return new Inventory(equipment, Optional.empty());
}
NbtList list = compound.getList("main", NbtElement.COMPOUND_TYPE);
DefaultedList<ItemStack> main = DefaultedList.ofSize(list.size(), ItemStack.EMPTY);
for (int i = 0; i < list.size(); i++) {
main.set(i, ItemStack.fromNbt(list.getCompound(i)));
}
return new Inventory(equipment, Optional.of(main));
}
}

View file

@ -15,11 +15,13 @@ import com.minelittlepony.unicopia.*;
import com.minelittlepony.unicopia.ability.AbilityDispatcher;
import com.minelittlepony.unicopia.ability.EarthPonyStompAbility;
import com.minelittlepony.unicopia.ability.magic.*;
import com.minelittlepony.unicopia.ability.magic.spell.AbstractDisguiseSpell;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.ability.magic.spell.trait.TraitDiscovery;
import com.minelittlepony.unicopia.advancement.UCriteria;
import com.minelittlepony.unicopia.entity.*;
import com.minelittlepony.unicopia.entity.behaviour.EntityAppearance;
import com.minelittlepony.unicopia.entity.duck.LivingEntityDuck;
import com.minelittlepony.unicopia.entity.effect.SunBlindnessStatusEffect;
import com.minelittlepony.unicopia.entity.effect.UEffects;
@ -52,7 +54,6 @@ import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.packet.s2c.play.EntityPassengersSetS2CPacket;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundEvents;
@ -167,7 +168,13 @@ public class Pony extends Living<PlayerEntity> implements Transmittable, Copieab
@Override
public Race getSpecies() {
return getActualSpecies();
return getSpellSlot()
.get(SpellPredicate.IS_MIMIC, true)
.map(AbstractDisguiseSpell::getDisguise)
.map(EntityAppearance::getAppearance)
.flatMap(Pony::of)
.map(Pony::getActualSpecies)
.orElse(getActualSpecies());
}
public Race getActualSpecies() {
@ -318,10 +325,7 @@ public class Pony extends Living<PlayerEntity> implements Transmittable, Copieab
}
} else {
entity.stopRiding();
if (ridee instanceof ServerPlayerEntity) {
((ServerPlayerEntity)ridee).networkHandler.sendPacket(new EntityPassengersSetS2CPacket(ridee));
}
Living.transmitPassengers(ridee);
}
}
}
@ -567,7 +571,7 @@ public class Pony extends Living<PlayerEntity> implements Transmittable, Copieab
compound.putString("playerSpecies", Race.REGISTRY.getId(getActualSpecies()).toString());
compound.putFloat("magicExhaustion", magicExhaustion);
compound.putInt("ticksHanging", ticksHanging);
NbtSerialisable.writeBlockPos("hangingPosition", hangingPosition, compound);
BLOCK_POS.writeOptional("hangingPosition", compound, hangingPosition);
compound.putInt("ticksInSun", ticksInSun);
compound.putBoolean("hasShades", hasShades);
compound.put("powers", powers.toNBT());
@ -604,7 +608,7 @@ public class Pony extends Living<PlayerEntity> implements Transmittable, Copieab
magicExhaustion = compound.getFloat("magicExhaustion");
ticksHanging = compound.getInt("ticksHanging");
hangingPosition = NbtSerialisable.readBlockPos("hangingPosition", compound);
hangingPosition = NbtSerialisable.BLOCK_POS.readOptional("hangingPosition", compound);
ticksInSun = compound.getInt("ticksInSun");
hasShades = compound.getBoolean("hasShades");

View file

@ -0,0 +1,25 @@
package com.minelittlepony.unicopia.mixin;
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.CallbackInfoReturnable;
import com.minelittlepony.unicopia.ability.magic.spell.effect.HydrophobicSpell;
import net.minecraft.block.BlockState;
import net.minecraft.fluid.FlowableFluid;
import net.minecraft.fluid.Fluid;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
@Mixin(FlowableFluid.class)
abstract class MixinFlowableFluid {
@Inject(method = "canFill", at = @At("HEAD"), cancellable = true)
private void onCanFill(BlockView world, BlockPos pos, BlockState state, Fluid fluid, CallbackInfoReturnable<Boolean> info) {
if (world instanceof World w && HydrophobicSpell.blocksFluidFlow(w, pos, state, fluid)) {
info.setReturnValue(false);
}
}
}

View file

@ -39,6 +39,6 @@ public interface ParticleSource extends ParticleSpawner {
@Override
default void addParticle(ParticleEffect effect, Vec3d position, Vec3d velocity) {
getReferenceWorld().addParticle(effect, position.x, position.y, position.z, velocity.x, velocity.y, velocity.z);
ParticleUtils.spawnParticle(getReferenceWorld(), effect, position, velocity);
}
}

View file

@ -4,11 +4,15 @@ import java.util.*;
import java.util.function.Function;
import java.util.stream.Stream;
import com.mojang.serialization.Codec;
import net.minecraft.nbt.*;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
public interface NbtSerialisable {
Serializer<BlockPos> BLOCK_POS = Serializer.of(NbtHelper::toBlockPos, NbtHelper::fromBlockPos);
/**
* Called to save this to nbt to persist state on file or to transmit over the network
*
@ -41,12 +45,12 @@ public interface NbtSerialisable {
return new Vec3d(list.getDouble(0), list.getDouble(1), list.getDouble(2));
}
static void writeBlockPos(String name, Optional<BlockPos> pos, NbtCompound nbt) {
pos.map(NbtHelper::fromBlockPos).ifPresent(p -> nbt.put(name, p));
static <T> T decode(Codec<T> codec, NbtElement nbt) {
return codec.decode(NbtOps.INSTANCE, nbt).result().get().getFirst();
}
static Optional<BlockPos> readBlockPos(String name, NbtCompound nbt) {
return nbt.contains(name) ? Optional.ofNullable(NbtHelper.toBlockPos(nbt.getCompound(name))) : Optional.empty();
static <T> NbtElement encode(Codec<T> codec, T value) {
return codec.encodeStart(NbtOps.INSTANCE, value).result().get();
}
interface Serializer<T> {
@ -54,6 +58,16 @@ public interface NbtSerialisable {
NbtCompound write(T t);
default Optional<T> readOptional(String name, NbtCompound compound) {
return compound.contains(name, NbtElement.COMPOUND_TYPE)
? Optional.ofNullable(read(compound.getCompound(name)))
: Optional.empty();
}
default void writeOptional(String name, NbtCompound compound, Optional<T> t) {
t.map(this::write).ifPresent(tag -> compound.put(name, tag));
}
default T read(NbtElement element) {
return read((NbtCompound)element);
}

View file

@ -0,0 +1,67 @@
package com.minelittlepony.unicopia.util;
import java.util.*;
import java.util.function.*;
/**
* Utility for performing an atomic swap of two values.
*/
@FunctionalInterface
public interface Swap<T> extends BiConsumer<T, T> {
/**
* Returns a new swap that performs the same action as this one only if the passed in shouldRun check passes on both inputs.
*/
@SuppressWarnings("unchecked")
default <E> Swap<E> upcast(Predicate<E> shouldRun) {
Swap<T> swap = this;
return (a, b) -> {
if (shouldRun.test(a) && shouldRun.test(b)) {
swap.accept((T)a, (T)b);
}
};
}
@SafeVarargs
static <E> Swap<E> union(Swap<E>... swaps) {
return union(List.of(swaps));
}
/**
* Creates a swap from a collection of multiple swaps.
* Executes them in the order they are presented by Iterable#forEach
*
* Changes to the underlying list an equivalent change to the swap returned by this method.
*/
static <E> Swap<E> union(Iterable<Swap<E>> swaps) {
return (a, b) -> swaps.forEach(consumer -> consumer.accept(a, b));
}
/**
* Creates a swap for switching numerical values.
*/
static <E, N extends Number> Swap<E> of(Function<E, N> getter, BiConsumer<E, N> setter, Function<Float, N> converter) {
return of(getter, e -> converter.apply(1F), setter, converter);
}
/**
* Creates a swap for converting numerical values where the source and destination may have different scales.
*/
static <E, N extends Number> Swap<E> of(Function<E, N> getter, Function<E, N> maxGetter, BiConsumer<E, N> setter, Function<Float, N> converter) {
return of(
e -> getter.apply(e).floatValue() / maxGetter.apply(e).floatValue(),
(e, value) -> setter.accept(e, converter.apply(value.floatValue() * maxGetter.apply(e).floatValue()))
);
}
/**
* Creates a swap from a getter and setter.
*/
static <E, T> Swap<E> of(Function<E, T> getter, BiConsumer<E, T> setter) {
return (a, b) -> {
T aa = getter.apply(a);
T bb = getter.apply(b);
setter.accept(a, bb);
setter.accept(b, aa);
};
}
}

View file

@ -8,7 +8,7 @@ import org.jetbrains.annotations.Nullable;
import net.minecraft.entity.Entity;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraft.world.EntityView;
public interface VecHelper {
@ -24,7 +24,7 @@ public interface VecHelper {
};
}
static List<Entity> findInRange(@Nullable Entity origin, World w, Vec3d pos, double radius, @Nullable Predicate<Entity> predicate) {
static List<Entity> findInRange(@Nullable Entity origin, EntityView w, Vec3d pos, double radius, @Nullable Predicate<Entity> predicate) {
double diameter = radius * 2;
return w.getOtherEntities(origin, Box.of(pos, diameter, diameter, diameter), predicate == null ? inRange(pos, radius) : inRange(pos, radius).and(predicate));
}

View file

@ -15,7 +15,6 @@ import net.minecraft.util.math.random.Random;
*Interface for a 3d shape, used for spawning particles in a designated area (or anything else you need shapes for).
*/
public interface PointGenerator {
/**
* Get the volume of space filled by this shape, or the surface area if hollow.
*
@ -72,7 +71,6 @@ public interface PointGenerator {
default PointGenerator offset(Vec3d offset) {
final PointGenerator source = this;
return new PointGenerator() {
@Override
public double getVolumeOfSpawnableSpace() {
return source.getVolumeOfSpawnableSpace();

View file

@ -2,8 +2,8 @@ package com.minelittlepony.unicopia.util.shape;
import java.util.stream.Stream;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.*;
import net.minecraft.util.math.random.Random;
/**
*
@ -45,4 +45,51 @@ public interface Shape extends PointGenerator {
}
return new RotatedShape(this, pitch, yaw);
}
/**
* Returns a new point generator where all of its points are offset by the specified amount.
*/
@Override
default Shape offset(Vec3i offset) {
return offset(Vec3d.of(offset));
}
/**
* Returns a new point generator where all of its points are offset by the specified amount.
*/
@Override
default Shape offset(Vec3d offset) {
final Shape source = this;
return new Shape() {
@Override
public double getVolumeOfSpawnableSpace() {
return source.getVolumeOfSpawnableSpace();
}
@Override
public Vec3d computePoint(Random rand) {
return source.computePoint(rand).add(offset);
}
@Override
public Stream<BlockPos> getBlockPositions() {
return source.getBlockPositions().map(pos -> pos.add(offset.x, offset.y, offset.z));
}
@Override
public Vec3d getLowerBound() {
return source.getLowerBound().add(offset);
}
@Override
public Vec3d getUpperBound() {
return source.getLowerBound().add(offset);
}
@Override
public boolean isPointInside(Vec3d point) {
return source.isPointInside(point.subtract(offset));
}
};
}
}

View file

@ -184,6 +184,8 @@
"spell.unicopia.frost": "Frost",
"spell.unicopia.frost.lore": "Chilling to the touch, this gem will freeze whatever it is used on",
"spell.unicopia.hydrophobic": "Repel Water",
"spell.unicopia.hydrophobic.lore": "Creates a protective bubble around the user that prevents water from entering",
"spell.unicopia.chilling_breath": "Chilling Breath",
"spell.unicopia.chilling_breath.lore": "Alters the ability of certain objects to distenguish between hot and cold",
"spell.unicopia.scorch": "Scorching",
@ -202,6 +204,10 @@
"spell.unicopia.vortex.lore": "Creates a magnetic force that pulls in other targets",
"spell.unicopia.dark_vortex": "Dark Vortex",
"spell.unicopia.dark_vortex.lore": "Creates a black hole from which nothing can escape",
"spell.unicopia.mimic": "Mimic",
"spell.unicopia.mimic.lore": "Temporarily changes the caster's appearance to look like another entity or player",
"spell.unicopia.mind_swap": "Mind Swap",
"spell.unicopia.mind_swap.lore": "Temporarily swaps the caster's mind into the body of another entity or player",
"spell.unicopia.displacement": "Displacement",
"spell.unicopia.displacement.lore": "Swaps the caster's locatin with that of another entity",
"spell.unicopia.portal": "Arcane Rift",

View file

@ -15,6 +15,7 @@
"MixinEntity",
"MixinFallingBlock",
"MixinFallingBlockEntity",
"MixinFlowableFluid",
"MixinItem",
"MixinItemEntity",
"MixinLivingEntity",