Files
Wizardry/src/main/java/electroblob/wizardry/spell/Transportation.java
T
Electroblob77 f37812be3e That's one heck of a commit you've got there...
I may have got a bit behind with version control. A lot behind, in fact. Maybe I'll go back and split this sometime - then again, I probably won't. But hey, at least it's here!
2019-08-18 00:04:18 +01:00

198 lines
7.5 KiB
Java

package electroblob.wizardry.spell;
import electroblob.wizardry.block.BlockTransportationStone;
import electroblob.wizardry.data.IStoredVariable;
import electroblob.wizardry.data.Persistence;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.packet.PacketTransportation;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.Location;
import electroblob.wizardry.util.NBTExtras;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.MobEffects;
import net.minecraft.item.EnumAction;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class Transportation extends Spell {
public static final String TELEPORT_COUNTDOWN = "teleport_countdown";
public static final int MAX_REMEMBERED_LOCATIONS = 4;
// For some reason 'the diamond' doesn't work if I chain methods onto this. Type inference is weird.
public static final IStoredVariable<List<Location>> LOCATIONS_KEY = new IStoredVariable.StoredVariable<List<Location>, NBTTagList>("stoneCirclePos",
s -> NBTExtras.listToNBT(s, Location::toNBT), t -> new ArrayList<>(NBTExtras.NBTToList(t, Location::fromNBT)), Persistence.ALWAYS).setSynced();
public static final IStoredVariable<Integer> COUNTDOWN_KEY = IStoredVariable.StoredVariable.ofInt("tpCountdown", Persistence.NEVER).withTicker(Transportation::update);
public Transportation(){
super("transportation", EnumAction.BOW, false);
addProperties(TELEPORT_COUNTDOWN);
WizardData.registerStoredVariables(LOCATIONS_KEY, COUNTDOWN_KEY);
}
@Override
public boolean requiresPacket(){
return false;
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
WizardData data = WizardData.get(caster);
// Fixes the sound not playing in first person.
if(world.isRemote) this.playSound(world, caster, ticksInUse, -1, modifiers);
// Only works when the caster is in the same dimension.
if(data != null){
Integer countdown = data.getVariable(COUNTDOWN_KEY);
if(countdown == null || countdown == 0){
List<Location> locations = data.getVariable(Transportation.LOCATIONS_KEY);
if(locations == null) data.setVariable(Transportation.LOCATIONS_KEY, locations = new ArrayList<>(Transportation.MAX_REMEMBERED_LOCATIONS));
if(locations.isEmpty()){
if(!world.isRemote) caster.sendStatusMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".undefined"), true);
return false;
}
if(ItemArtefact.isArtefactActive(caster, WizardryItems.charm_transportation)){
List<Location> locationsInDimension = locations.stream().filter(l -> l.dimension == caster.dimension).collect(Collectors.toList());
if(locationsInDimension.isEmpty()){
if(!world.isRemote) caster.sendStatusMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".wrongdimension"), true);
return false;
}
Location destination = getLocationAimedAt(caster, locationsInDimension, 1);
if(destination == null) return false; // None of them were aimed at
if(attemptTravelTo(caster, world, destination.pos, modifiers)){
// Move the selected destination to the end of the list, making it the 'most recent' one
// This makes my life easier in update() below, and is a kind of useful feature too
locations.remove(destination);
locations.add(destination);
if(!world.isRemote) data.sync();
return true;
}
}else{
Location destination = locations.get(locations.size() - 1); // The most recent one, or the only one
if(destination.dimension == caster.dimension){
return attemptTravelTo(caster, world, destination.pos, modifiers);
}else{
if(!world.isRemote) caster.sendStatusMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".wrongdimension"), true);
}
}
}
}
return false;
}
// The following four methods centralise and neaten up the code
// Since the deviation angle is also used by the UI renderer, this also ensures they use the same calculation
/** Returns the location from the given list that the give player is aiming at, or null if they are not aiming
* at any of them. */
public static Location getLocationAimedAt(EntityPlayer player, List<Location> locations, float partialTicks){
return locations.stream()
.filter(l -> isLocationAimedAt(player, l.pos, partialTicks))
.min(Comparator.comparingDouble(l -> getLookDeviationAngle(player, l.pos, partialTicks)))
.orElse(null);
}
public static boolean isLocationAimedAt(EntityPlayer player, BlockPos pos, float partialTicks){
Vec3d origin = player.getPositionEyes(partialTicks);
Vec3d centre = WizardryUtilities.getCentre(pos);
Vec3d direction = centre.subtract(origin);
double distance = direction.length();
return getLookDeviationAngle(player, pos, partialTicks) < getIconSize(distance);
}
public static double getLookDeviationAngle(EntityPlayer player, BlockPos pos, float partialTicks){
Vec3d origin = player.getPositionEyes(partialTicks);
Vec3d look = player.getLook(partialTicks);
Vec3d centre = WizardryUtilities.getCentre(pos);
Vec3d direction = centre.subtract(origin);
double distance = direction.length();
return Math.acos(direction.dotProduct(look) / distance); // Angle between a and b = acos((a.b) / (|a|*|b|))
}
public static double getIconSize(double distance){
return 0.05 + 2/(distance + 5);
}
private boolean attemptTravelTo(EntityPlayer player, World world, BlockPos destination, SpellModifiers modifiers){
WizardData data = WizardData.get(player);
if(BlockTransportationStone.testForCircle(world, destination)){
this.playSound(world, player, 0, -1, modifiers);
player.addPotionEffect(new PotionEffect(MobEffects.NAUSEA, 150, 0));
data.setVariable(COUNTDOWN_KEY, getProperty(TELEPORT_COUNTDOWN).intValue());
return true;
}else{
if(!world.isRemote) player.sendStatusMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".missing"), true);
return false;
}
}
private static int update(EntityPlayer player, Integer countdown){
if(countdown == null) return 0;
if(!player.world.isRemote){
WizardData data = WizardData.get(player);
List<Location> locations = data.getVariable(Transportation.LOCATIONS_KEY);
if(locations == null || locations.isEmpty()) return 0;
// If the location was selected, either it was already at the end of the list or it was moved there
Location destination = locations.get(locations.size() - 1);
if(countdown == 1 && destination.dimension == player.dimension){
player.setPositionAndUpdate(destination.pos.getX() + 0.5, destination.pos.getY(), destination.pos.getZ() + 0.5);
player.addPotionEffect(new PotionEffect(MobEffects.BLINDNESS, 50, 0));
IMessage msg = new PacketTransportation.Message(player.getEntityId());
WizardryPacketHandler.net.sendToDimension(msg, player.world.provider.getDimension());
}
if(countdown > 0){
countdown--;
}
}
return countdown;
}
}