Added final config options. Added gamestages support. Fixed rendering issues

This commit is contained in:
Tschipp 2022-12-05 00:17:48 +01:00
parent 375eb8dcc3
commit 191ce26ee4
46 changed files with 1347 additions and 423 deletions

View File

@ -2,6 +2,17 @@ package tschipp.carryon;
import com.mojang.brigadier.CommandDispatcher;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import tschipp.carryon.common.carry.CarryOnData;
import tschipp.carryon.common.carry.CarryOnData.CarryType;
import tschipp.carryon.common.carry.CarryOnDataManager;
import tschipp.carryon.common.carry.PlacementHandler;
import tschipp.carryon.common.command.CommandCarryOn;
import tschipp.carryon.config.ConfigLoader;
import tschipp.carryon.networking.clientbound.ClientboundStartRidingPacket;
@ -55,4 +66,98 @@ public class CarryOnCommon
CommandCarryOn.register(dispatcher);
}
public static void onCarryTick(ServerPlayer player)
{
CarryOnData carry = CarryOnDataManager.getCarryData(player);
if(carry.isCarrying())
{
if(carry.getActiveScript().isPresent())
{
String cmd = carry.getActiveScript().get().scriptEffects().commandLoop();
if(!cmd.isEmpty())
player.getServer().getCommands().performPrefixedCommand(player.getServer().createCommandSourceStack(), "/execute as " + player.getGameProfile().getName() + " run " + cmd);
}
if (!Constants.COMMON_CONFIG.settings.slownessInCreative && player.isCreative())
return;
player.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, 1, potionLevel(carry, player.level), false, false));
Inventory inv = player.getInventory();
inv.selected = carry.getSelected();
}
}
/**
* Returns true if the block can be broken.
*/
public static boolean onTryBreakBlock(Player player)
{
if (player != null && !Constants.COMMON_CONFIG.settings.hitWhileCarrying)
{
CarryOnData carry = CarryOnDataManager.getCarryData(player);
if(carry.isCarrying())
return false;
}
return true;
}
/**
* Returns true of the entity can be attacked
*/
public static boolean onAttackedByPlayer(Player player)
{
if (player != null && !Constants.COMMON_CONFIG.settings.hitWhileCarrying)
{
CarryOnData carry = CarryOnDataManager.getCarryData(player);
if(carry.isCarrying())
return false;
}
return true;
}
public static void onPlayerAttacked(Player player)
{
if (Constants.COMMON_CONFIG.settings.dropCarriedWhenHit && !player.level.isClientSide)
{
CarryOnData carry = CarryOnDataManager.getCarryData(player);
if (carry.isCarrying())
{
PlacementHandler.placeCarried((ServerPlayer) player);
}
}
}
private static int potionLevel(CarryOnData carry, Level level)
{
if(carry.isCarrying(CarryType.PLAYER))
return 1;
if(carry.isCarrying(CarryType.ENTITY))
{
Entity entity = carry.getEntity(level);
int i = (int) (entity.getBbHeight() * entity.getBbWidth());
if (i > 4)
i = 4;
if (!Constants.COMMON_CONFIG.settings.heavyEntities)
i = 1;
return (int) (i * Constants.COMMON_CONFIG.settings.entitySlownessMultiplier);
}
if(carry.isCarrying(CarryType.BLOCK))
{
String nbt = carry.getNbt().toString();
int i = nbt.length() / 500;
if (i > 4)
i = 4;
if (!Constants.COMMON_CONFIG.settings.heavyTiles)
i = 1;
return (int) (i * Constants.COMMON_CONFIG.settings.blockSlownessMultiplier);
}
return 0;
}
}

View File

