From fa1aadcd86fa6f94ea8b627fccebc082a6d7cf4c Mon Sep 17 00:00:00 2001 From: Christian Knaapen Date: Mon, 16 Jan 2023 01:53:09 +0100 Subject: [PATCH] Using Create outlines and ghost blocks for block previews. Fixed item names. --- .../create/CreateClientTest.java | 31 ++++++ .../create/events/ClientEvents.java | 4 +- .../utility/ghost/GhostBlockRenderer.java | 4 +- .../item/DiamondRandomizerBagItem.java | 2 +- .../item/GoldenRandomizerBagItem.java | 2 +- .../item/RandomizerBagItem.java | 2 +- .../render/BlockPreviewRenderer.java | 104 +++++++++++------- .../render/RenderHandler.java | 78 ++++++++----- .../assets/effortlessbuilding/lang/en_us.json | 12 +- 9 files changed, 158 insertions(+), 81 deletions(-) create mode 100644 src/main/java/nl/requios/effortlessbuilding/create/CreateClientTest.java diff --git a/src/main/java/nl/requios/effortlessbuilding/create/CreateClientTest.java b/src/main/java/nl/requios/effortlessbuilding/create/CreateClientTest.java new file mode 100644 index 0000000..2f4f2c4 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/CreateClientTest.java @@ -0,0 +1,31 @@ +package nl.requios.effortlessbuilding.create; + +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.phys.AABB; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import nl.requios.effortlessbuilding.create.foundation.utility.Color; + +@Mod.EventBusSubscriber(Dist.CLIENT) +public class CreateClientTest { + + @SubscribeEvent + public static void onTick(TickEvent.ClientTickEvent event) { + CreateClient.GHOST_BLOCKS.showGhostState(1, Blocks.SPRUCE_LOG.defaultBlockState()) + .at(0, 120, 0) + .breathingAlpha(); + CreateClient.GHOST_BLOCKS.showGhostState(2, Blocks.SPRUCE_LOG.defaultBlockState()) + .at(1, 120, 0) + .breathingAlpha(); + + CreateClient.OUTLINER.showAABB(1, new AABB(0, 0, 0, 10, 2, 6) + .move(10, 120, 0)) + .withFaceTexture(AllSpecialTextures.CHECKERED) + .colored(new Color(0.11f, 0.49f, 0.7f, 1f)) +// .colored(0xbfbfbf) + .disableNormals() + .lineWidth(1 / 32f); + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/events/ClientEvents.java b/src/main/java/nl/requios/effortlessbuilding/create/events/ClientEvents.java index 0a13b2c..c849e88 100644 --- a/src/main/java/nl/requios/effortlessbuilding/create/events/ClientEvents.java +++ b/src/main/java/nl/requios/effortlessbuilding/create/events/ClientEvents.java @@ -35,8 +35,8 @@ public class ClientEvents { Level world = Minecraft.getInstance().level; AnimationTickHolder.tick(); - CreateClient.OUTLINER.tickOutlines(); CreateClient.GHOST_BLOCKS.tickGhosts(); + CreateClient.OUTLINER.tickOutlines(); CameraAngleAnimationService.tick(); } @@ -89,7 +89,7 @@ public class ClientEvents { event.setPitch(CameraAngleAnimationService.getPitch(partialTicks)); } - protected static boolean isGameActive() { + public static boolean isGameActive() { return !(Minecraft.getInstance().level == null || Minecraft.getInstance().player == null); } diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/ghost/GhostBlockRenderer.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/ghost/GhostBlockRenderer.java index a27b869..1fa167b 100644 --- a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/ghost/GhostBlockRenderer.java +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/ghost/GhostBlockRenderer.java @@ -75,7 +75,7 @@ public abstract class GhostBlockRenderer { BlockState state = params.state; BlockPos pos = params.pos; - float alpha = params.alphaSupplier.get() * .75f/* * PlacementHelpers.getCurrentAlpha()*/; + float alpha = params.alphaSupplier.get()/* * .75f* PlacementHelpers.getCurrentAlpha()*/; BakedModel model = dispatcher.getBlockModel(state); RenderType layer = RenderType.translucent(); @@ -88,7 +88,7 @@ public abstract class GhostBlockRenderer { ms.scale(.85f, .85f, .85f); ms.translate(-.5, -.5, -.5); - renderModel(ms.last(), vb, state, model, 1f, 1f, 1f, alpha, + renderModel(ms.last(), vb, state, model, 0f, 1f, 1f, alpha, LevelRenderer.getLightColor(mc.level, pos), OverlayTexture.NO_OVERLAY, ModelUtil.VIRTUAL_DATA, layer); diff --git a/src/main/java/nl/requios/effortlessbuilding/item/DiamondRandomizerBagItem.java b/src/main/java/nl/requios/effortlessbuilding/item/DiamondRandomizerBagItem.java index 069a4ce..b7620b9 100644 --- a/src/main/java/nl/requios/effortlessbuilding/item/DiamondRandomizerBagItem.java +++ b/src/main/java/nl/requios/effortlessbuilding/item/DiamondRandomizerBagItem.java @@ -33,7 +33,7 @@ public class DiamondRandomizerBagItem extends AbstractRandomizerBagItem{ @Override public Component getDisplayName() { - return Component.translatable("effortlessbuilding:diamond_randomizer_bag"); + return Component.translatable("item.effortlessbuilding.diamond_randomizer_bag"); } @Nullable diff --git a/src/main/java/nl/requios/effortlessbuilding/item/GoldenRandomizerBagItem.java b/src/main/java/nl/requios/effortlessbuilding/item/GoldenRandomizerBagItem.java index 1d05c7d..d853f62 100644 --- a/src/main/java/nl/requios/effortlessbuilding/item/GoldenRandomizerBagItem.java +++ b/src/main/java/nl/requios/effortlessbuilding/item/GoldenRandomizerBagItem.java @@ -33,7 +33,7 @@ public class GoldenRandomizerBagItem extends AbstractRandomizerBagItem{ @Override public Component getDisplayName() { - return Component.translatable("effortlessbuilding:golden_randomizer_bag"); + return Component.translatable("item.effortlessbuilding.golden_randomizer_bag"); } @Nullable diff --git a/src/main/java/nl/requios/effortlessbuilding/item/RandomizerBagItem.java b/src/main/java/nl/requios/effortlessbuilding/item/RandomizerBagItem.java index ac41deb..06c7687 100644 --- a/src/main/java/nl/requios/effortlessbuilding/item/RandomizerBagItem.java +++ b/src/main/java/nl/requios/effortlessbuilding/item/RandomizerBagItem.java @@ -33,7 +33,7 @@ public class RandomizerBagItem extends AbstractRandomizerBagItem { @Override public Component getDisplayName() { - return Component.translatable("effortlessbuilding:randomizer_bag"); + return Component.translatable("item.effortlessbuilding.randomizer_bag"); } @Nullable diff --git a/src/main/java/nl/requios/effortlessbuilding/render/BlockPreviewRenderer.java b/src/main/java/nl/requios/effortlessbuilding/render/BlockPreviewRenderer.java index 641cf36..99284be 100644 --- a/src/main/java/nl/requios/effortlessbuilding/render/BlockPreviewRenderer.java +++ b/src/main/java/nl/requios/effortlessbuilding/render/BlockPreviewRenderer.java @@ -32,6 +32,9 @@ import nl.requios.effortlessbuilding.buildmodifier.BuildModifiers; import nl.requios.effortlessbuilding.buildmodifier.ModifierSettingsManager; import nl.requios.effortlessbuilding.buildmodifier.ModifierSettingsManager.ModifierSettings; import nl.requios.effortlessbuilding.compatibility.CompatHelper; +import nl.requios.effortlessbuilding.create.AllSpecialTextures; +import nl.requios.effortlessbuilding.create.CreateClient; +import nl.requios.effortlessbuilding.create.foundation.utility.Color; import nl.requios.effortlessbuilding.helper.ReachHelper; import nl.requios.effortlessbuilding.helper.SurvivalHelper; import nl.requios.effortlessbuilding.item.AbstractRandomizerBagItem; @@ -51,7 +54,7 @@ public class BlockPreviewRenderer { private static BlockPos previousSecondPos; private static int soundTime = 0; - public static void render(PoseStack matrixStack, MultiBufferSource.BufferSource renderTypeBuffer, Player player, ModifierSettings modifierSettings, ModeSettings modeSettings) { + public static void render(PoseStack ms, MultiBufferSource.BufferSource buffer, Player player, ModifierSettings modifierSettings, ModeSettings modeSettings) { //Render placed blocks with dissolve effect //Use fancy shader if config allows, otherwise no dissolve @@ -62,7 +65,7 @@ public class BlockPreviewRenderer { double totalTime = Mth.clampedLerp(30, 60, placed.firstPos.distSqr(placed.secondPos) / 100.0) * BuildConfig.visuals.dissolveTimeMultiplier.get(); float dissolve = (ClientEvents.ticksInGame - placed.time) / (float) totalTime; - renderBlockPreviews(matrixStack, renderTypeBuffer, placed.coordinates, placed.blockStates, placed.itemStacks, dissolve, placed.firstPos, placed.secondPos, false, placed.breaking); + renderBlockPreviews(ms, buffer, placed.coordinates, placed.blockStates, placed.itemStacks, dissolve, placed.firstPos, placed.secondPos, false, placed.breaking); } } } @@ -188,22 +191,45 @@ public class BlockPreviewRenderer { if (blockStates.size() != 0 && newCoordinates.size() == blockStates.size()) { int blockCount; - //Use fancy shader if config allows, otherwise outlines - if (BuildConfig.visuals.useShaders.get() && newCoordinates.size() < BuildConfig.visuals.shaderThreshold.get()) { - blockCount = renderBlockPreviews(matrixStack, renderTypeBuffer, newCoordinates, blockStates, itemStacks, 0f, firstPos, secondPos, !breaking, breaking); - } else { - VertexConsumer buffer = RenderHandler.beginLines(renderTypeBuffer); + if (!breaking) { + //Use fancy shader if config allows, otherwise outlines + if (BuildConfig.visuals.useShaders.get() && newCoordinates.size() < BuildConfig.visuals.shaderThreshold.get()) { + blockCount = renderBlockPreviews(ms, buffer, newCoordinates, blockStates, itemStacks, 0f, firstPos, secondPos, !breaking, breaking); - Vec3 color = new Vec3(1f, 1f, 1f); - if (breaking) color = new Vec3(1f, 0f, 0f); + CreateClient.OUTLINER.showCluster("blockpreviews", newCoordinates) + .withFaceTexture(AllSpecialTextures.CHECKERED) + .disableNormals() + .lineWidth(1 / 32f) + .colored(new Color(1f, 1f, 1f, 1f)); + } else { +// VertexConsumer vc = RenderHandler.beginLines(buffer); +// +// Vec3 color = new Vec3(1f, 1f, 1f); +// if (breaking) color = new Vec3(1f, 0f, 0f); +// +// for (int i = newCoordinates.size() - 1; i >= 0; i--) { +// VoxelShape collisionShape = blockStates.get(i).getCollisionShape(player.level, newCoordinates.get(i)); +// RenderHandler.renderBlockOutline(ms, vc, newCoordinates.get(i), collisionShape, color); +// } +// +// RenderHandler.endLines(buffer); - for (int i = newCoordinates.size() - 1; i >= 0; i--) { - VoxelShape collisionShape = blockStates.get(i).getCollisionShape(player.level, newCoordinates.get(i)); - RenderHandler.renderBlockOutline(matrixStack, buffer, newCoordinates.get(i), collisionShape, color); + CreateClient.OUTLINER.showCluster("blockpreviews", newCoordinates) + .withFaceTexture(AllSpecialTextures.HIGHLIGHT_CHECKERED) + .disableNormals() + .lineWidth(1 / 16f) + .colored(new Color(1f, 1f, 1f, 1f)); + + blockCount = newCoordinates.size(); } - RenderHandler.endLines(renderTypeBuffer); - + } else { + //Breaking + CreateClient.OUTLINER.showCluster("blockpreviews", newCoordinates) + .withFaceTexture(AllSpecialTextures.THIN_CHECKERED) + .disableNormals() + .lineWidth(1 / 16f) + .colored(new Color(0.8f, 0.1f, 0.1f, 1f)); blockCount = newCoordinates.size(); } @@ -240,29 +266,29 @@ public class BlockPreviewRenderer { //Draw outlines if no block in hand //Find proper raytrace: either normal range or increased range depending on canBreakFar - VertexConsumer buffer = RenderHandler.beginLines(renderTypeBuffer); - HitResult objectMouseOver = Minecraft.getInstance().hitResult; - HitResult breakingRaytrace = ReachHelper.canBreakFar(player) ? lookingAt : objectMouseOver; - - if (player.isCreative() && noBlockInHand && breakingRaytrace != null && breakingRaytrace.getType() == HitResult.Type.BLOCK) { - BlockHitResult blockBreakingRaytrace = (BlockHitResult) breakingRaytrace; - List breakCoordinates = BuildModifiers.findCoordinates(player, blockBreakingRaytrace.getBlockPos()); - - //Only render first outline if further than normal reach - boolean excludeFirst = objectMouseOver != null && objectMouseOver.getType() == HitResult.Type.BLOCK; - for (int i = excludeFirst ? 1 : 0; i < breakCoordinates.size(); i++) { - BlockPos coordinate = breakCoordinates.get(i); - - BlockState blockState = player.level.getBlockState(coordinate); - if (!blockState.isAir()) { - if (SurvivalHelper.canBreak(player.level, player, coordinate) || i == 0) { - VoxelShape collisionShape = blockState.getCollisionShape(player.level, coordinate); - RenderHandler.renderBlockOutline(matrixStack, buffer, coordinate, collisionShape, new Vec3(0f, 0f, 0f)); - } - } - } - } - RenderHandler.endLines(renderTypeBuffer); +// VertexConsumer vc = RenderHandler.beginLines(buffer); +// HitResult objectMouseOver = Minecraft.getInstance().hitResult; +// HitResult breakingRaytrace = ReachHelper.canBreakFar(player) ? lookingAt : objectMouseOver; +// +// if (player.isCreative() && noBlockInHand && breakingRaytrace != null && breakingRaytrace.getType() == HitResult.Type.BLOCK) { +// BlockHitResult blockBreakingRaytrace = (BlockHitResult) breakingRaytrace; +// List breakCoordinates = BuildModifiers.findCoordinates(player, blockBreakingRaytrace.getBlockPos()); +// +// //Only render first outline if further than normal reach +// boolean excludeFirst = objectMouseOver != null && objectMouseOver.getType() == HitResult.Type.BLOCK; +// for (int i = excludeFirst ? 1 : 0; i < breakCoordinates.size(); i++) { +// BlockPos coordinate = breakCoordinates.get(i); +// +// BlockState blockState = player.level.getBlockState(coordinate); +// if (!blockState.isAir()) { +// if (SurvivalHelper.canBreak(player.level, player, coordinate) || i == 0) { +// VoxelShape collisionShape = blockState.getCollisionShape(player.level, coordinate); +// RenderHandler.renderBlockOutline(ms, vc, coordinate, collisionShape, new Vec3(0f, 0f, 0f)); +// } +// } +// } +// } +// RenderHandler.endLines(buffer); } } @@ -290,9 +316,9 @@ public class BlockPreviewRenderer { if (CompatHelper.isItemBlockProxy(itemstack)) itemstack = CompatHelper.getItemBlockByState(itemstack, blockState); - //Check if can place + //Check if we can place //If check is turned off, check if blockstate is the same (for dissolve effect) - if ((!checkCanPlace /*&& player.world.getNewBlockState(blockPos) == blockState*/) || //TODO enable (breaks the breaking shader) + if ((!checkCanPlace && player.level.getBlockState(blockPos) == blockState) || //TODO enable (breaks the breaking shader) SurvivalHelper.canPlace(player.level, player, blockPos, blockState, itemstack, modifierSettings.doQuickReplace(), Direction.UP)) { RenderHandler.renderBlockPreview(matrixStack, renderTypeBuffer, dispatcher, blockPos, blockState, dissolve, firstPos, secondPos, red); diff --git a/src/main/java/nl/requios/effortlessbuilding/render/RenderHandler.java b/src/main/java/nl/requios/effortlessbuilding/render/RenderHandler.java index d557a00..0f2e5e6 100644 --- a/src/main/java/nl/requios/effortlessbuilding/render/RenderHandler.java +++ b/src/main/java/nl/requios/effortlessbuilding/render/RenderHandler.java @@ -14,6 +14,7 @@ import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.client.resources.model.BakedModel; import net.minecraft.core.BlockPos; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; @@ -22,11 +23,14 @@ import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.RenderLevelLastEvent; import net.minecraftforge.client.event.RenderLevelStageEvent; import net.minecraftforge.client.model.data.ModelData; +import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod.EventBusSubscriber; import nl.requios.effortlessbuilding.EffortlessBuilding; import nl.requios.effortlessbuilding.buildmode.ModeSettingsManager; import nl.requios.effortlessbuilding.buildmodifier.ModifierSettingsManager; +import nl.requios.effortlessbuilding.create.CreateClient; +import nl.requios.effortlessbuilding.create.events.ClientEvents; /*** * Main render class for Effortless Building @@ -34,6 +38,17 @@ import nl.requios.effortlessbuilding.buildmodifier.ModifierSettingsManager; @EventBusSubscriber(Dist.CLIENT) public class RenderHandler { + @SubscribeEvent + public static void onTick(TickEvent.ClientTickEvent event) { + if (!ClientEvents.isGameActive()) return; + + Player player = Minecraft.getInstance().player; + ModeSettingsManager.ModeSettings modeSettings = ModeSettingsManager.getModeSettings(player); + ModifierSettingsManager.ModifierSettings modifierSettings = ModifierSettingsManager.getModifierSettings(player); + + BlockPreviewRenderer.render(null, null, player, modifierSettings, modeSettings); + } + @SubscribeEvent public static void onRender(RenderLevelLastEvent event) { Vec3 cameraPos = Minecraft.getInstance().gameRenderer.getMainCamera().getPosition(); @@ -83,39 +98,44 @@ public class RenderHandler { renderTypeBuffer.endBatch(); } - protected static void renderBlockPreview(PoseStack matrixStack, MultiBufferSource.BufferSource renderTypeBuffer, BlockRenderDispatcher dispatcher, + protected static void renderBlockPreview(PoseStack ms, MultiBufferSource.BufferSource buffer, BlockRenderDispatcher dispatcher, BlockPos blockPos, BlockState blockState, float dissolve, BlockPos firstPos, BlockPos secondPos, boolean red) { if (blockState == null) return; - matrixStack.pushPose(); - matrixStack.translate(blockPos.getX(), blockPos.getY(), blockPos.getZ()); -// matrixStack.rotate(Vector3f.YP.rotationDegrees(-90f)); - matrixStack.translate(-0.01f, -0.01f, -0.01f); - matrixStack.scale(1.02f, 1.02f, 1.02f); + CreateClient.GHOST_BLOCKS.showGhostState(blockPos.toShortString(), blockState) +// .breathingAlpha() + .alpha(0.7f) + .at(blockPos); - //Begin block preview rendering - RenderType blockPreviewRenderType = BuildRenderTypes.getBlockPreviewRenderType(dissolve, blockPos, firstPos, secondPos, red); - VertexConsumer buffer = renderTypeBuffer.getBuffer(blockPreviewRenderType); - - try { - BakedModel model = dispatcher.getBlockModel(blockState); - dispatcher.getModelRenderer().renderModel(matrixStack.last(), buffer, blockState, model, - 1f, 1f, 1f, 0, OverlayTexture.NO_OVERLAY, ModelData.EMPTY, blockPreviewRenderType); - } catch (NullPointerException e) { - EffortlessBuilding.logger.warn("RenderHandler::renderBlockPreview cannot render " + blockState.getBlock().toString()); - - //Render outline as backup, escape out of the current renderstack - matrixStack.popPose(); - renderTypeBuffer.endBatch(); - VertexConsumer lineBuffer = beginLines(renderTypeBuffer); - renderBlockOutline(matrixStack, lineBuffer, blockPos, new Vec3(1f, 1f, 1f)); - endLines(renderTypeBuffer); - buffer = renderTypeBuffer.getBuffer(Sheets.translucentCullBlockSheet()); //any type will do, as long as we have something on the stack - matrixStack.pushPose(); - } - - renderTypeBuffer.endBatch(); - matrixStack.popPose(); +// ms.pushPose(); +// ms.translate(blockPos.getX(), blockPos.getY(), blockPos.getZ()); +//// ms.rotate(Vector3f.YP.rotationDegrees(-90f)); +// ms.translate(-0.01f, -0.01f, -0.01f); +// ms.scale(1.02f, 1.02f, 1.02f); +// +// //Begin block preview rendering +// RenderType blockPreviewRenderType = BuildRenderTypes.getBlockPreviewRenderType(dissolve, blockPos, firstPos, secondPos, red); +// VertexConsumer vc = buffer.getBuffer(blockPreviewRenderType); +// +// try { +// BakedModel model = dispatcher.getBlockModel(blockState); +// dispatcher.getModelRenderer().renderModel(ms.last(), vc, blockState, model, +// 1f, 1f, 1f, 0, OverlayTexture.NO_OVERLAY, ModelData.EMPTY, blockPreviewRenderType); +// } catch (NullPointerException e) { +// EffortlessBuilding.logger.warn("RenderHandler::renderBlockPreview cannot render " + blockState.getBlock().toString()); +// +// //Render outline as backup, escape out of the current renderstack +// ms.popPose(); +// buffer.endBatch(); +// VertexConsumer lineBuffer = beginLines(buffer); +// renderBlockOutline(ms, lineBuffer, blockPos, new Vec3(1f, 1f, 1f)); +// endLines(buffer); +// vc = buffer.getBuffer(Sheets.translucentCullBlockSheet()); //any type will do, as long as we have something on the stack +// ms.pushPose(); +// } +// +// buffer.endBatch(); +// ms.popPose(); } protected static void renderBlockOutline(PoseStack matrixStack, VertexConsumer buffer, BlockPos pos, Vec3 color) { diff --git a/src/main/resources/assets/effortlessbuilding/lang/en_us.json b/src/main/resources/assets/effortlessbuilding/lang/en_us.json index 3a37363..e5da310 100644 --- a/src/main/resources/assets/effortlessbuilding/lang/en_us.json +++ b/src/main/resources/assets/effortlessbuilding/lang/en_us.json @@ -11,12 +11,12 @@ "key.effortlessbuilding.redo.desc": "Redo", "key.effortlessbuilding.altplacement.desc": "Alternative placement", - "effortlessbuilding:randomizer_bag": "Leather Randomizer Bag", - "effortlessbuilding:golden_randomizer_bag": "Golden Randomizer Bag", - "effortlessbuilding:diamond_randomizer_bag": "Diamond Randomizer Bag", - "effortlessbuilding:reach_upgrade1": "Reach Upgrade 1", - "effortlessbuilding:reach_upgrade2": "Reach Upgrade 2", - "effortlessbuilding:reach_upgrade3": "Reach Upgrade 3", + "item.effortlessbuilding.randomizer_bag": "Leather Randomizer Bag", + "item.effortlessbuilding.golden_randomizer_bag": "Golden Randomizer Bag", + "item.effortlessbuilding.diamond_randomizer_bag": "Diamond Randomizer Bag", + "item.effortlessbuilding.reach_upgrade1": "Reach Upgrade 1", + "item.effortlessbuilding.reach_upgrade2": "Reach Upgrade 2", + "item.effortlessbuilding.reach_upgrade3": "Reach Upgrade 3", "effortlessbuilding.mode.normal": "Disable", "effortlessbuilding.mode.normal_plus": "Single",