Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mapper backend refactor and texture deco improvements #280

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
4 changes: 2 additions & 2 deletions src/main/java/com/cleanroommc/groovyscript/GroovyScript.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import com.cleanroommc.groovyscript.event.EventHandler;
import com.cleanroommc.groovyscript.helper.JsonHelper;
import com.cleanroommc.groovyscript.helper.StyleConstant;
import com.cleanroommc.groovyscript.mapper.ObjectMapper;
import com.cleanroommc.groovyscript.mapper.AbstractObjectMapper;
import com.cleanroommc.groovyscript.mapper.ObjectMapperManager;
import com.cleanroommc.groovyscript.network.CReload;
import com.cleanroommc.groovyscript.network.NetworkHandler;
Expand Down Expand Up @@ -153,7 +153,7 @@ public static void initializeGroovyPreInit() {
StandardInfoParserRegistry.init();
VanillaModule.initializeBinding();
ModSupport.init();
for (ObjectMapper<?> goh : ObjectMapperManager.getObjectMappers()) {
for (AbstractObjectMapper<?> goh : ObjectMapperManager.getObjectMappers()) {
getSandbox().registerBinding(goh);
}
if (FMLLaunchHandler.isDeobfuscatedEnvironment()) Documentation.generate();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package com.cleanroommc.groovyscript.mapper;

import com.cleanroommc.groovyscript.api.GroovyLog;
import com.cleanroommc.groovyscript.api.INamed;
import com.cleanroommc.groovyscript.api.IObjectParser;
import com.cleanroommc.groovyscript.api.Result;
import com.cleanroommc.groovyscript.compat.mods.GroovyContainer;
import com.cleanroommc.groovyscript.helper.ArrayUtils;
import com.cleanroommc.groovyscript.sandbox.expand.IDocumented;
import com.cleanroommc.groovyscript.server.CompletionParams;
import com.cleanroommc.groovyscript.server.Completions;
import groovy.lang.Closure;
import groovy.lang.groovydoc.Groovydoc;
import groovy.lang.groovydoc.GroovydocHolder;
import org.apache.commons.lang3.StringUtils;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.Parameter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.Modifier;
import java.util.*;

public abstract class AbstractObjectMapper<T> extends Closure<T> implements INamed, IDocumented, IObjectParser<T>, TextureBinder<T> {

private final String name;
private final GroovyContainer<?> mod;
private final Class<T> returnType;
private final List<Class<?>[]> paramTypes;
protected String documentation = StringUtils.EMPTY;
private List<MethodNode> methodNodes;

protected AbstractObjectMapper(String name, GroovyContainer<?> mod, Class<T> returnType) {
super(null);
this.name = name;
this.mod = mod;
this.returnType = returnType;
this.paramTypes = new ArrayList<>();
addSignature(String.class);
}

/**
* Call in ctor to configure signatures
*/
protected final void clearSignatures() {
this.paramTypes.clear();
this.methodNodes = null;
}

/**
* Call in ctor to configure signatures.
* By default, only `name(String)` exists.
*/
protected final void addSignature(Class<?>... types) {
this.paramTypes.add(types);
this.methodNodes = null;
}

public final T doCall(String s, Object... args) {
return invokeWithDefault(false, s, args);
}

public final T doCall() {
return invokeDefault();
}

@Nullable
public final T invoke(boolean silent, String s, Object... args) {
Result<T> t = Objects.requireNonNull(parse(s, args), "Object mapper must return a non null result!");
if (t.hasError()) {
if (!silent) {
if (this.mod == null) {
GroovyLog.get().error("Can't find {} for name {}!", name, s);
} else {
GroovyLog.get().error("Can't find {} {} for name {}!", mod, name, s);
}
if (t.getError() != null && !t.getError().isEmpty()) {
GroovyLog.get().error(" - reason: {}", t.getError());
}
}
return null;
}
return Objects.requireNonNull(t.getValue(), "Object mapper result must contain a non-null value!");
}

public final T invokeWithDefault(boolean silent, String s, Object... args) {
T t = invoke(silent, s, args);
return t != null ? t : invokeDefault();
}

public final T invokeDefault() {
Result<T> t = getDefaultValue();
return t == null || t.hasError() ? null : t.getValue();
}

/**
* Returns a default value for this mapper. This is called every time the parser returns an errored result.
*
* @return default value of this mapper. May be null
*/
public abstract Result<T> getDefaultValue();

/**
* Adds all possible values this mapper can have at a param position.
* For example the `item()` mapper adds all item registry names when the index is 0.
*
* @param index the index of the param to complete
* @param params the values of all current (constant) params of the mapper
* @param items a list of completion items
*/
public void provideCompletion(int index, CompletionParams params, Completions items) {}

/**
* Draws an image representation of the given object. This is used for lsp.
* The icon will show up in VSC or other code editors with compat.
* If this is implemented, {@link #hasTextureBinder()} must return true. Otherwise, this will not be used.
*
* @param t object for which a texture should be drawn.
*/
public void bindTexture(T t) {}

/**
* Determines if {@link #bindTexture(Object)} is implemented and should be used.
*
* @return true if this mapper can bind textures
*/
public abstract boolean hasTextureBinder();

@NotNull
public List<String> getTooltip(T t) {
return Collections.emptyList();
}

public List<MethodNode> getMethodNodes() {
if (methodNodes == null) {
this.methodNodes = new ArrayList<>();
for (Class<?>[] paramType : this.paramTypes) {
Parameter[] params = ArrayUtils.map(
paramType,
c -> new Parameter(ClassHelper.makeCached(c), ""),
new Parameter[paramType.length]);
MethodNode node = new MethodNode(
this.name,
Modifier.PUBLIC | Modifier.FINAL,
ClassHelper.makeCached(this.returnType),
params,
null,
null);
node.setDeclaringClass(
this.mod != null ? ClassHelper.makeCached(this.mod.get().getClass()) : ClassHelper.makeCached(ObjectMapperManager.class));
node.setNodeMetaData(GroovydocHolder.DOC_COMMENT, new Groovydoc(getDocumentation(), node));
this.methodNodes.add(node);
}
}
return methodNodes;
}

@Override
public String getName() {
return name;
}

@Override
public Collection<String> getAliases() {
return Collections.singleton(this.name);
}

public GroovyContainer<?> getMod() {
return mod;
}

public Class<T> getReturnType() {
return returnType;
}

public List<Class<?>[]> getParamTypes() {
return paramTypes;
}

@Override
public final String getDocumentation() {
return documentation;
}

protected final String docOfType(String type) {
String mod = this.mod == null ? StringUtils.EMPTY : this.mod.getContainerName() + ' ';
return "returns a " + mod + type;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.cleanroommc.groovyscript.mapper;

import com.cleanroommc.groovyscript.api.Result;
import com.cleanroommc.groovyscript.compat.mods.GroovyContainer;
import com.cleanroommc.groovyscript.server.CompletionParams;
import com.cleanroommc.groovyscript.server.Completions;
import net.minecraft.block.Block;
import net.minecraft.block.properties.IProperty;
import net.minecraft.block.state.IBlockState;
import net.minecraft.init.Blocks;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.common.registry.ForgeRegistries;
import net.prominic.groovyls.util.CompletionItemFactory;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionItemKind;
import org.jetbrains.annotations.NotNull;

import java.util.Collections;
import java.util.List;

public class BlockStateMapper extends AbstractObjectMapper<IBlockState> {

public static final BlockStateMapper INSTANCE = new BlockStateMapper("blockstate", null);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this is the only place you are going to init BlockStateMapper, why pass null here and not drop and argument and call super(name, null, IBlockState.class)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was doing that for item so that mods can extend that class for a template (for example gregtech metaitems). I guess its not very useful for blockstate


protected BlockStateMapper(String name, GroovyContainer<?> mod) {
super(name, mod, IBlockState.class);
addSignature(String.class, int.class);
addSignature(String.class, String[].class);
this.documentation = docOfType("block state");
}

@Override
public Result<IBlockState> getDefaultValue() {
return Result.some(Blocks.AIR.getDefaultState());
}

@Override
public @NotNull Result<IBlockState> parse(String mainArg, Object[] args) {
brachy84 marked this conversation as resolved.
Show resolved Hide resolved
return ObjectMappers.parseBlockState(mainArg, args);
}

@Override
public void provideCompletion(int index, CompletionParams params, Completions items) {
if (index == 0) items.addAllOfRegistry(ForgeRegistries.BLOCKS);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible to have completion for properties of blockstates?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about that, but we would need information about what the first param currently is

if (index >= 1 && params.isParamType(0, String.class)) {
Block block = ForgeRegistries.BLOCKS.getValue(new ResourceLocation(params.getParamAsType(0, String.class)));
if (block != null) {
// TODO completions for ints doesnt work properly
/*items.addAll(block.getBlockState().getValidStates(), state -> {
return CompletionItemFactory.createCompletion(CompletionItemKind.Value, String.valueOf(state.getBlock().getMetaFromState(state)));
});*/
for (IProperty property : block.getBlockState().getProperties()) {
items.addAll(property.getAllowedValues(), val -> {
CompletionItem item = CompletionItemFactory.createCompletion(CompletionItemKind.Constant, property.getName() + "=" + property.getName((Comparable) val));
return item;
});
}
}
}
}

@Override
public void bindTexture(IBlockState iBlockState) {
ItemStack itemStack = new ItemStack(iBlockState.getBlock(), 1, iBlockState.getBlock().getMetaFromState(iBlockState));
TextureBinder.ofItem().bindTexture(itemStack);
}

@Override
public @NotNull List<String> getTooltip(IBlockState iBlockState) {
ItemStack itemStack = new ItemStack(iBlockState.getBlock(), 1, iBlockState.getBlock().getMetaFromState(iBlockState));
return Collections.singletonList(itemStack.getDisplayName());
}

@Override
public boolean hasTextureBinder() {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.cleanroommc.groovyscript.mapper;

import com.cleanroommc.groovyscript.api.Result;
import com.cleanroommc.groovyscript.compat.mods.GroovyContainer;
import com.cleanroommc.groovyscript.server.CompletionParams;
import com.cleanroommc.groovyscript.server.Completions;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.item.ItemStack;
import net.minecraftforge.fml.common.registry.ForgeRegistries;
import org.jetbrains.annotations.NotNull;

import java.util.Collections;
import java.util.List;

public class ItemStackMapper extends AbstractObjectMapper<ItemStack> {

public static final ItemStackMapper INSTANCE = new ItemStackMapper("item", null);

protected ItemStackMapper(String name, GroovyContainer<?> mod) {
super(name, mod, ItemStack.class);
addSignature(String.class, int.class);
this.documentation = docOfType("item stack");
}

@Override
public Result<ItemStack> getDefaultValue() {
return Result.some(ItemStack.EMPTY);
}

@Override
public @NotNull Result<ItemStack> parse(String mainArg, Object[] args) {
return ObjectMappers.parseItemStack(mainArg, args);
}

@Override
public void provideCompletion(int index, CompletionParams params, Completions items) {
if (index == 0) items.addAllOfRegistry(ForgeRegistries.ITEMS);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible for the valid metadata of items to have autocompletion? pretty sure its a "no"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is possible, but id argue that its not worth it

Copy link
Member Author

@brachy84 brachy84 Jan 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was not possible. Now it might be. But idk how to trigger completion for ints

}

@Override
public void bindTexture(ItemStack item) {
GlStateManager.enableDepth();
RenderHelper.enableGUIStandardItemLighting();
var mc = Minecraft.getMinecraft();
var fontRenderer = item.getItem().getFontRenderer(item);
if (fontRenderer == null) fontRenderer = mc.fontRenderer;
mc.getRenderItem().renderItemAndEffectIntoGUI(null, item, 0, 0);
mc.getRenderItem().renderItemOverlayIntoGUI(fontRenderer, item, 0, 0, null);
GlStateManager.disableBlend();
RenderHelper.disableStandardItemLighting();
GlStateManager.enableAlpha();
GlStateManager.disableDepth();
}

@Override
public @NotNull List<String> getTooltip(ItemStack itemStack) {
return Collections.singletonList(itemStack.getDisplayName());
}

@Override
public boolean hasTextureBinder() {
return true;
}
}
Loading