@ -10,14 +10,15 @@ public class CarryOnCommonClient
{
public static void checkForKeybinds()
{
Player player = Minecraft.getInstance().player;
Minecraft mc = Minecraft.getInstance();
Player player = mc.player;
if(player != null) {
CarryOnData carry = CarryOnDataManager.getCarryData(player);
if (CarryOnKeybinds.carryKey.isDown() && !carry.isKeyPressed()) {
if ((CarryOnKeybinds.carryKey.isUnbound() ? player.isShiftKeyDown() : CarryOnKeybinds.carryKey.isDown()) && !carry.isKeyPressed()) {
CarryOnKeybinds.onCarryKey(true);
carry.setKeyPressed(true);
CarryOnDataManager.setCarryData(player, carry);
} else if (!CarryOnKeybinds.carryKey.isDown() && carry.isKeyPressed()) {
} else if (!(CarryOnKeybinds.carryKey.isUnbound() ? player.isShiftKeyDown() : CarryOnKeybinds.carryKey.isDown()) && carry.isKeyPressed()) {
CarryOnKeybinds.onCarryKey(false);
carry.setKeyPressed(false);
CarryOnDataManager.setCarryData(player, carry);
@ -25,4 +26,20 @@ public class CarryOnCommonClient
}
}
public static void onCarryClientTick()
{
Player player = Minecraft.getInstance().player;
if(player != null) {
CarryOnData carry = CarryOnDataManager.getCarryData(player);
if(carry.isCarrying())
{
player.getInventory().selected = carry.getSelected();
}
}
}
public static Player getPlayer()
{
return Minecraft.getInstance().player;
}
}

View File

@ -1,12 +0,0 @@
package tschipp.carryon.client;
import net.minecraft.client.Minecraft;
import net.minecraft.world.entity.player.Player;
public class CarryOnClient
{
public static Player getPlayer()
{
return Minecraft.getInstance().player;
}
}

View File

@ -1,5 +1,6 @@
package tschipp.carryon.client.keybinds;
import com.mojang.blaze3d.platform.InputConstants;
import net.minecraft.client.KeyMapping;
import tschipp.carryon.Constants;
import tschipp.carryon.networking.serverbound.ServerboundCarryKeyPressedPacket;
@ -13,7 +14,7 @@ public class CarryOnKeybinds
public static void registerKeybinds(Consumer<KeyMapping> registrar)
{
carryKey = new KeyMapping("key.carry.desc", 340, "key.carry.category");
carryKey = new ConflictFreeKeyMapping("key.carry.desc", Services.PLATFORM.getPlatformName().equals("Forge") ? InputConstants.KEY_LSHIFT : InputConstants.UNKNOWN.getValue(), "key.carry.category");
registrar.accept(carryKey);
}

View File

@ -0,0 +1,23 @@
package tschipp.carryon.client.keybinds;
import com.mojang.blaze3d.platform.InputConstants.Type;
import net.minecraft.client.KeyMapping;
public class ConflictFreeKeyMapping extends KeyMapping
{
public ConflictFreeKeyMapping(String $$0, int $$1, String $$2)
{
super($$0, $$1, $$2);
}
public ConflictFreeKeyMapping(String $$0, Type $$1, int $$2, String $$3)
{
super($$0, $$1, $$2, $$3);
}
@Override
public boolean same(KeyMapping $$0)
{
return false;
}
}

View File

@ -0,0 +1,125 @@
package tschipp.carryon.client.modeloverride;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.datafixers.util.Either;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import net.minecraft.commands.arguments.blocks.BlockStateParser;
import net.minecraft.commands.arguments.blocks.BlockStateParser.BlockResult;
import net.minecraft.commands.arguments.item.ItemParser;
import net.minecraft.commands.arguments.item.ItemParser.ItemResult;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.Registry;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.Property;
import tschipp.carryon.common.scripting.Matchables.NBTCondition;
import javax.annotation.Nullable;
import java.util.Map;
public class ModelOverride
{
public static Codec<ModelOverride> CODEC = Codec.STRING.comapFlatMap(ModelOverride::of, override -> override.raw);
private String raw;
private BlockResult parsedBlock;
private Type type;
private Either<ItemResult, BlockResult> parsedRHS;
private Either<ItemStack, BlockState> renderObject;
private ModelOverride(String raw, BlockResult parsedBlock, Type type, Either<ItemResult, BlockResult> parsedRHS)
{
this.raw = raw;
this.parsedBlock = parsedBlock;
this.type = type;
this.parsedRHS = parsedRHS;
parsedRHS.ifLeft(res -> {
ItemStack stack = new ItemStack(res.item());
if(res.nbt() != null)
stack.setTag(res.nbt());
this.renderObject = Either.left(stack);
});
parsedRHS.ifRight(res -> {
BlockState state = res.blockState();
this.renderObject = Either.right(state);
});
}
public static DataResult<ModelOverride> of(String str)
{
if(!str.contains("->"))
return DataResult.error(str + " must contain -> Arrow!");
String[] split = str.split("->");
String from = split[0];
String to = split[1];
BlockResult res;
try {
res = BlockStateParser.parseForBlock(Registry.BLOCK, from, true);
} catch (CommandSyntaxException e) {
return DataResult.error("Error while parsing " + from + ":" + e.getMessage());
}
Type type = Type.ITEM;
if(to.contains("(") && to.contains(")"))
{
String t = to.substring(to.indexOf("(") + 1, to.indexOf(")"));
if(t.equals("block"))
type = Type.BLOCK;
to = to.substring(to.indexOf(")") + 1);
}
Either<ItemResult, BlockResult> either;
try {
if(type == Type.ITEM)
either = Either.left(ItemParser.parseForItem(HolderLookup.forRegistry(Registry.ITEM), new StringReader(to)));
else
either = Either.right(BlockStateParser.parseForBlock(Registry.BLOCK, to, true));
}catch (CommandSyntaxException e) {
return DataResult.error("Error while parsing " + to + ":" + e.getMessage());
}
return DataResult.success(new ModelOverride(str, res, type, either));
}
public boolean matches(BlockState state, @Nullable CompoundTag tag)
{
if(state.getBlock() == parsedBlock.blockState().getBlock() && matchesProperties(state, parsedBlock.properties()))
{
if(tag == null || parsedBlock.nbt() == null)
return true;
NBTCondition nbt = new NBTCondition(parsedBlock.nbt());
return nbt.matches(tag);
}
return false;
}
public Either<ItemStack, BlockState> getRenderObject()
{
return this.renderObject;
}
private boolean matchesProperties(BlockState state, Map<Property<?>, Comparable<?>> props)
{
for(var entry : props.entrySet())
{
var val = state.getValue(entry.getKey());
if(val != entry.getValue())
return false;
}
return true;
}
public enum Type {
ITEM,
BLOCK
}
}

View File

@ -0,0 +1,51 @@
package tschipp.carryon.client.modeloverride;
import com.mojang.serialization.DataResult;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.block.state.BlockState;
import tschipp.carryon.Constants;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class ModelOverrideHandler
{
private static List<ModelOverride> OVERRIDES = new ArrayList<>();
public static void initModelOverrides()
{
OVERRIDES.clear();
for(String ov : Constants.CLIENT_CONFIG.modelOverrides)
{
addFromString(ov);
}
}
public static Optional<ModelOverride> getModelOverride(BlockState state, @Nullable CompoundTag tag)
{
for(ModelOverride ov : OVERRIDES)
{
if(ov.matches(state, tag))
return Optional.of(ov);
}
return Optional.empty();
}
public static void addFromString(String str)
{
DataResult<ModelOverride> res = ModelOverride.of(str);
if(res.result().isPresent())
{
ModelOverride override = res.result().get();
OVERRIDES.add(override);
}
else
{
Constants.LOG.debug("Error while parsing ModelOverride: " + res.error().get().message());
}
}
}

View File

@ -61,7 +61,7 @@ public class CarriedObjectRender
CarryOnData carry = CarryOnDataManager.getCarryData(player);
if (!Constants.CLIENT_CONFIG.facePlayer && CarryRenderHelper.isChest(state.getBlock())) {
if (Constants.CLIENT_CONFIG.facePlayer != CarryRenderHelper.isChest(state.getBlock())) {
matrix.mulPose(Vector3f.YP.rotationDegrees(180));
matrix.mulPose(Vector3f.XN.rotationDegrees(8));
} else {

View File

@ -24,12 +24,16 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.VoxelShape;
import tschipp.carryon.Constants;
import tschipp.carryon.client.modeloverride.ModelOverride;
import tschipp.carryon.client.modeloverride.ModelOverrideHandler;
import tschipp.carryon.common.carry.CarryOnData;
import tschipp.carryon.common.carry.CarryOnData.CarryType;
import tschipp.carryon.common.carry.CarryOnDataManager;
import tschipp.carryon.common.scripting.CarryOnScript;
import tschipp.carryon.common.scripting.CarryOnScript.ScriptRender;
import java.util.Optional;
public class CarryRenderHelper
{
public static Vec3 getExactPos(Entity entity, float partialticks)
@ -139,8 +143,9 @@ public class CarryRenderHelper
applyGeneralTransformations(player, partialticks, matrix);
if (!Constants.CLIENT_CONFIG.facePlayer && isChest(block))
if (Constants.CLIENT_CONFIG.facePlayer != CarryRenderHelper.isChest(block))
{
//TODO: RealFirstPersonRender
//if ((ModList.get().isLoaded("realrender") || ModList.get().isLoaded("rfpr")) && perspective == 0)
// matrix.translate(0, 0, -0.4);
matrix.mulPose(Vector3f.YP.rotationDegrees(180));
@ -149,6 +154,8 @@ public class CarryRenderHelper
// matrix.translate(0, 0, 0.4);
//matrix.mulPose(Vector3f.YP.rotationDegrees(180));
float height = getRenderHeight(player);
float offset = (height - 1f) / 1.2f;
matrix.translate(0, -offset, 0);
@ -202,7 +209,7 @@ public class CarryRenderHelper
Vec3 translation = render.renderTranslation().getVec();
Vec3 rotation = render.renderRotation().getVec();
Vec3 scale = render.renderscale().getVec(1);
Vec3 scale = render.renderscale().getVec(1, 1, 1);
Quaternion rot = Vector3f.XP.rotationDegrees((float) rotation.x);
rot.mul(Vector3f.YP.rotationDegrees((float) rotation.y));
@ -232,18 +239,36 @@ public class CarryRenderHelper
if(render.renderNameBlock().isPresent())
state = Registry.BLOCK.get(render.renderNameBlock().get()).defaultBlockState();
}
Optional<ModelOverride> ov = ModelOverrideHandler.getModelOverride(state, carry.getContentNbt());
if(ov.isPresent())
{
var renderObj = ov.get().getRenderObject();
if(renderObj.right().isPresent())
state = renderObj.right().get();
}
return state;
}
public static BakedModel getRenderBlock(Player player)
{
CarryOnData carry = CarryOnDataManager.getCarryData(player);
ItemRenderer renderer = Minecraft.getInstance().getItemRenderer();
BlockState state = getRenderState(player);
BakedModel model = Minecraft.getInstance().getBlockRenderer().getBlockModel(state);
if(state.getRenderShape() != RenderShape.MODEL || model.isCustomRenderer() || model.getQuads(state, null, RandomSource.create()).size() <= 0) {
ItemStack stack = new ItemStack(state.getBlock());
model = Minecraft.getInstance().getItemRenderer().getModel(stack, player.level, player, 0);
model = renderer.getModel(stack, player.level, player, 0);
}
Optional<ModelOverride> ov = ModelOverrideHandler.getModelOverride(state, carry.getContentNbt());
if(ov.isPresent())
{
var renderObj = ov.get().getRenderObject();
if(renderObj.left().isPresent())
model = renderer.getModel(renderObj.left().get(), player.level, player, 0);
}
return model;
@ -273,9 +298,17 @@ public class CarryRenderHelper
CarryOnData carry = CarryOnDataManager.getCarryData(player);
if(carry.isCarrying(CarryType.BLOCK))
{
VoxelShape shape = getRenderState(player).getShape(player.level, player.blockPosition());
BlockState state = getRenderState(player);
VoxelShape shape = state.getShape(player.level, player.blockPosition());
if(shape == null)
return 1f;
Optional<ModelOverride> ov = ModelOverrideHandler.getModelOverride(state, carry.getContentNbt());
if(ov.isPresent())
{
var renderObj = ov.get().getRenderObject();
if(renderObj.left().isPresent())
return 0.8f;
}
float width = (float)Math.abs(shape.bounds().maxX - shape.bounds().minX);
return width;
}
@ -293,9 +326,17 @@ public class CarryRenderHelper
CarryOnData carry = CarryOnDataManager.getCarryData(player);
if(carry.isCarrying(CarryType.BLOCK))
{
VoxelShape shape = getRenderState(player).getShape(player.level, player.blockPosition());
BlockState state = getRenderState(player);
VoxelShape shape = state.getShape(player.level, player.blockPosition());
if(shape == null)
return 1f;
Optional<ModelOverride> ov = ModelOverrideHandler.getModelOverride(state, carry.getContentNbt());
if(ov.isPresent())
{
var renderObj = ov.get().getRenderObject();
if(renderObj.left().isPresent())
return 0.5f;
}
float height = (float)Math.abs(shape.bounds().maxY - shape.bounds().minY);
return height;
}

View File

@ -23,6 +23,7 @@ public class CarryOnData {
private CompoundTag nbt;
private boolean keyPressed = false;
private CarryOnScript activeScript;
private int selectedSlot = 0;
public CarryOnData(CompoundTag data)
{
@ -41,6 +42,10 @@ public class CarryOnData {
DataResult<CarryOnScript> res = CarryOnScript.CODEC.parse(NbtOps.INSTANCE, data.get("activeScript"));
this.activeScript = res.getOrThrow(false, (s) -> {throw new RuntimeException("Failed to decode activeScript during CarryOnData serialization: " + s);});
}
if(data.contains("selected"))
this.selectedSlot = data.getInt("selected");
}
public CompoundTag getNbt()
@ -53,9 +58,19 @@ public class CarryOnData {
Tag tag = res.getOrThrow(false, (s) -> {throw new RuntimeException("Failed to encode activeScript during CarryOnData serialization: " + s);});
nbt.put("activeScript", tag);
}
nbt.putInt("selected", this.selectedSlot);
return nbt;
}
public CompoundTag getContentNbt()
{
if(type == CarryType.BLOCK && nbt.contains("block"))
return nbt.getCompound("block");
else if(type == CarryType.ENTITY && nbt.contains("entity"))
return nbt.getCompound("entity");
return null;
}
public void setBlock(BlockState state, @Nullable BlockEntity tile)
{
this.type = CarryType.BLOCK;
@ -142,6 +157,14 @@ public class CarryOnData {
this.nbt.putBoolean("keyPressed", val);
}
public void setSelected(int selectedSlot) {
this.selectedSlot = selectedSlot;
}
public int getSelected() {
return this.selectedSlot;
}
public void clear()
{
this.type = CarryType.INVALID;

View File

@ -19,6 +19,7 @@ public class CarryOnDataManager {
public static void setCarryData(Player player, CarryOnData data)
{
data.setSelected(player.getInventory().selected);
CompoundTag nbt = data.getNbt();
nbt.putInt("tick", player.tickCount);
player.getEntityData().set(CARRY_DATA_KEY, nbt);

View File

@ -19,13 +19,19 @@ import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import tschipp.carryon.Constants;
import tschipp.carryon.common.config.ListHandler;
import tschipp.carryon.common.pickupcondition.PickupCondition;
import tschipp.carryon.common.pickupcondition.PickupConditionHandler;
import tschipp.carryon.common.scripting.CarryOnScript;
import tschipp.carryon.common.scripting.ScriptManager;
import tschipp.carryon.networking.clientbound.ClientboundStartRidingPacket;
import tschipp.carryon.platform.Services;
import javax.annotation.Nullable;
import java.util.Optional;
import java.util.UUID;
import java.util.function.BiFunction;
import java.util.function.Function;
public class PickupHandler {
@ -57,7 +63,7 @@ public class PickupHandler {
}
public static boolean tryPickUpBlock(ServerPlayer player, BlockPos pos, Level level)
public static boolean tryPickUpBlock(ServerPlayer player, BlockPos pos, Level level, @Nullable BiFunction<BlockState, BlockPos, Boolean> pickupCallback)
{
if(!canCarryGeneral(player, Vec3.atCenterOf(pos)))
return false;
@ -85,9 +91,16 @@ public class PickupHandler {
return false;
}
//TODO: Gamestages conditions check
Optional<PickupCondition> cond = PickupConditionHandler.getPickupCondition(state);
if(cond.isPresent())
{
if(!cond.get().isFulfilled(player))
return false;
}
//TODO: Protections
boolean doPickup = pickupCallback == null ? true : pickupCallback.apply(state, pos);
if(!doPickup)
return false;
Optional<CarryOnScript> result = ScriptManager.inspectBlock(state, level, pos, nbt);
if(result.isPresent())
@ -116,7 +129,7 @@ public class PickupHandler {
public static boolean tryPickupEntity(ServerPlayer player, Entity entity)
public static boolean tryPickupEntity(ServerPlayer player, Entity entity, @Nullable Function<Entity, Boolean> pickupCallback)
{
if(!canCarryGeneral(player, entity.position()))
return false;
@ -149,10 +162,16 @@ public class PickupHandler {
return false;
}
//TODO: Gamestages conditions check
//TODO: Protections
Optional<PickupCondition> cond = PickupConditionHandler.getPickupCondition(entity);
if(cond.isPresent())
{
if(!cond.get().isFulfilled(player))
return false;
}
boolean doPickup = pickupCallback == null ? true : pickupCallback.apply(entity);
if(!doPickup)
return false;
CarryOnData carry = CarryOnDataManager.getCarryData(player);
@ -214,18 +233,4 @@ public class PickupHandler {
return true;
}
public static void onCarryTick(ServerPlayer player)
{
CarryOnData carry = CarryOnDataManager.getCarryData(player);
if(carry.isCarrying())
{
if(carry.getActiveScript().isPresent())
{
String cmd = carry.getActiveScript().get().scriptEffects().commandLoop();
if(!cmd.isEmpty())
player.getServer().getCommands().performPrefixedCommand(player.getServer().createCommandSourceStack(), "/execute as " + player.getGameProfile().getName() + " run " + cmd);
}
}
}
}

View File

@ -1,6 +1,7 @@
package tschipp.carryon.common.carry;
import net.minecraft.core.BlockPos;
import net.minecraft.core.BlockPos.MutableBlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents;
@ -11,6 +12,7 @@ import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.animal.horse.Horse;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
@ -23,6 +25,7 @@ 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 javax.annotation.Nullable;
@ -53,30 +56,30 @@ public class PlacementHandler
BlockEntity blockEntity = carry.getBlockEntity(pos);
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)
{
if (!canPlace) {
level.playSound(null, player.getX(), player.getY(), player.getZ(), SoundEvents.LAVA_POP, SoundSource.PLAYERS, 0.5F, 0.5F);
return false;
}
state = getPlacementState(state, player, context, pos);
boolean doPlace = placementCallback == null || placementCallback.apply(pos, state);
if (!doPlace)
return false;
if(carry.getActiveScript().isPresent())
{
if (carry.getActiveScript().isPresent()) {
ScriptEffects effects = carry.getActiveScript().get().scriptEffects();
String cmd = effects.commandPlace();
if(!cmd.isEmpty())
if (!cmd.isEmpty())
player.getServer().getCommands().performPrefixedCommand(player.getServer().createCommandSourceStack(), "/execute as " + player.getGameProfile().getName() + " run " + cmd);
}
level.setBlock(pos, state, 3);
level.setBlockAndUpdate(pos, state);
if (blockEntity != null)
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);
@ -88,24 +91,25 @@ public class PlacementHandler
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 instanceof DirectionProperty) {
if (prop instanceof DirectionProperty)
state = updateProperty(state, placementState, prop);
}
if (prop.getValueClass() == Direction.Axis.class) {
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);
}
}
state = Block.updateFromNeighbourShapes(state, player.level, pos);
BlockState updatedState = Block.updateFromNeighbourShapes(state, player.level, pos);
if (updatedState.getBlock() == state.getBlock())
state = updatedState;
if (placementState.hasProperty(BlockStateProperties.WATERLOGGED))
if (placementState.hasProperty(BlockStateProperties.WATERLOGGED) && state.hasProperty(BlockStateProperties.WATERLOGGED))
state = state.setValue(BlockStateProperties.WATERLOGGED, placementState.getValue(BlockStateProperties.WATERLOGGED));
return state;
@ -135,13 +139,12 @@ public class PlacementHandler
Vec3 placementPos = Vec3.atBottomCenterOf(pos);
if(carry.isCarrying(CarryType.PLAYER))
{
if (carry.isCarrying(CarryType.PLAYER)) {
Entity otherPlayer = player.getFirstPassenger();
player.ejectPassengers();
carry.clear();
CarryOnDataManager.setCarryData(player, carry);
if(otherPlayer == null)
if (otherPlayer == null)
return true;
otherPlayer.teleportTo(placementPos.x, placementPos.y, placementPos.z);
player.swing(InteractionHand.MAIN_HAND, true);
@ -155,33 +158,35 @@ public class PlacementHandler
if (!doPlace)
return false;
if(carry.getActiveScript().isPresent())
{
if (carry.getActiveScript().isPresent()) {
ScriptEffects effects = carry.getActiveScript().get().scriptEffects();
String cmd = effects.commandPlace();
if(!cmd.isEmpty())
if (!cmd.isEmpty())
player.getServer().getCommands().performPrefixedCommand(player.getServer().createCommandSourceStack(), "/execute as " + player.getGameProfile().getName() + " run " + cmd);
}
level.addFreshEntity(entity);
if(entity instanceof Mob mob)
mob.playAmbientSound();
level.addFreshEntity(entity);
if (entity instanceof Mob mob)
mob.playAmbientSound();
player.swing(InteractionHand.MAIN_HAND, true);
carry.clear();
CarryOnDataManager.setCarryData(player, carry);
return true;
player.swing(InteractionHand.MAIN_HAND, true);
carry.clear();
CarryOnDataManager.setCarryData(player, carry);
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))
if (!carry.isCarrying(CarryType.ENTITY) && !carry.isCarrying(CarryType.PLAYER))
return;
Level level = player.level;
Entity entityHeld;
if(carry.isCarrying(CarryType.ENTITY))
if (carry.isCarrying(CarryType.ENTITY))
entityHeld = carry.getEntity(level);
else
entityHeld = player.getFirstPassenger();
@ -191,47 +196,40 @@ public class PlacementHandler
double distance = entityClicked.blockPosition().distSqr(player.blockPosition());
Entity lowestEntity = entityClicked.getRootVehicle();
int numPassengers = getPassengerCount(lowestEntity);
if (numPassengers < Constants.COMMON_CONFIG.settings.maxEntityStackLimit - 1)
{
if (numPassengers < Constants.COMMON_CONFIG.settings.maxEntityStackLimit - 1) {
Entity topEntity = getTopPassenger(lowestEntity);
if(topEntity == entityHeld)
if (topEntity == entityHeld)
return;
if (ListHandler.isStackingPermitted(topEntity))
{
if (ListHandler.isStackingPermitted(topEntity)) {
double sizeEntity = topEntity.getBbHeight() * topEntity.getBbWidth();
if (Constants.COMMON_CONFIG.settings.entitySizeMattersStacking && sizeHeldEntity <= sizeEntity || !Constants.COMMON_CONFIG.settings.entitySizeMattersStacking)
{
if (Constants.COMMON_CONFIG.settings.entitySizeMattersStacking && sizeHeldEntity <= sizeEntity || !Constants.COMMON_CONFIG.settings.entitySizeMattersStacking) {
if (topEntity instanceof Horse horse)
horse.setTamed(true);
if (distance < 6)
{
if (distance < 6) {
double tempX = entityClicked.getX();
double tempY = entityClicked.getY();
double tempZ = entityClicked.getZ();
if(carry.isCarrying(CarryType.ENTITY)) {
if (carry.isCarrying(CarryType.ENTITY)) {
entityHeld.setPos(tempX, tempY + 2.6, tempZ);
level.addFreshEntity(entityHeld);
entityHeld.teleportTo(tempX, tempY, tempZ);
}
entityHeld.startRiding(topEntity, false);
}
else
{
if(carry.isCarrying(CarryType.ENTITY)) {
} else {
if (carry.isCarrying(CarryType.ENTITY)) {
entityHeld.setPos(entityClicked.getX(), entityClicked.getY(), entityClicked.getZ());
level.addFreshEntity(entityHeld);
}
entityHeld.startRiding(topEntity, false);
}
if(carry.getActiveScript().isPresent())
{
if (carry.getActiveScript().isPresent()) {
ScriptEffects effects = carry.getActiveScript().get().scriptEffects();
String cmd = effects.commandPlace();
if(!cmd.isEmpty())
if (!cmd.isEmpty())
player.getServer().getCommands().performPrefixedCommand(player.getServer().createCommandSourceStack(), "/execute as " + player.getGameProfile().getName() + " run " + cmd);
}
@ -239,27 +237,86 @@ public class PlacementHandler
carry.clear();
CarryOnDataManager.setCarryData(player, carry);
level.playSound(null, player.getX(), player.getY(), player.getZ(), SoundEvents.HORSE_SADDLE, SoundSource.PLAYERS, 0.5F, 1.5F);
}
else
{
} else {
level.playSound(null, player.getX(), player.getY(), player.getZ(), SoundEvents.LAVA_POP, SoundSource.PLAYERS, 0.5F, 0.5F);
}
}
}
else
{
} 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().getBoolean(GameRules.RULE_KEEPINVENTORY) || !died) {
if (!carry.isCarrying(CarryType.PLAYER)) {
CarryOnDataManager.setCarryData(newPlayer, carry);
newPlayer.getInventory().selected = oldPlayer.getInventory().selected;
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.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);
}
private static BlockPos getDeathPlacementPos(BlockState state, ServerPlayer player)
{
MutableBlockPos pos = new MutableBlockPos();
BlockPos p = player.blockPosition();
int DISTANCE = 15;
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);
pos.set(p.getX() + x, p.getY() + y, p.getZ() + z);
BlockPlaceContext context = new BlockPlaceContext(player, InteractionHand.MAIN_HAND, ItemStack.EMPTY, BlockHitResult.miss(Vec3.atCenterOf(pos), Direction.DOWN, pos));
boolean canPlace = state.canSurvive(player.level, pos) && player.level.getBlockState(pos).canBeReplaced(context) && player.level.isUnobstructed(state, pos, CollisionContext.of(player));
if (canPlace)
return pos;
}
}
}
return p;
}
private static int getPassengerCount(Entity entity)
{
int passengers = 0;
while (entity.isVehicle())
{
while (entity.isVehicle()) {
List<Entity> pass = entity.getPassengers();
if (!pass.isEmpty())
{
if (!pass.isEmpty()) {
entity = pass.get(0);
passengers++;
}
@ -271,11 +328,9 @@ public class PlacementHandler
private static Entity getTopPassenger(Entity entity)
{
Entity top = entity;
while (entity.isVehicle())
{
while (entity.isVehicle()) {
List<Entity> pass = entity.getPassengers();
if (!pass.isEmpty())
{
if (!pass.isEmpty()) {
entity = pass.get(0);
top = entity;
}

View File

@ -8,13 +8,20 @@ import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.block.state.BlockState;
import tschipp.carryon.Constants;
import tschipp.carryon.client.modeloverride.ModelOverride;
import tschipp.carryon.client.modeloverride.ModelOverrideHandler;
import tschipp.carryon.common.carry.CarryOnData;
import tschipp.carryon.common.carry.CarryOnData.CarryType;
import tschipp.carryon.common.carry.CarryOnDataManager;
import tschipp.carryon.common.pickupcondition.PickupCondition;
import tschipp.carryon.common.pickupcondition.PickupConditionHandler;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
public class CommandCarryOn
{
@ -45,27 +52,32 @@ public class CommandCarryOn
CarryOnData carry = CarryOnDataManager.getCarryData(player);
if (carry.isCarrying(CarryType.BLOCK))
{
log(source,"Block: " + carry.getBlock().getBlock());
log(source,"BlockState: " + carry.getBlock());
BlockState block = carry.getBlock();
log(source,"Block: " + block.getBlock());
log(source,"BlockState: " + block);
log(source,"NBT: " + carry.getNbt());
//TODO
// if (ModelOverridesHandler.hasCustomOverrideModel(ItemCarryonBlock.getBlockState(main), ItemCarryonBlock.getTileData(main)))
// source.sendSuccess(Component.literal("Override Model: " + ModelOverridesHandler.getOverrideObject(ItemCarryonBlock.getBlockState(main), ItemCarryonBlock.getTileData(main))), true);
Optional<ModelOverride> ov = ModelOverrideHandler.getModelOverride(block, carry.getContentNbt());
if(ov.isPresent())
log(source, "Override Model: " + ov.get().getRenderObject());
Optional<PickupCondition> cond = PickupConditionHandler.getPickupCondition(block);
if(cond.isPresent())
log(source, "Custom Pickup Condition: " + cond.get().getCondition());
// if (CustomPickupOverrideHandler.hasSpecialPickupConditions(ItemCarryonBlock.getBlockState(main)))
// source.sendSuccess(Component.literal("Custom Pickup Condition: " + CustomPickupOverrideHandler.getPickupCondition(ItemCarryonBlock.getBlockState(main))), true);
return 1;
}
else if (carry.isCarrying(CarryType.ENTITY))
{
log(source,"Entity: " + carry.getEntity(player.level));
log(source,"Entity Name: " + carry.getEntity(player.level).getType());
Entity entity = carry.getEntity(player.level);
log(source,"Entity: " + entity);
log(source,"Entity Name: " + entity.getType());
log(source,"NBT: " + carry.getNbt());
// if (CustomPickupOverrideHandler.hasSpecialPickupConditions(ItemCarryonEntity.getEntity(main, player.level)))
// source.sendSuccess(Component.literal("Custom Pickup Condition: " + CustomPickupOverrideHandler.getPickupCondition(ItemCarryonEntity.getEntity(main, player.level))), true);
Optional<PickupCondition> cond = PickupConditionHandler.getPickupCondition(entity);
if(cond.isPresent())
log(source, "Custom Pickup Condition: " + cond.get().getCondition());
return 1;
}

View File

@ -151,7 +151,9 @@ public class CarryConfig {
description = "Usually all the block state information is retained when placing a block that was picked up. But some information is changed to a modified property, like rotation or orientation. In this list, add additional properties that should NOT be saved and instead be updated when placed. Format: modid:block[propertyname]. Note: You don't need to add an entry for every subtype of a same block. For example, we only add an entry for one type of slab, but the change is applied to all slabs."
)
public String[] placementStateExceptions = {
"minecraft:chest[type]"
"minecraft:chest[type]",
"minecraft:stone_button[face]",
"minecraft:vine[north,east,south,west,up]"
};
@Property(
@ -291,26 +293,8 @@ public class CarryConfig {
description = "Model Overrides based on NBT or Meta. Advanced users only! Read about the format here: https://github.com/Tschipp/CarryOn/wiki/Model-Override-Config"
)
public String[] modelOverrides = {
"minecraft:hopper->(block)minecraft:hopper",
"minecraft:comparator->(block)minecraft:comparator",
"minecraft:repeater->(block)minecraft:repeater",
"minecraft:cauldron->(block)minecraft:cauldron",
"minecraft:brewing_stand->(item)minecraft:brewing_stand",
"minecraft:flower_pot->(block)minecraft:flower_pot",
"minecraft:sugar_cane->(block)minecraft:sugar_cane",
"minecraft:redstone_wire->(item)minecraft:redstone",
"animania:block_nest->(block)animania:block_nest",
"animania:cheese_mold;0->(block)animania:cheese_mold;0",
"animania:cheese_mold;1->(block)animania:cheese_mold;1",
"animania:cheese_mold;2->(block)animania:cheese_mold;2",
"animania:cheese_mold;3->(block)animania:cheese_mold;3",
"animania:cheese_mold;4->(block)animania:cheese_mold;4",
"animania:cheese_mold;5->(block)animania:cheese_mold;5",
"animania:cheese_mold;6->(block)animania:cheese_mold;6",
"animania:cheese_mold;7->(block)animania:cheese_mold;7",
"animania:cheese_mold;8->(block)animania:cheese_mold;8",
"animania:cheese_mold;9->(block)animania:cheese_mold;9",
"animania:cheese_mold;10->(block)animania:cheese_mold;10"
"minecraft:bamboo_sapling->(block)minecraft:bamboo"
};
}

View File

@ -1,4 +1,4 @@
package tschipp.carryon.common.carry;
package tschipp.carryon.common.config;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceLocation;
@ -8,6 +8,7 @@ import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.properties.Property;
import tschipp.carryon.Constants;
import tschipp.carryon.utils.StringHelper;
import java.util.*;
import java.util.stream.Collectors;
@ -131,12 +132,13 @@ public class ListHandler {
if(!propString.contains("[") || !propString.contains("]"))
continue;
String name = propString.substring(0, propString.indexOf("["));
String propName = propString.substring(propString.indexOf("[") + 1, propString.indexOf("]"));
String props = propString.substring(propString.indexOf("[") + 1, propString.indexOf("]"));
Block blk = Registry.BLOCK.get(new ResourceLocation(name));
for(Property<?> prop : blk.defaultBlockState().getProperties())
{
if(prop.getName().equals(propName))
PROPERTY_EXCEPTION_CLASSES.add(prop.getValueClass());
for(String propName : props.split(",")) {
for (Property<?> prop : blk.defaultBlockState().getProperties()) {
if (prop.getName().equals(propName))
PROPERTY_EXCEPTION_CLASSES.add(prop.getValueClass());
}
}
}
@ -164,6 +166,7 @@ public class ListHandler {
for (ResourceLocation key : keys)
{
if (containsAll(key.toString(), filter))
{
toAddTo.add(key.toString());
@ -180,15 +183,7 @@ public class ListHandler {
public static boolean containsAll(String str, String... strings)
{
boolean containsAll = true;
for (String s : strings)
{
if (!str.contains(s))
containsAll = false;
}
return containsAll;
return StringHelper.matchesWildcards(str, strings);
}
public static void addForbiddenTiles(String toAdd)

View File

@ -0,0 +1,123 @@
package tschipp.carryon.common.pickupcondition;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import net.minecraft.commands.arguments.blocks.BlockStateParser;
import net.minecraft.commands.arguments.blocks.BlockStateParser.BlockResult;
import net.minecraft.core.Registry;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.Property;
import tschipp.carryon.platform.Services;
import tschipp.carryon.utils.StringHelper;
import java.util.Map;
public class PickupCondition
{
public static Codec<PickupCondition> CODEC = Codec.STRING.comapFlatMap(PickupCondition::of, pickupCondition -> pickupCondition.str);
private String str, cond, match;
private boolean wildcards;
private PickupCondition(String str, String cond, String match)
{
this.str = str;
this.cond = cond;
this.match = match;
}
public static DataResult<PickupCondition> of(String str)
{
if(!(str.contains("(") && str.endsWith(")")))
return DataResult.error("Error while parsing: "+ str +". Pickup Condition must contain proper brackets.");
String cond = str.substring(str.indexOf("("), str.length()-1);
String match = str.substring(0, str.indexOf("("));
PickupCondition condition = new PickupCondition(str, cond, match);
if(match.contains("*"))
condition.wildcards = true;
return DataResult.success(condition);
}
public boolean matches(BlockState state)
{
if(wildcards)
{
String name = match.contains("[") ? match.substring(0, match.indexOf("[")) : match;
String[] split = name.replace("*", ",").split(",");
String stateName = Registry.BLOCK.getKey(state.getBlock()).toString();
if(StringHelper.matchesWildcards(stateName, split))
{
if(match.contains("["))
{
stateName = stateName + match.substring(match.indexOf("["));
BlockResult result = parseState(stateName);
return matchesProperties(state, result.properties());
}
else
return true;
}
else
return false;
}
else
{
BlockResult res = parseState(match);
return res.blockState().getBlock() == state.getBlock() && matchesProperties(state, res.properties());
}
}
public boolean matches(Entity entity)
{
String entityName = Registry.ENTITY_TYPE.getKey(entity.getType()).toString();
if(wildcards)
{
String[] split = match.replace("*", ",").split(",");
return StringHelper.matchesWildcards(entityName, split);
}
else
return entityName.equals(match);
}
public String getCondition()
{
return cond;
}
public boolean isFulfilled(ServerPlayer player)
{
return Services.GAMESTAGES.hasStage(player, cond);
}
private BlockResult parseState(String state)
{
try {
BlockResult result = BlockStateParser.parseForBlock(Registry.BLOCK, state, false);
return result;
} catch (CommandSyntaxException e) {
e.printStackTrace();
}
return null;
}
private boolean matchesProperties(BlockState state, Map<Property<?>, Comparable<?>> props)
{
for(var entry : props.entrySet())
{
var val = state.getValue(entry.getKey());
if(val != entry.getValue())
return false;
}
return true;
}
}

View File

@ -0,0 +1,71 @@
package tschipp.carryon.common.pickupcondition;
import com.mojang.serialization.DataResult;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.block.state.BlockState;
import tschipp.carryon.Constants;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class PickupConditionHandler
{
private static final List<PickupCondition> BLOCK_CONDITIONS = new ArrayList<>();
private static final List<PickupCondition> ENTITY_CONDITIONS = new ArrayList<>();
public static void initPickupConditions()
{
BLOCK_CONDITIONS.clear();
ENTITY_CONDITIONS.clear();
for(String cond : Constants.COMMON_CONFIG.customPickupConditions.customPickupConditionsBlocks)
{
DataResult<PickupCondition> res = PickupCondition.of(cond);
if(res.result().isPresent())
{
PickupCondition pickupCondition = res.result().get();
BLOCK_CONDITIONS.add(pickupCondition);
}
else
{
Constants.LOG.debug("Error while parsing Pickup Conditions: " + res.error().get().message());
}
}
for(String cond : Constants.COMMON_CONFIG.customPickupConditions.customPickupConditionsEntities)
{
DataResult<PickupCondition> res = PickupCondition.of(cond);
if(res.result().isPresent())
{
PickupCondition pickupCondition = res.result().get();
ENTITY_CONDITIONS.add(pickupCondition);
}
else
{
Constants.LOG.debug("Error while parsing Pickup Conditions: " + res.error().get().message());
}
}
}
public static Optional<PickupCondition> getPickupCondition(BlockState state)
{
for(PickupCondition cond : BLOCK_CONDITIONS)
{
if(cond.matches(state))
return Optional.of(cond);
}
return Optional.empty();
}
public static Optional<PickupCondition> getPickupCondition(Entity entity)
{
for(PickupCondition cond : ENTITY_CONDITIONS)
{
if(cond.matches(entity))
return Optional.of(cond);
}
return Optional.empty();
}
}

View File

@ -17,10 +17,7 @@ import net.minecraft.world.scores.Score;
import net.minecraft.world.scores.Scoreboard;
import tschipp.carryon.platform.Services;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.*;
public final class Matchables
{
@ -29,10 +26,18 @@ public final class Matchables
boolean matches(T elem);
}
private static float getValueFromString(String toGetFrom, String key, float defaultValue)
private static float getValueFromStringOrDefault(String toGetFrom, String key, float defaultVal)
{
Optional<Float> val = getValueFromString(toGetFrom, key);
if(val.isPresent())
return val.get();
return defaultVal;
}
private static Optional<Float> getValueFromString(String toGetFrom, String key)
{
if (toGetFrom == null || toGetFrom.isEmpty())
return defaultValue;
return Optional.empty();
String[] s = toGetFrom.split(",");
for (String string : s)
@ -50,11 +55,11 @@ public final class Matchables
{
}
return numb;
return Optional.of(numb);
}
}
return defaultValue;
return Optional.empty();
}
public record NumberBoundCondition(String bounds) implements Matchable<Number>
@ -203,14 +208,15 @@ public final class Matchables
public static final GamestageCondition NONE = new GamestageCondition("");
@Override
public boolean matches(ServerPlayer elem)
public boolean matches(ServerPlayer player)
{
if(!Services.PLATFORM.isModLoaded("gamestages"))
return true;
//TODO: Gamestages
if(gamestage == null || gamestage.isEmpty())
return true;
return true;
return Services.GAMESTAGES.hasStage(player, gamestage);
}
}
@ -271,8 +277,8 @@ public final class Matchables
if (cond == null || cond.isEmpty())
return true;
BlockPos blockpos = new BlockPos(getValueFromString(cond, "x", 0), getValueFromString(cond, "y", 0), getValueFromString(cond, "z", 0));
BlockPos expand = new BlockPos(getValueFromString(cond, "dx", 0), getValueFromString(cond, "dy", 0), getValueFromString(cond, "dz", 0));
BlockPos blockpos = new BlockPos(getValueFromStringOrDefault(cond, "x", 0), getValueFromStringOrDefault(cond, "y", 0), getValueFromStringOrDefault(cond, "z", 0));
BlockPos expand = new BlockPos(getValueFromStringOrDefault(cond, "dx", 0), getValueFromStringOrDefault(cond, "dy", 0), getValueFromStringOrDefault(cond, "dz", 0));
BlockPos expanded = blockpos.offset(expand);
BlockPos pos = elem.blockPosition();
@ -339,7 +345,7 @@ public final class Matchables
int idx = names.indexOf(name);
int lev = levels.get(idx);
if (lev == amp)
if (lev <= amp)
matches++;
}
}
@ -372,12 +378,30 @@ public final class Matchables
String source;
Vec3 vec;
boolean x, y, z;
public OptionalVec3(String source)
{
this.source = source;
double x = getValueFromString(source, "x", 0);
double y = getValueFromString(source, "y", 0);
double z = getValueFromString(source, "z", 0);
Optional<Float> xOpt = getValueFromString(source, "x");
Optional<Float> yOpt = getValueFromString(source, "y");
Optional<Float> zOpt = getValueFromString(source, "z");
float x = 0, y = 0, z = 0;
if(xOpt.isPresent()) {
x = xOpt.get();
this.x = true;
}
if(yOpt.isPresent()) {
y = yOpt.get();
this.y = true;
}
if(zOpt.isPresent()) {
z = zOpt.get();
this.z = true;
}
vec = new Vec3(x, y, z);
}
@ -386,16 +410,19 @@ public final class Matchables
return source;
}
/**
* Gets the contained optional vector. Nonexisting numbers are set to 0.
*/
public Vec3 getVec()
{
return vec;
}
public Vec3 getVec(double defaultValue)
public Vec3 getVec(double dX, double dY, double dZ)
{
double x = vec.x == 0f ? defaultValue : vec.x;
double y = vec.y == 0f ? defaultValue : vec.y;
double z = vec.z == 0f ? defaultValue : vec.z;
double x = !this.x ? dX : vec.x;
double y = !this.y ? dY : vec.y;
double z = !this.z ? dZ : vec.z;
return new Vec3(x, y, z);
}
}

View File

@ -2,7 +2,9 @@ package tschipp.carryon.config;
//Many Thanks to ThatGravyBoat for this template!
import tschipp.carryon.common.carry.ListHandler;
import tschipp.carryon.client.modeloverride.ModelOverrideHandler;
import tschipp.carryon.common.config.ListHandler;
import tschipp.carryon.common.pickupcondition.PickupConditionHandler;
import tschipp.carryon.config.annotations.Category;
import tschipp.carryon.config.annotations.Config;
import tschipp.carryon.config.annotations.Property;
@ -33,6 +35,8 @@ public class ConfigLoader {
public static void onConfigLoaded() {
ListHandler.initConfigLists();
PickupConditionHandler.initPickupConditions();
ModelOverrideHandler.initModelOverrides();
}
public static BuiltCategory buildCategory(String categoryDesc, Object object) throws IllegalAccessException {

View File

@ -11,6 +11,7 @@ import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import tschipp.carryon.Constants;
import tschipp.carryon.client.render.CarryRenderHelper;
import tschipp.carryon.common.carry.CarryOnData;
import tschipp.carryon.common.carry.CarryOnDataManager;
@ -28,7 +29,7 @@ public class HumanoidModelMixin {
@Inject(at = @At("RETURN"), method = "setupAnim(Lnet/minecraft/world/entity/LivingEntity;FFFFF)V")
private void onSetupAnimations(LivingEntity living, float f1, float f2, float f3, float f4, float f5, CallbackInfo ci)
{
if(living instanceof Player player)
if(living instanceof Player player && Constants.CLIENT_CONFIG.renderArms)
{
CarryOnData carry = CarryOnDataManager.getCarryData(player);
if(carry.isCarrying() && !player.isVisuallySwimming() && !player.isFallFlying())
@ -47,8 +48,8 @@ public class HumanoidModelMixin {
boolean renderLeft = render.renderLeftArm();
boolean renderRight = render.renderRightArm();
Vec3 rotLeft = render.renderRotationLeftArm().getVec();
Vec3 rotRight = render.renderRotationRightArm().getVec();
Vec3 rotLeft = render.renderRotationLeftArm().getVec(-x, -offset, z);
Vec3 rotRight = render.renderRotationRightArm().getVec(-x, offset, -z);
if(renderLeft)
changeRotation(leftArm, (float) rotLeft.x, (float) rotLeft.y, (float) rotLeft.z);

View File

@ -1,6 +1,7 @@
package tschipp.carryon.platform;
import tschipp.carryon.Constants;
import tschipp.carryon.platform.services.IGamestagePlatformHelper;
import tschipp.carryon.platform.services.IPlatformHelper;
import java.util.ServiceLoader;
@ -9,6 +10,8 @@ public class Services {
public static final IPlatformHelper PLATFORM = load(IPlatformHelper.class);
public static final IGamestagePlatformHelper GAMESTAGES = load(IGamestagePlatformHelper.class);
public static <T> T load(Class<T> clazz) {
final T loadedService = ServiceLoader.load(clazz)

View File

@ -0,0 +1,8 @@
package tschipp.carryon.platform.services;
import net.minecraft.world.entity.player.Player;
public interface IGamestagePlatformHelper
{
boolean hasStage(Player player, String stage);
}

View File

@ -0,0 +1,18 @@
package tschipp.carryon.utils;
public class StringHelper
{
public static boolean matchesWildcards(String str, String[] wildcards)
{
for(String w : wildcards)
{
if(!str.contains(w))
return false;
int i = str.indexOf(w);
str = str.substring(i + w.length());
}
return true;
}
}

View File

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 170 KiB

View File

@ -6,6 +6,15 @@ plugins {
archivesBaseName = "${mod_id}-fabric-${minecraft_version}"
apply from: 'https://raw.githubusercontent.com/MinecraftModDevelopment/Gradle-Collection/22e7d543a18cd30675277fbfa3669e3d9e206010/generic/secrets.gradle'
if (project.hasProperty('secretFile')) {
loadSecrets(new File((String) findProperty('secretFile')))
}
if (System.getenv('BUILD_NUMBER') != null) {
version += "." + System.getenv('BUILD_NUMBER')
}
dependencies {
minecraft "com.mojang:minecraft:${minecraft_version}"
mappings loom.officialMojangMappings()

View File

@ -9,6 +9,7 @@ public class ClientEvents {
{
ClientTickEvents.END_CLIENT_TICK.register(mc -> {
CarryOnCommonClient.checkForKeybinds();
CarryOnCommonClient.onCarryClientTick();
});
}

View File

@ -1,10 +1,10 @@
package tschipp.carryon.events;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.fabricmc.fabric.api.event.player.UseBlockCallback;
import net.fabricmc.fabric.api.event.player.UseEntityCallback;
import net.fabricmc.fabric.api.event.player.*;
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
@ -35,7 +35,7 @@ public class CommonEvents {
CarryOnData carry = CarryOnDataManager.getCarryData(player);
if(!carry.isCarrying())
{
if (PickupHandler.tryPickUpBlock((ServerPlayer) player, pos, world))
if (PickupHandler.tryPickUpBlock((ServerPlayer) player, pos, world, null))
return InteractionResult.SUCCESS;
return InteractionResult.PASS;
}
@ -67,7 +67,7 @@ public class CommonEvents {
CarryOnData carry = CarryOnDataManager.getCarryData(player);
if (!carry.isCarrying()) {
if (PickupHandler.tryPickupEntity((ServerPlayer) player, entity)) {
if (PickupHandler.tryPickupEntity((ServerPlayer) player, entity, null)) {
return InteractionResult.SUCCESS;
}
}
@ -92,11 +92,37 @@ public class CommonEvents {
ScriptReloadListener.syncScriptsWithClient(player);
});
ServerTickEvents.END_SERVER_TICK.register(server -> {
for(ServerPlayer player : server.getPlayerList().getPlayers())
PickupHandler.onCarryTick(player);
CarryOnCommon.onCarryTick(player);
});
ServerPlayerEvents.COPY_FROM.register(((oldPlayer, newPlayer, alive) -> {
PlacementHandler.placeCarriedOnDeath(oldPlayer, newPlayer, !alive);
}));
PlayerBlockBreakEvents.BEFORE.register(((world, player, pos, state, blockEntity) -> {
if(!CarryOnCommon.onTryBreakBlock(player))
return false;
return true;
}));
AttackBlockCallback.EVENT.register(((player, world, hand, pos, direction) -> {
if(!CarryOnCommon.onTryBreakBlock(player))
return InteractionResult.SUCCESS;
return InteractionResult.PASS;
}));
AttackEntityCallback.EVENT.register(((player, world, hand, entity, hitResult) -> {
if(!CarryOnCommon.onAttackedByPlayer(player))
return InteractionResult.SUCCESS;
return InteractionResult.PASS;
}));
//TODO: drop carried when attacked
}
}

View File

@ -0,0 +1,29 @@
package tschipp.carryon.mixin;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.player.Player;
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 tschipp.carryon.Constants;
import tschipp.carryon.common.carry.CarryOnData;
import tschipp.carryon.common.carry.CarryOnDataManager;
import tschipp.carryon.common.carry.PlacementHandler;
@Mixin(Player.class)
public class PlayerMixinFabric
{
@Inject(at = @At("HEAD"), method = "hurt(Lnet/minecraft/world/damagesource/DamageSource;F)Z")
private void onHurt(DamageSource damageSource, float f, CallbackInfoReturnable<Boolean> cir) {
if(Constants.COMMON_CONFIG.settings.dropCarriedWhenHit)
{
Player player = ((Player)(Object)this);
CarryOnData carry = CarryOnDataManager.getCarryData(player);
if(carry.isCarrying() && !player.level.isClientSide)
PlacementHandler.placeCarried((ServerPlayer)player);
}
}
}

View File

@ -0,0 +1,13 @@
package tschipp.carryon.platform;
import net.minecraft.world.entity.player.Player;
import tschipp.carryon.platform.services.IGamestagePlatformHelper;
public class FabricGamestagesHelper implements IGamestagePlatformHelper
{
@Override
public boolean hasStage(Player player, String stage)
{
return true;
}
}

View File

@ -0,0 +1 @@
tschipp.carryon.platform.FabricGamestagesHelper

View File

@ -4,6 +4,7 @@
"package": "tschipp.carryon.mixin",
"compatibilityLevel": "JAVA_17",
"mixins": [
"PlayerMixinFabric"
],
"client": [
"ItemInHandRendererMixin",

View File

@ -2,19 +2,19 @@
"schemaVersion": 1,
"id": "carryon",
"version": "${version}",
"name": "Carry On",
"description": "This is an example description! Tell everyone what your mod is about!",
"description": "Carry On is a simple mod that improves game interaction by allowing players to pick up, carry, and place single block Tile Entities using only their empty hands.",
"authors": [
"Tschipp"
"Tschipp", "Purplicious_Cow", "cy4n"
],
"contact": {
"homepage": "https://fabricmc.net/",
"sources": "https://github.com/FabricMC/fabric-example-mod"
"homepage": "https://tschipp.ch/",
"sources": "https://github.com/Tschipp/CarryOn",
"issues": "https://github.com/Tschipp/CarryOn/issues"
},
"license": "CC0-1.0",
"icon": "assets/carryon/logo.png",
"license": "GNU LGPLv3",
"icon": "logo.png",
"environment": "*",
"entrypoints": {

View File

@ -18,6 +18,15 @@ apply plugin: 'org.spongepowered.mixin'
apply plugin: 'maven-publish'
apply plugin: 'org.parchmentmc.librarian.forgegradle'
apply from: 'https://raw.githubusercontent.com/MinecraftModDevelopment/Gradle-Collection/22e7d543a18cd30675277fbfa3669e3d9e206010/generic/secrets.gradle'
if (project.hasProperty('secretFile')) {
loadSecrets(new File((String) findProperty('secretFile')))
}
if (System.getenv('BUILD_NUMBER') != null) {
version += "." + System.getenv('BUILD_NUMBER')
}
archivesBaseName = "${mod_id}-forge-${minecraft_version}"
mixin {
@ -88,9 +97,16 @@ minecraft {
sourceSets.main.resources.srcDir 'src/generated/resources'
repositories {
maven {
url 'https://maven.blamejared.com'
}
}
dependencies {
minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}"
compileOnly project(":Common")
compileOnly fg.deobf("net.darkhax.gamestages:GameStages-Forge-1.19.2:11.0.2")
annotationProcessor 'org.spongepowered:mixin:0.8.4-SNAPSHOT:processor'
}

View File

@ -0,0 +1,12 @@
package tschipp.carryon.compat;
import net.darkhax.gamestages.GameStageHelper;
import net.minecraft.world.entity.player.Player;
public class GamestageCompat
{
public static boolean hasStage(Player player, String stage)
{
return GameStageHelper.hasStage(player, stage);
}
}

View File

@ -78,6 +78,7 @@ public class ClientEvents {
if(event.phase == Phase.END)
{
CarryOnCommonClient.checkForKeybinds();
CarryOnCommonClient.onCarryClientTick();
}
}

View File

@ -4,6 +4,8 @@ import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraftforge.common.MinecraftForge;
@ -13,24 +15,29 @@ import net.minecraftforge.event.OnDatapackSyncEvent;
import net.minecraftforge.event.RegisterCommandsEvent;
import net.minecraftforge.event.TickEvent.Phase;
import net.minecraftforge.event.TickEvent.ServerTickEvent;
import net.minecraftforge.event.entity.living.LivingAttackEvent;
import net.minecraftforge.event.entity.living.LivingSpawnEvent.CheckSpawn;
import net.minecraftforge.event.entity.player.AttackEntityEvent;
import net.minecraftforge.event.entity.player.PlayerEvent.BreakSpeed;
import net.minecraftforge.event.entity.player.PlayerEvent.Clone;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.event.level.BlockEvent;
import net.minecraftforge.event.level.BlockEvent.BreakEvent;
import net.minecraftforge.event.level.BlockEvent.EntityPlaceEvent;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.eventbus.api.Event.Result;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.InterModComms;
import net.minecraftforge.fml.InterModComms.IMCMessage;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLDedicatedServerSetupEvent;
import tschipp.carryon.CarryOnCommon;
import tschipp.carryon.Constants;
import tschipp.carryon.common.carry.*;
import tschipp.carryon.common.carry.CarryOnData;
import tschipp.carryon.common.carry.CarryOnData.CarryType;
import tschipp.carryon.common.carry.CarryOnDataManager;
import tschipp.carryon.common.carry.PickupHandler;
import tschipp.carryon.common.carry.PlacementHandler;
import tschipp.carryon.common.scripting.ScriptReloadListener;
import java.util.stream.Stream;
@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.FORGE, modid = Constants.MOD_ID)
public class CommonEvents
{
@ -51,30 +58,35 @@ public class CommonEvents
CarryOnData carry = CarryOnDataManager.getCarryData(player);
if (!carry.isCarrying()) {
if (PickupHandler.tryPickUpBlock((ServerPlayer) player, pos, level)) {
if (PickupHandler.tryPickUpBlock((ServerPlayer) player, pos, level, (pState, pPos) -> {
BlockEvent.BreakEvent breakEvent = new BreakEvent(level, pPos, pState, player);
MinecraftForge.EVENT_BUS.post(breakEvent);
return !breakEvent.isCanceled();
})) {
success = true;
}
} else {
if (carry.isCarrying(CarryType.BLOCK)) {
if (PlacementHandler.tryPlaceBlock((ServerPlayer) player, pos, event.getFace(), (pos2, state) -> {
PlacementHandler.tryPlaceBlock((ServerPlayer) player, pos, event.getFace(), (pos2, state) -> {
BlockSnapshot snapshot = BlockSnapshot.create(level.dimension(), level, pos2);
EntityPlaceEvent event1 = new EntityPlaceEvent(snapshot, level.getBlockState(pos), player);
MinecraftForge.EVENT_BUS.post(event1);
return !event1.isCanceled();
})) {
success = true;
}
});
} else {
//TODO: Entity place perms
if (PlacementHandler.tryPlaceEntity((ServerPlayer) player,pos, event.getFace(), null))
{
success = true;
}
PlacementHandler.tryPlaceEntity((ServerPlayer) player, pos, event.getFace(), (pPos, toPlace) -> {
if (toPlace instanceof Mob mob) {
CheckSpawn checkSpawn = new CheckSpawn(mob, level, pPos.x, pPos.y, pPos.z, null, MobSpawnType.EVENT);
MinecraftForge.EVENT_BUS.post(checkSpawn);
return event.getResult() != Result.DENY;
}
return true;
});
}
success = true;
}
if(success)
{
if (success) {
event.setUseBlock(Event.Result.DENY);
event.setUseItem(Event.Result.DENY);
event.setCancellationResult(InteractionResult.SUCCESS);
@ -97,16 +109,18 @@ public class CommonEvents
CarryOnData carry = CarryOnDataManager.getCarryData(player);
if (!carry.isCarrying()) {
if (PickupHandler.tryPickupEntity((ServerPlayer) player, target)) {
if (PickupHandler.tryPickupEntity((ServerPlayer) player, target, (toPickup) -> {
AttackEntityEvent attackEvent = new AttackEntityEvent(player, toPickup);
MinecraftForge.EVENT_BUS.post(attackEvent);
return !attackEvent.isCanceled();
})) {
event.setResult(Result.DENY);
event.setCancellationResult(InteractionResult.SUCCESS);
event.setCanceled(true);
return;
}
}
else if(carry.isCarrying(CarryType.ENTITY) || carry.isCarrying(CarryType.PLAYER))
{
PlacementHandler.tryStackEntity((ServerPlayer)player, target);
} else if (carry.isCarrying(CarryType.ENTITY) || carry.isCarrying(CarryType.PLAYER)) {
PlacementHandler.tryStackEntity((ServerPlayer) player, target);
}
}
@ -116,49 +130,6 @@ public class CommonEvents
CarryOnCommon.registerCommands(event.getDispatcher());
}
@SubscribeEvent(priority = EventPriority.LOW)
public void serverLoad(FMLDedicatedServerSetupEvent event)
{
Stream<IMCMessage> messages = InterModComms.getMessages(Constants.MOD_ID);
messages.forEach(msg -> {
String method = msg.method();
Object obj = msg.messageSupplier().get();
if (!(obj instanceof String str))
return;
switch (method)
{
case "blacklistBlock":
ListHandler.addForbiddenTiles(str);
break;
case "blacklistEntity":
ListHandler.addForbiddenEntities(str);
break;
case "whitelistBlock":
ListHandler.addAllowedTiles(str);
break;
case "whitelistEntity":
ListHandler.addAllowedEntities(str);
break;
case "blacklistStacking":
ListHandler.addForbiddenStacking(str);
break;
case "whitelistStacking":
ListHandler.addAllowedStacking(str);
break;
//TODO
// case "addModelOverride":
// ModelOverridesHandler.parseOverride(str, 0);
// break;
}
});
}
@SubscribeEvent
public static void onDatapackRegister(AddReloadListenerEvent event)
{
@ -169,21 +140,55 @@ public class CommonEvents
public static void onDatapackSync(OnDatapackSyncEvent event)
{
ServerPlayer player = event.getPlayer();
if(player == null)
{
for(ServerPlayer p : event.getPlayerList().getPlayers())
if (player == null) {
for (ServerPlayer p : event.getPlayerList().getPlayers())
ScriptReloadListener.syncScriptsWithClient(p);
}
else
} else
ScriptReloadListener.syncScriptsWithClient(player);
}
@SubscribeEvent
public static void onServerTick(ServerTickEvent event)
{
if(event.phase == Phase.END)
for(ServerPlayer player : event.getServer().getPlayerList().getPlayers())
PickupHandler.onCarryTick(player);
if (event.phase == Phase.END)
for (ServerPlayer player : event.getServer().getPlayerList().getPlayers())
CarryOnCommon.onCarryTick(player);
}
@SubscribeEvent
public static void onClone(Clone event)
{
if (!event.getOriginal().level.isClientSide)
PlacementHandler.placeCarriedOnDeath((ServerPlayer) event.getOriginal(), (ServerPlayer) event.getEntity(), event.isWasDeath());
}
@SubscribeEvent
public static void harvestSpeed(BreakSpeed event)
{
if (!CarryOnCommon.onTryBreakBlock(event.getEntity()))
event.setNewSpeed(0);
}
@SubscribeEvent
public static void attackEntity(AttackEntityEvent event)
{
if(!CarryOnCommon.onAttackedByPlayer(event.getEntity()))
event.setCanceled(true);
}
@SubscribeEvent
public static void onBreakBlock(BreakEvent event)
{
if (!CarryOnCommon.onTryBreakBlock(event.getPlayer())) {
event.setCanceled(true);
}
}
@SubscribeEvent
public static void playerAttack(LivingAttackEvent event)
{
if(event.getEntity() instanceof Player player)
CarryOnCommon.onPlayerAttacked(player);
}
}

View File

@ -1,8 +1,58 @@
package tschipp.carryon.events;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.InterModComms;
import net.minecraftforge.fml.InterModComms.IMCMessage;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLDedicatedServerSetupEvent;
import tschipp.carryon.Constants;
import tschipp.carryon.client.modeloverride.ModelOverrideHandler;
import tschipp.carryon.common.config.ListHandler;
import java.util.stream.Stream;
@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD, modid = Constants.MOD_ID)
public class ModBusEvents {
@SubscribeEvent(priority = EventPriority.LOW)
public void serverLoad(FMLDedicatedServerSetupEvent event)
{
Stream<IMCMessage> messages = InterModComms.getMessages(Constants.MOD_ID);
messages.forEach(msg -> {
String method = msg.method();
Object obj = msg.messageSupplier().get();
if (!(obj instanceof String str))
return;
switch (method) {
case "blacklistBlock":
ListHandler.addForbiddenTiles(str);
break;
case "blacklistEntity":
ListHandler.addForbiddenEntities(str);
break;
case "whitelistBlock":
ListHandler.addAllowedTiles(str);
break;
case "whitelistEntity":
ListHandler.addAllowedEntities(str);
break;
case "blacklistStacking":
ListHandler.addForbiddenStacking(str);
break;
case "whitelistStacking":
ListHandler.addAllowedStacking(str);
break;
case "addModelOverride":
ModelOverrideHandler.addFromString(str);
break;
}
});
}
}

View File

@ -0,0 +1,17 @@
package tschipp.carryon.platform;
import net.minecraft.world.entity.player.Player;
import tschipp.carryon.compat.GamestageCompat;
import tschipp.carryon.platform.services.IGamestagePlatformHelper;
public class ForgeGamestagesHelper implements IGamestagePlatformHelper
{
@Override
public boolean hasStage(Player player, String stage)
{
if(!Services.PLATFORM.isModLoaded("gamestages"))
return true;
return GamestageCompat.hasStage(player, stage);
}
}

View File

@ -9,8 +9,8 @@ import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.network.NetworkDirection;
import net.minecraftforge.network.NetworkEvent.Context;
import net.minecraftforge.network.PacketDistributor;
import tschipp.carryon.CarryOnCommonClient;
import tschipp.carryon.CarryOnForge;
import tschipp.carryon.client.CarryOnClient;
import tschipp.carryon.config.BuiltConfig;
import tschipp.carryon.config.forge.ConfigLoaderImpl;
import tschipp.carryon.networking.PacketBase;
@ -70,7 +70,7 @@ public class ForgePlatformHelper implements IPlatformHelper {
{
ctx.get().setPacketHandled(true);
ctx.get().enqueueWork(() -> {
handler.accept(packet, CarryOnClient.getPlayer());
handler.accept(packet, CarryOnCommonClient.getPlayer());
});
}
};

View File

@ -1,42 +1,18 @@
# This is an example mods.toml file. It contains the data relating to the loading mods.
# There are several mandatory fields (#mandatory), and many more that are optional (#optional).
# The overall format is standard TOML format, v0.5.0.
# Note that there are a couple of TOML lists in this file.
# Find more information on toml format here: https://github.com/toml-lang/toml
# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml
modLoader="javafml" #mandatory
# A version range to match for said mod loader - for regular FML @Mod it will be the forge version
loaderVersion="[43,)" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions.
# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties.
# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here.
license="All rights reserved"
# A URL to refer people to when problems occur with this mod
#issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional
# A list of mods - how many allowed here is determined by the individual mod loader
license="GNU LGPLv3"
issueTrackerURL="https://github.com/Tschipp/CarryOn/issues"
[[mods]] #mandatory
# The modid of the mod
modId="carryon" #mandatory
# The version number of the mod - there's a few well known ${} variables useable here or just hardcode it
# ${file.jarVersion} will substitute the value of the Implementation-Version as read from the mod's JAR file metadata
# see the associated build.gradle script for how to populate this completely automatically during a build
version="${file.jarVersion}" #mandatory
# A display name for the mod
displayName="Carry On" #mandatory
# A URL to query for updates for this mod. See the JSON update specification https://mcforge.readthedocs.io/en/latest/gettingstarted/autoupdate/
#updateJSONURL="https://change.me.example.invalid/updates.json" #optional
# A URL for the "homepage" for this mod, displayed in the mod UI
#displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional
# A file name (in the root of the mod JAR) containing a logo for display
logoFile="carryon.png" #optional
# A text field displayed in the mod UI
credits="Tschipp, PurpliciousCow, cy4n" #optional
# A text field displayed in the mod UI
authors="Tschipp, PurpliciousCow, cy4n" #optional
# The description text for the mod (multi line!) (#mandatory)
description='''
Carry On Mod.
'''
# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional.
modId="carryon" #mandatory
version="${file.jarVersion}" #mandatory
displayName="Carry On" #mandatory
displayURL="https://tschipp.ch" #optional
logoFile="logo.png" #optional
authors="Tschipp, Purplicious_Cow, cy4n" #optional
description='''Carry On is a simple mod that improves game interaction by allowing players to pick up, carry, and place single block Tile Entities using only their empty hands.'''
[[dependencies.carryon]] #optional
# the modid of the dependency
modId="forge" #mandatory
@ -52,7 +28,6 @@ Carry On Mod.
[[dependencies.carryon]]
modId="minecraft"
mandatory=true
# This version range declares a minimum of the current minecraft version up to but not including the next major version
versionRange="[1.19.2,1.20)"
ordering="NONE"
side="BOTH"

View File

@ -0,0 +1 @@
tschipp.carryon.platform.ForgeGamestagesHelper

32
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,32 @@
#!/usr/bin/env groovy
pipeline {
agent any
tools {
jdk "jdk-17.0.1"
}
stages {
stage('Clean') {
steps {
withCredentials([file(credentialsId: 'mod_build_secrets', variable: 'ORG_GRADLE_PROJECT_secretFile')]) {
echo 'Cleaning Project'
sh 'chmod +x gradlew'
sh './gradlew clean'
}
}
}
stage('Build') {
steps {
withCredentials([file(credentialsId: 'mod_build_secrets', variable: 'ORG_GRADLE_PROJECT_secretFile')]) {
echo 'Building'
sh './gradlew build'
}
}
}
}
post {
always {
archive 'build/libs/**.jar'
}
}
}

262
LICENSE
View File

@ -1,121 +1,165 @@
Creative Commons Legal Code
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
CC0 1.0 Universal
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
0. Additional Definitions.
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
4. Limitations and Disclaimers.
1. Exception to Section 3 of the GNU GPL.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View File

@ -1,4 +1,20 @@
# MultiLoader Template
# Carry On [![](http://cf.way2muchnoise.eu/carry-on.svg)](https://minecraft.curseforge.com/projects/carry-on) [![](http://cf.way2muchnoise.eu/versions/carry-on.svg)](https://minecraft.curseforge.com/projects/carry-on)
To use CarryOn in your projects, include this in your build.gradle:
```
repositories {
maven {
url "https://maven.blamejared.com/"
}
}
dependencies {
deobfCompile "tschipp.carryon:carryon-MCVERSION:MODVERSION"
}
```
Make sure to replace `MCVERSION` and `MODVERSION` with the appropriate versions.
# Development
This project provides a Gradle project template that can compile mods for both Forge and Fabric using a common sourceset. This project does not require any third party libraries or dependencies.
@ -8,18 +24,12 @@ This project provides a Gradle project template that can compile mods for both F
This guide will show how to import the MultiLoader Template into IntelliJ IDEA. The setup process is roughly equivalent to setting up Forge and Fabric independently and should be very familiar to anyone who has worked with their MDKs.
1. Clone or download this repository to your computer.
2. Configure the project by editing the `group`, `mod_name`, `mod_author`, and `mod_id` properties in the `gradle.properties` file. You will also need to change the `rootProject.name` property in `settings.gradle`.
3. Open the template's root folder as a new project in IDEA. This is the folder that contains this README file and the gradlew executable.
4. If your default JVM/JDK is not Java 17 you will encounter an error when opening the project. This error is fixed by going to `File > Settings > Build, Execution, Deployment > Build Tools > Gradle > Gradle JVM`and changing the value to a valid Java 17 JVM. You will also need to set the Project SDK to Java 17. This can be done by going to `File > Project Structure > Project SDK`. Once both have been set open the Gradle tab in IDEA and click the refresh button to reload the project.
5. Open the Gradle tab in IDEA if it has not already been opened. Navigate to `Your Project > Common > Tasks > vanilla gradle > decompile`. Run this task to decompile Minecraft.
6. Open the Gradle tab in IDEA if it has not already been opened. Navigate to `Your Project > Forge > Tasks > forgegradle runs > genIntellijRuns`. Run this task to set up run configurations for Forge.
7. Open your Run/Debug Configurations. Under the Application category there should now be options to run Forge and Fabric projects. Select one of the client options and try to run it.
8. Assuming you were able to run the game in step 7 your workspace should now be set up.
2. Open the template's root folder as a new project in IDEA. This is the folder that contains this README file and the gradlew executable.
3. If your default JVM/JDK is not Java 17 you will encounter an error when opening the project. This error is fixed by going to `File > Settings > Build, Execution, Deployment > Build Tools > Gradle > Gradle JVM`and changing the value to a valid Java 17 JVM. You will also need to set the Project SDK to Java 17. This can be done by going to `File > Project Structure > Project SDK`. Once both have been set open the Gradle tab in IDEA and click the refresh button to reload the project.
4. Open the Gradle tab in IDEA if it has not already been opened. Navigate to `Your Project > Common > Tasks > vanilla gradle > decompile`. Run this task to decompile Minecraft.
5. Open the Gradle tab in IDEA if it has not already been opened. Navigate to `Your Project > Forge > Tasks > forgegradle runs > genIntellijRuns`. Run this task to set up run configurations for Forge.
6. Open your Run/Debug Configurations. Under the Application category there should now be options to run Forge and Fabric projects. Select one of the client options and try to run it.
7. Assuming you were able to run the game in step 7 your workspace should now be set up.
### Eclipse
While it is possible to use this template in Eclipse it is not recommended. During the development of this template multiple critical bugs and quirks related to Eclipse were found at nearly every level of the required build tools. While we continue to work with these tools to report and resolve issues support for projects like these are not there yet. For now Eclipse is considered unsupported by this project. The development cycle for build tools is notoriously slow so there are no ETAs available.
## Development Guide
When using this template the majority of your mod is developed in the Common project. The Common project is compiled against the vanilla game and is used to hold code that is shared between the different loader-specific versions of your mod. The Common project has no knowledge or access to ModLoader specific code, apis, or concepts. Code that requires something from a specific loader must be done through the project that is specific to that loader, such as the Forge or Fabric project.
Loader specific projects such as the Forge and Fabric project are used to load the Common project into the game. These projects also define code that is specific to that loader. Loader specific projects can access all of the code in the Common project. It is important to remember that the Common project can not access code from loader specific projects.

View File

@ -8,7 +8,7 @@ subprojects {
jar {
from(rootProject.file("LICENSE")) {
rename { "${it}_${mod_name}" }
rename { "${it}_${mod_id}" }
}
manifest {
attributes([