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,165 @@
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.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.Method;
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;
protected List<Class<?>[]> paramTypes;
protected String documentation = StringUtils.EMPTY;
private List<MethodNode> methodNodes;
private Boolean hasTextureBinder;

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);
}

protected final void addSignature(Class<?>... types) {
this.paramTypes.add(types);
}

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();
}

public abstract Result<T> getDefaultValue();


public void provideCompletion(int index, Completions items) {}

public void bindTexture(T t) {}

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

public boolean hasTextureBinder() {
if (this.hasTextureBinder == null) {
for (Method method : getClass().getDeclaredMethods()) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

why does this need reflection?

Copy link
Member Author

Choose a reason for hiding this comment

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

Its a default implementation to check if the method is overriden. Only then the texture binder can be defined

if (method.getName().equals("bindTexture") && method.getParameterTypes().length == 1 && this.returnType.isAssignableFrom(method.getParameterTypes()[0])) {
this.hasTextureBinder = true;
return true;
}
}
this.hasTextureBinder = false;
}
return this.hasTextureBinder;
}

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,49 @@
package com.cleanroommc.groovyscript.mapper;

import com.cleanroommc.groovyscript.api.Result;
import com.cleanroommc.groovyscript.compat.mods.GroovyContainer;
import com.cleanroommc.groovyscript.server.Completions;
import net.minecraft.block.state.IBlockState;
import net.minecraft.init.Blocks;
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 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);
}

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

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

@Override
public void provideCompletion(int index, 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

}

@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());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.cleanroommc.groovyscript.mapper;

import com.cleanroommc.groovyscript.api.Result;
import com.cleanroommc.groovyscript.compat.mods.GroovyContainer;
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, 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());
}
}
Loading