389 lines
15 KiB
Java
389 lines
15 KiB
Java
/*
|
|
* GNU Lesser General Public License v3
|
|
* Copyright (C) 2024 Tschipp
|
|
* mrtschipp@gmail.com
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 3 of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
package tschipp.carryon.common.carry;
|
|
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Direction;
|
|
import net.minecraft.server.level.ServerPlayer;
|
|
import net.minecraft.sounds.SoundEvents;
|
|
import net.minecraft.sounds.SoundSource;
|
|
import net.minecraft.world.InteractionHand;
|
|
import net.minecraft.world.effect.MobEffects;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.entity.Mob;
|
|
import net.minecraft.world.entity.animal.equine.Horse;
|
|
import net.minecraft.world.item.ItemStack;
|
|
import net.minecraft.world.item.context.BlockPlaceContext;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.block.Block;
|
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
|
import net.minecraft.world.level.block.state.properties.Property;
|
|
import net.minecraft.world.level.gamerules.GameRules;
|
|
import net.minecraft.world.phys.BlockHitResult;
|
|
import net.minecraft.world.phys.Vec3;
|
|
import net.minecraft.world.phys.shapes.CollisionContext;
|
|
import tschipp.carryon.Constants;
|
|
import tschipp.carryon.common.carry.CarryOnData.CarryType;
|
|
import tschipp.carryon.common.config.ListHandler;
|
|
import tschipp.carryon.common.scripting.CarryOnScript.ScriptEffects;
|
|
import tschipp.carryon.networking.clientbound.ClientboundStartRidingOtherPlayerPacket;
|
|
import tschipp.carryon.platform.Services;
|
|
|
|
import javax.annotation.Nullable;
|
|
import java.util.ArrayList;
|
|
import java.util.Comparator;
|
|
import java.util.List;
|
|
import java.util.function.BiFunction;
|
|
|
|
public class PlacementHandler
|
|
{
|
|
|
|
public static boolean tryPlaceBlock(ServerPlayer player, BlockPos pos, Direction facing, @Nullable BiFunction<BlockPos, BlockState, Boolean> placementCallback)
|
|
{
|
|
CarryOnData carry = CarryOnDataManager.getCarryData(player);
|
|
if (!carry.isCarrying(CarryOnData.CarryType.BLOCK))
|
|
return false;
|
|
|
|
if (player.tickCount == carry.getTick())
|
|
return false;
|
|
|
|
Level level = player.level();
|
|
BlockState state = carry.getBlock();
|
|
|
|
BlockPlaceContext context = new BlockPlaceContext(player, InteractionHand.MAIN_HAND, ItemStack.EMPTY, BlockHitResult.miss(player.position(), facing, pos));
|
|
|
|
if (!level.getBlockState(pos).canBeReplaced(context))
|
|
pos = pos.relative(facing);
|
|
|
|
context = new BlockPlaceContext(player, InteractionHand.MAIN_HAND, ItemStack.EMPTY, BlockHitResult.miss(player.position(), facing, pos));
|
|
|
|
BlockEntity blockEntity = carry.getBlockEntity(pos, level.registryAccess());
|
|
|
|
state = getPlacementState(state, player, context, pos);
|
|
|
|
boolean canPlace = state.canSurvive(level, pos) && level.mayInteract(player, pos) && level.getBlockState(pos).canBeReplaced(context) && level.isUnobstructed(state, pos, CollisionContext.of(player));
|
|
if (!canPlace) {
|
|
level.playSound(null, player.getX(), player.getY(), player.getZ(), SoundEvents.LAVA_POP, SoundSource.PLAYERS, 0.5F, 0.5F);
|
|
return false;
|
|
}
|
|
|
|
boolean doPlace = placementCallback == null || placementCallback.apply(pos, state);
|
|
|
|
if (!doPlace)
|
|
return false;
|
|
|
|
if(level.isOutsideBuildHeight(pos))
|
|
return false;
|
|
|
|
if (carry.getActiveScript().isPresent()) {
|
|
ScriptEffects effects = carry.getActiveScript().get().scriptEffects();
|
|
String cmd = effects.commandPlace();
|
|
if (!cmd.isEmpty())
|
|
player.level().getServer().getCommands().performPrefixedCommand(player.level().getServer().createCommandSourceStack(), "/execute as " + player.getGameProfile().name() + " run " + cmd);
|
|
}
|
|
|
|
level.setBlockAndUpdate(pos, state);
|
|
if (blockEntity != null) {
|
|
blockEntity.setBlockState(state);
|
|
level.setBlockEntity(blockEntity);
|
|
}
|
|
|
|
level.updateNeighborsAt(pos.relative(Direction.DOWN), level.getBlockState(pos.relative(Direction.DOWN)).getBlock());
|
|
carry.clear();
|
|
CarryOnDataManager.setCarryData(player, carry);
|
|
player.playSound(state.getSoundType().getPlaceSound(), 1.0f, 0.5f);
|
|
level.playSound(null, pos, state.getSoundType().getPlaceSound(), SoundSource.BLOCKS, 1.0f, 0.5f);
|
|
player.swing(InteractionHand.MAIN_HAND, true);
|
|
player.removeEffect(MobEffects.SLOWNESS);
|
|
return true;
|
|
}
|
|
|
|
private static BlockState getPlacementState(BlockState state, ServerPlayer player, BlockPlaceContext context, BlockPos pos)
|
|
{
|
|
BlockState placementState = state.getBlock().getStateForPlacement(context);
|
|
if (placementState == null || placementState.getBlock() != state.getBlock())
|
|
placementState = state;
|
|
|
|
for (var prop : placementState.getProperties()) {
|
|
if (prop.getValueClass() == Direction.class)
|
|
state = updateProperty(state, placementState, prop);
|
|
if (prop.getValueClass() == Direction.Axis.class)
|
|
state = updateProperty(state, placementState, prop);
|
|
//This is needed for certain blocks, otherwise we get problems like chests not connecting
|
|
if (ListHandler.isPropertyException(prop)) {
|
|
state = updateProperty(state, placementState, prop);
|
|
}
|
|
}
|
|
|
|
BlockState updatedState = Block.updateFromNeighbourShapes(state, player.level(), pos);
|
|
if (updatedState.getBlock() == state.getBlock())
|
|
state = updatedState;
|
|
|
|
if (placementState.hasProperty(BlockStateProperties.WATERLOGGED) && state.hasProperty(BlockStateProperties.WATERLOGGED))
|
|
state = state.setValue(BlockStateProperties.WATERLOGGED, placementState.getValue(BlockStateProperties.WATERLOGGED));
|
|
|
|
return state;
|
|
}
|
|
|
|
private static <T extends Comparable<T>> BlockState updateProperty(BlockState state, BlockState otherState, Property<T> prop)
|
|
{
|
|
var val = otherState.getValue(prop);
|
|
return state.setValue(prop, val);
|
|
}
|
|
|
|
public static boolean tryPlaceEntity(ServerPlayer player, BlockPos pos, Direction facing, @Nullable BiFunction<Vec3, Entity, Boolean> placementCallback)
|
|
{
|
|
CarryOnData carry = CarryOnDataManager.getCarryData(player);
|
|
|
|
if (!carry.isCarrying(CarryType.ENTITY) && !carry.isCarrying(CarryType.PLAYER))
|
|
return false;
|
|
|
|
if (player.tickCount == carry.getTick())
|
|
return false;
|
|
|
|
Level level = player.level();
|
|
|
|
BlockPlaceContext context = new BlockPlaceContext(player, InteractionHand.MAIN_HAND, ItemStack.EMPTY, BlockHitResult.miss(player.position(), facing, pos));
|
|
if (!level.getBlockState(pos).canBeReplaced(context)) {
|
|
pos = pos.relative(facing);
|
|
context = new BlockPlaceContext(player, InteractionHand.MAIN_HAND, ItemStack.EMPTY, BlockHitResult.miss(player.position(), facing, pos));
|
|
}
|
|
|
|
if (!level.getBlockState(pos).canBeReplaced(context)) {
|
|
level.playSound(null, player.getX(), player.getY(), player.getZ(), SoundEvents.LAVA_POP, SoundSource.PLAYERS, 0.5F, 0.5F);
|
|
return false;
|
|
}
|
|
|
|
Vec3 placementPos = Vec3.atBottomCenterOf(pos);
|
|
|
|
if (carry.isCarrying(CarryType.PLAYER)) {
|
|
Entity otherPlayer = carry.getCarryingPlayer(level);
|
|
player.ejectPassengers();
|
|
Services.PLATFORM.sendPacketToAllPlayers(Constants.PACKET_ID_START_RIDING_OTHER, new ClientboundStartRidingOtherPlayerPacket(player.getId(), otherPlayer.getId(), false), player.level());
|
|
carry.clear();
|
|
CarryOnDataManager.setCarryData(player, carry);
|
|
otherPlayer.teleportTo(placementPos.x, placementPos.y, placementPos.z);
|
|
player.swing(InteractionHand.MAIN_HAND, true);
|
|
player.removeEffect(MobEffects.SLOWNESS);
|
|
return true;
|
|
}
|
|
|
|
Entity entity = carry.getEntity(level);
|
|
entity.setPos(placementPos);
|
|
|
|
boolean doPlace = placementCallback == null || placementCallback.apply(placementPos, entity);
|
|
if (!doPlace)
|
|
return false;
|
|
|
|
if (carry.getActiveScript().isPresent()) {
|
|
ScriptEffects effects = carry.getActiveScript().get().scriptEffects();
|
|
String cmd = effects.commandPlace();
|
|
if (!cmd.isEmpty())
|
|
player.level().getServer().getCommands().performPrefixedCommand(player.level().getServer().createCommandSourceStack(), "/execute as " + player.getGameProfile().name() + " run " + cmd);
|
|
}
|
|
|
|
level.addFreshEntity(entity);
|
|
if (entity instanceof Mob mob)
|
|
mob.playAmbientSound();
|
|
|
|
player.swing(InteractionHand.MAIN_HAND, true);
|
|
carry.clear();
|
|
CarryOnDataManager.setCarryData(player, carry);
|
|
player.removeEffect(MobEffects.SLOWNESS);
|
|
return true;
|
|
}
|
|
|
|
public static void tryStackEntity(ServerPlayer player, Entity entityClicked)
|
|
{
|
|
if(!Constants.COMMON_CONFIG.settings.stackableEntities)
|
|
return;
|
|
|
|
CarryOnData carry = CarryOnDataManager.getCarryData(player);
|
|
if (!carry.isCarrying(CarryType.ENTITY) && !carry.isCarrying(CarryType.PLAYER))
|
|
return;
|
|
|
|
Level level = player.level();
|
|
Entity entityHeld;
|
|
if (carry.isCarrying(CarryType.ENTITY))
|
|
entityHeld = carry.getEntity(level);
|
|
else
|
|
entityHeld = player.getFirstPassenger();
|
|
|
|
if(entityHeld == null)
|
|
return;
|
|
|
|
double sizeHeldEntity = entityHeld.getBbHeight() * entityHeld.getBbWidth();
|
|
double distance = entityClicked.blockPosition().distSqr(player.blockPosition());
|
|
Entity lowestEntity = entityClicked.getRootVehicle();
|
|
int numPassengers = getPassengerCount(lowestEntity);
|
|
if (numPassengers < Constants.COMMON_CONFIG.settings.maxEntityStackLimit - 1) {
|
|
Entity topEntity = getTopPassenger(lowestEntity);
|
|
|
|
if (topEntity == entityHeld)
|
|
return;
|
|
|
|
if (ListHandler.isStackingPermitted(topEntity)) {
|
|
double sizeEntity = topEntity.getBbHeight() * topEntity.getBbWidth();
|
|
if (!Constants.COMMON_CONFIG.settings.entitySizeMattersStacking || sizeHeldEntity <= sizeEntity) {
|
|
if (topEntity instanceof Horse horse)
|
|
horse.setTamed(true);
|
|
|
|
if (distance < 6) {
|
|
double tempX = entityClicked.getX();
|
|
double tempY = entityClicked.getY();
|
|
double tempZ = entityClicked.getZ();
|
|
if (carry.isCarrying(CarryType.ENTITY)) {
|
|
entityHeld.setPos(tempX, tempY + 2.6, tempZ);
|
|
level.addFreshEntity(entityHeld);
|
|
entityHeld.teleportTo(tempX, tempY, tempZ);
|
|
}
|
|
entityHeld.startRiding(topEntity, false,false);
|
|
} else {
|
|
if (carry.isCarrying(CarryType.ENTITY)) {
|
|
entityHeld.setPos(entityClicked.getX(), entityClicked.getY(), entityClicked.getZ());
|
|
level.addFreshEntity(entityHeld);
|
|
}
|
|
entityHeld.startRiding(topEntity, false,false);
|
|
}
|
|
|
|
if (carry.getActiveScript().isPresent()) {
|
|
ScriptEffects effects = carry.getActiveScript().get().scriptEffects();
|
|
String cmd = effects.commandPlace();
|
|
if (!cmd.isEmpty())
|
|
player.level().getServer().getCommands().performPrefixedCommand(player.level().getServer().createCommandSourceStack(), "/execute as " + player.getGameProfile().name() + " run " + cmd);
|
|
}
|
|
|
|
player.swing(InteractionHand.MAIN_HAND, true);
|
|
carry.clear();
|
|
CarryOnDataManager.setCarryData(player, carry);
|
|
level.playSound(null, player.getX(), player.getY(), player.getZ(), SoundEvents.HORSE_SADDLE, SoundSource.PLAYERS, 0.5F, 1.5F);
|
|
player.removeEffect(MobEffects.SLOWNESS);
|
|
} else {
|
|
level.playSound(null, player.getX(), player.getY(), player.getZ(), SoundEvents.LAVA_POP, SoundSource.PLAYERS, 0.5F, 0.5F);
|
|
}
|
|
}
|
|
} else {
|
|
level.playSound(null, player.getX(), player.getY(), player.getZ(), SoundEvents.LAVA_POP, SoundSource.PLAYERS, 0.5F, 0.5F);
|
|
}
|
|
}
|
|
|
|
public static void placeCarriedOnDeath(ServerPlayer oldPlayer, ServerPlayer newPlayer, boolean died)
|
|
{
|
|
CarryOnData carry = CarryOnDataManager.getCarryData(oldPlayer);
|
|
if (oldPlayer.level().getGameRules().get(GameRules.KEEP_INVENTORY) || !died) {
|
|
if (!carry.isCarrying(CarryType.PLAYER)) {
|
|
CarryOnDataManager.setCarryData(newPlayer, carry);
|
|
newPlayer.getInventory().setSelectedSlot(oldPlayer.getInventory().getSelectedSlot());
|
|
return;
|
|
}
|
|
}
|
|
|
|
placeCarried(oldPlayer);
|
|
}
|
|
|
|
public static void placeCarried(ServerPlayer player)
|
|
{
|
|
CarryOnData carry = CarryOnDataManager.getCarryData(player);
|
|
if (carry.isCarrying(CarryType.ENTITY)) {
|
|
Entity entity = carry.getEntity(player.level());
|
|
entity.setPos(player.position());
|
|
player.level().addFreshEntity(entity);
|
|
} else if (carry.isCarrying(CarryType.BLOCK)) {
|
|
BlockPlaceContext context = new BlockPlaceContext(player, InteractionHand.MAIN_HAND, ItemStack.EMPTY, BlockHitResult.miss(Vec3.atCenterOf(player.blockPosition()), Direction.DOWN, player.blockPosition()));
|
|
BlockState state = getPlacementState(carry.getBlock(), player, context, player.blockPosition());
|
|
BlockPos pos = getDeathPlacementPos(state, player);
|
|
BlockEntity blockEntity = carry.getBlockEntity(pos, player.level().registryAccess());
|
|
player.level().setBlock(pos, state, 3);
|
|
if (blockEntity != null)
|
|
player.level().setBlockEntity(blockEntity);
|
|
} else if (carry.isCarrying(CarryType.PLAYER)) {
|
|
player.ejectPassengers();
|
|
}
|
|
carry.clear();
|
|
CarryOnDataManager.setCarryData(player, carry);
|
|
player.removeEffect(MobEffects.SLOWNESS);
|
|
}
|
|
|
|
private static BlockPos getDeathPlacementPos(BlockState state, ServerPlayer player)
|
|
{
|
|
BlockPos p = player.blockPosition();
|
|
|
|
int DISTANCE = 15;
|
|
|
|
List<BlockPos> potentialPositions = new ArrayList<>();
|
|
|
|
for (int j = 0; j < DISTANCE * 2; j++) {
|
|
for (int i = 0; i < DISTANCE * 2; i++) {
|
|
for (int k = 0; k < DISTANCE * 2; k++) {
|
|
int x = i % 2 == 0 ? i / 2 : -(i / 2);
|
|
int y = j % 2 == 0 ? j / 2 : -(j / 2);
|
|
int z = k % 2 == 0 ? k / 2 : -(k / 2);
|
|
potentialPositions.add(new BlockPos(p.getX() + x, p.getY() + y, p.getZ() + z));
|
|
}
|
|
}
|
|
}
|
|
|
|
potentialPositions.sort(Comparator.comparingDouble(posA -> player.distanceToSqr(posA.getCenter())));
|
|
|
|
for(BlockPos potential : potentialPositions)
|
|
{
|
|
BlockPlaceContext context = new BlockPlaceContext(player, InteractionHand.MAIN_HAND, ItemStack.EMPTY, BlockHitResult.miss(Vec3.atCenterOf(potential), Direction.DOWN, potential));
|
|
boolean canPlace = state.canSurvive(player.level(), potential) && player.level().getBlockState(potential).canBeReplaced(context) && player.level().isUnobstructed(state, potential, CollisionContext.of(player));
|
|
|
|
if (canPlace)
|
|
return potential;
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
private static int getPassengerCount(Entity entity)
|
|
{
|
|
int passengers = 0;
|
|
while (entity.isVehicle()) {
|
|
List<Entity> pass = entity.getPassengers();
|
|
if (!pass.isEmpty()) {
|
|
entity = pass.get(0);
|
|
passengers++;
|
|
}
|
|
}
|
|
|
|
return passengers;
|
|
}
|
|
|
|
private static Entity getTopPassenger(Entity entity)
|
|
{
|
|
Entity top = entity;
|
|
while (entity.isVehicle()) {
|
|
List<Entity> pass = entity.getPassengers();
|
|
if (!pass.isEmpty()) {
|
|
entity = pass.get(0);
|
|
top = entity;
|
|
}
|
|
}
|
|
|
|
return top;
|
|
}
|
|
|
|
}
|