Skip to content

Commit

Permalink
Add hints
Browse files Browse the repository at this point in the history
  • Loading branch information
Matyrobbrt committed Oct 26, 2024
1 parent eb7eb93 commit b7877d5
Show file tree
Hide file tree
Showing 11 changed files with 200 additions and 43 deletions.
2 changes: 2 additions & 0 deletions src/generated/resources/assets/ltminigames/lang/en_ud.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@
"ltminigames.minigame.chaos_block_party": "ʎʇɹɐԀ ʞɔoןᗺ soɐɥƆ",
"ltminigames.minigame.conservation_exploration": "uoıʇɐɹoןdxƎ uoıʇɐʌɹǝsuoƆ",
"ltminigames.minigame.crafting_bee.dont_cheat": "¡ʇɐǝɥɔ ʇ,uoᗡ",
"ltminigames.minigame.crafting_bee.hint": "sʇuǝıpǝɹbuı ɟo ɹǝqɯnu ɯopuɐɹ ɐ ɟo uoıʇısod ǝɥʇ buıʎɐןdsıp 'ʇuıɥ ɐ ʍoɥs oʇ ʞɔıןƆ",
"ltminigames.minigame.crafting_bee.hints_left": "ʇɟǝן sʇuıɥ %s ǝʌɐɥ noʎ",
"ltminigames.minigame.crafting_bee.team_has_completed_recipes": "sǝdıɔǝɹ %s ɟo ʇno %s pǝʇǝןdɯoɔ sɐɥ %s ɯɐǝ⟘",
"ltminigames.minigame.donation.acid_rain": "uıɐᴚ pıɔⱯ",
"ltminigames.minigame.donation.acid_rain.description": "¡ǝʇnuıɯ Ɩ ɹoɟ spɐǝɥ ,sɹǝʎɐןd ǝɥʇ uo uʍop pıɔɐ uıɐᴚ",
Expand Down
2 changes: 2 additions & 0 deletions src/generated/resources/assets/ltminigames/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@
"ltminigames.minigame.chaos_block_party": "Chaos Block Party",
"ltminigames.minigame.conservation_exploration": "Conservation Exploration",
"ltminigames.minigame.crafting_bee.dont_cheat": "Don't cheat!",
"ltminigames.minigame.crafting_bee.hint": "Click to show a hint, displaying the position of a random number of ingredients",
"ltminigames.minigame.crafting_bee.hints_left": "You have %s hints left",
"ltminigames.minigame.crafting_bee.team_has_completed_recipes": "Team %s has completed %s out of %s recipes",
"ltminigames.minigame.donation.acid_rain": "Acid Rain",
"ltminigames.minigame.donation.acid_rain.description": "Rain acid down on the players' heads for 1 minute!",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.lovetropics.minigames.LoveTropics;
import com.lovetropics.minigames.client.game.ClientGameStateManager;
import com.lovetropics.minigames.common.content.crafting_bee.CraftingBeeTexts;
import com.lovetropics.minigames.common.content.crafting_bee.SelectedRecipe;
import com.lovetropics.minigames.common.core.game.client_state.GameClientStateTypes;
import com.lovetropics.minigames.common.core.game.client_state.instance.CraftingBeeCrafts;
import com.mojang.blaze3d.platform.GlStateManager;
Expand All @@ -14,38 +15,98 @@
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.AbstractWidget;
import net.minecraft.client.gui.narration.NarrationElementOutput;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.inventory.CraftingScreen;
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.RenderStateShard;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.NonNullList;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.inventory.tooltip.TooltipComponent;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.client.event.RegisterClientTooltipComponentFactoriesEvent;
import net.neoforged.neoforge.client.event.ScreenEvent;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;
import java.util.UUID;
import java.util.function.Predicate;

@EventBusSubscriber(modid = LoveTropics.ID, value = Dist.CLIENT)
public class GameCraftingBeeHandler {
// TODO - hints
private static int hintsRemaining = 3;
private static int hintsRemaining;
private static UUID lastKnownGame;
private static Map<ResourceLocation, RecipeHint> hintGrids;

private static final ResourceLocation TEXTURE = ResourceLocation.fromNamespaceAndPath("ltminigames", "textures/gui/minigames/crafting_bee/items_bar.png");
private static final ResourceLocation GRID_TEXTURE = ResourceLocation.fromNamespaceAndPath("ltminigames", "textures/gui/minigames/crafting_bee/crafting_grid.png");

@EventBusSubscriber(modid = LoveTropics.ID, value = Dist.CLIENT, bus = EventBusSubscriber.Bus.MOD)
public static class ModSubscriber {
@SubscribeEvent
static void onRegisterTooltips(final RegisterClientTooltipComponentFactoriesEvent event) {
event.register(RecipeHint.class, recipeHint -> new ClientTooltipComponent() {
@Override
public int getHeight() {
return 58;
}

@Override
public int getWidth(Font font) {
return 54;
}

@Override
public void renderImage(Font font, int x, int y, GuiGraphics guiGraphics) {
guiGraphics.blit(GRID_TEXTURE, x, y, 0, 0, 54, 54, 54, 54);
for (int i = 0; i < recipeHint.grid().size(); i++) {
var ingredient = recipeHint.grid.get(i);
if (ingredient.isEmpty()) continue;

var size = recipeHint.grid.size() == 4 ? 2 : 3;

guiGraphics.renderFakeItem(
resolveIngredient(ingredient),
x + 1 + 18 * (i % size),
y + 1 + 18 * (i / size)
);
}
}
});
}
}

@SubscribeEvent
static void onGuiInit(ScreenEvent.Init.Post event) {
if (getState() == null || !(event.getScreen() instanceof CraftingScreen screen)) return;

var state = getState();
if (!Objects.equals(state.gameId(), lastKnownGame)) {
lastKnownGame = state.gameId();
hintsRemaining = state.allowedHints();
hintGrids = new HashMap<>();
}

event.addListener(new AbstractWidget(screen.getGuiLeft() + 22, screen.getGuiTop() - 21, 132, 21, Component.empty()) {
@Override
protected void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
Expand All @@ -57,16 +118,66 @@ protected void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, flo
renderItem(guiGraphics, craft.output(), x, this.getY() + 4, 0, 0, 1f, 1f, 1f, craft.done() ? .1f : 1f);

if (mouseX >= x && mouseX <= x + 16 && mouseY >= getY() + 4 && mouseY <= getY() + 20) {
var hint = hintGrids.get(craft.recipeId());

var tooltipLines = new ArrayList<>(Screen.getTooltipFromItem(Minecraft.getInstance(), craft.output()));
if (craft.done()) {
tooltipLines.set(0, tooltipLines.get(0).copy().withStyle(ChatFormatting.GREEN));
} else {
} else if (hint == null || hint.expectedIngredientCount() != hint.grid().stream().filter(Predicate.not(Ingredient::isEmpty)).count()) {
tooltipLines.add(CraftingBeeTexts.HINT);
tooltipLines.add(CraftingBeeTexts.HINTS_LEFT.apply(Component.literal(String.valueOf(hintsRemaining)).withStyle(ChatFormatting.AQUA)));
}
guiGraphics.renderTooltip(Minecraft.getInstance().font, tooltipLines, craft.output().getTooltipImage(), mouseX, mouseY);
guiGraphics.renderTooltip(Minecraft.getInstance().font, tooltipLines, Optional.<TooltipComponent>ofNullable(hint).filter($ -> !craft.done()), mouseX, mouseY);
}
}
}

@Override
public void onClick(double mouseX, double mouseY, int button) {
if (hintsRemaining <= 0) return;

var crafts = getState().crafts();

if (mouseY < getY() + 4 || mouseY > getY() + 4 + 16) return;
if (mouseX < getX() + 4 || mouseX > getX() + 4 + (18 * crafts.size() - 1)) return;
var index = (int)(mouseX - getX() - 4) / 18;

var craft = crafts.get(index);
if (craft.done()) return;

var recipe = new SelectedRecipe(craft.recipeId(), Minecraft.getInstance().player.connection.getRecipeManager());
var ingredients = recipe.decompose();

var grid = hintGrids.computeIfAbsent(craft.recipeId(), k -> new RecipeHint(
NonNullList.withSize(
recipe.recipe().map(shaped -> shaped.getWidth() * shaped.getHeight(), shapeless -> shapeless.getIngredients().size() > 3 ? 9 : shapeless.getIngredients().size()),
Ingredient.EMPTY),
(int)ingredients.stream().filter(Predicate.not(Ingredient::isEmpty)).count()
));

int filledGridAmount = (int) grid.grid().stream().filter(Predicate.not(Ingredient::isEmpty)).count();
if (grid.expectedIngredientCount() == filledGridAmount) return;

record PositionedIngredient(Ingredient ingredient, int position) {}

List<PositionedIngredient> ingredientsToPick = new ArrayList<>();
for (int i = 0; i < ingredients.size(); i++) {
var ingr = ingredients.get(i);
if (!ingr.isEmpty() && grid.grid().get(i).isEmpty()) {
ingredientsToPick.add(new PositionedIngredient(ingr, i));
}
}

Collections.shuffle(ingredientsToPick);
// Make sure that we never show the full recipe in just one hint
var ingredientsToShow = new Random().nextInt(filledGridAmount == 0 ? Math.max(1, ingredientsToPick.size() - 1) : ingredientsToPick.size());

for (int i = 0; i <= ingredientsToShow; i++) {
var ingredient = ingredientsToPick.get(i);
grid.grid().set(ingredient.position(), ingredient.ingredient());
}

hintsRemaining--;
}

@Override
Expand All @@ -81,6 +192,19 @@ private static CraftingBeeCrafts getState() {
return ClientGameStateManager.getOrNull(GameClientStateTypes.CRAFTING_BEE_CRAFTS);
}

private static ItemStack resolveIngredient(Ingredient ingredient) {
if (ingredient.isEmpty()) {
return ItemStack.EMPTY;
}
for (ItemStack item : ingredient.getItems()) {
// Prioritize vanilla items
if (item.getItem().builtInRegistryHolder().key().location().getNamespace().equals(ResourceLocation.DEFAULT_NAMESPACE)) {
return item;
}
}
return ingredient.getItems()[0];
}

public static void reset() {

}
Expand Down Expand Up @@ -186,4 +310,10 @@ public TintedVertexConsumer(VertexConsumer wrapped, float red, float green, floa
this.alpha = alpha;
}
}

public record RecipeHint(
NonNullList<Ingredient> grid,
int expectedIngredientCount
) implements TooltipComponent {}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
import com.lovetropics.minigames.common.core.game.state.team.GameTeam;
import com.lovetropics.minigames.common.core.game.state.team.GameTeamKey;
import com.lovetropics.minigames.common.core.game.state.team.TeamState;
import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.ChatFormatting;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.TickTask;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Player;
Expand All @@ -33,28 +33,33 @@

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class CraftingBeeBehavior implements IGameBehavior {
public static final MapCodec<CraftingBeeBehavior> CODEC = RecordCodecBuilder.mapCodec(in -> in.group(
RecipeSelector.CODEC.codec().listOf().fieldOf("selectors").forGetter(c -> c.selectors),
IngredientDecomposer.CODEC.codec().listOf().fieldOf("decomposers").forGetter(c -> c.decomposers)
IngredientDecomposer.CODEC.codec().listOf().fieldOf("decomposers").forGetter(c -> c.decomposers),
Codec.INT.optionalFieldOf("hints_per_player", 3).forGetter(c -> c.allowedHints)
).apply(in, CraftingBeeBehavior::new));

private final List<RecipeSelector> selectors;
private final List<IngredientDecomposer> decomposers;
private final int allowedHints;

private TeamState teams;
private IGamePhase game;

private ListMultimap<GameTeamKey, CraftingTask> tasks;

public CraftingBeeBehavior(List<RecipeSelector> selectors, List<IngredientDecomposer> decomposers) {
public CraftingBeeBehavior(List<RecipeSelector> selectors, List<IngredientDecomposer> decomposers, int allowedHints) {
this.selectors = selectors;
this.decomposers = decomposers;
this.allowedHints = allowedHints;
}

@Override
Expand Down Expand Up @@ -91,7 +96,8 @@ private void distributeIngredients(Collection<CraftingTask> tasks, PlayerSet pla

for (CraftingTask task : tasks) {
var ingredients = task.recipe.decompose();
var items = ingredients.stream().flatMap(this::singleDecomposition).toList();
var items = ingredients.stream().flatMap(this::singleDecomposition).collect(Collectors.toCollection(ArrayList::new));
Collections.shuffle(items);

// Evenly distribute the items between the players
int p = 0;
Expand All @@ -104,13 +110,14 @@ private void distributeIngredients(Collection<CraftingTask> tasks, PlayerSet pla
}

private Stream<ItemStack> singleDecomposition(Ingredient ingredient) {
if (ingredient.isEmpty()) return Stream.empty();

for (IngredientDecomposer decomposer : decomposers) {
var decomposed = decomposer.decompose(ingredient);
if (decomposed != null) {
return decomposed.stream().flatMap(this::singleDecomposition);
}
}
if (ingredient.getItems().length == 0) return Stream.empty();

// We have reduced the ingredient to its most basic form, so now we just pick the first item of the ingredient
for (ItemStack item : ingredient.getItems()) {
Expand All @@ -128,7 +135,7 @@ private void onCraft(Player player, ItemStack crafted, Container container) {

var teamTasks = tasks.get(team);
var task = teamTasks.stream().filter(c -> ItemStack.isSameItemSameComponents(crafted, c.output)).findFirst().orElse(null);
if (task == null) return;
if (task == null || task.done) return;

task.done = true;

Expand Down Expand Up @@ -157,7 +164,8 @@ private void sync(GameTeamKey team) {

private void sync(Player player) {
if (player instanceof ServerPlayer sp) {
GameClientState.sendToPlayer(new CraftingBeeCrafts(tasks.get(teams.getTeamForPlayer(player)).stream().map(CraftingTask::toCraft).toList()), sp);
GameClientState.sendToPlayer(new CraftingBeeCrafts(tasks.get(teams.getTeamForPlayer(player)).stream().map(CraftingTask::toCraft).toList(),
game.gameUuid(), allowedHints), sp);
}
}

Expand All @@ -168,10 +176,10 @@ public Supplier<? extends GameBehaviorType<?>> behaviorType() {

private static class CraftingTask {
private final ItemStack output;
private final RecipeSelector.SelectedRecipe recipe;
private final SelectedRecipe recipe;
private boolean done;

private CraftingTask(ItemStack output, RecipeSelector.SelectedRecipe recipe) {
private CraftingTask(ItemStack output, SelectedRecipe recipe) {
this.output = output;
this.recipe = recipe;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.lovetropics.minigames.common.content.crafting_bee;

import com.lovetropics.minigames.LoveTropics;
import com.lovetropics.minigames.common.content.MinigameTexts;
import com.lovetropics.minigames.common.core.game.util.TranslationCollector;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
Expand All @@ -11,6 +10,6 @@ public final class CraftingBeeTexts {

public static final TranslationCollector.Fun3 TEAM_HAS_COMPLETED_RECIPES = KEYS.add3("team_has_completed_recipes", "Team %s has completed %s out of %s recipes");
public static final Component DONT_CHEAT = KEYS.add("dont_cheat", "Don't cheat!").withStyle(ChatFormatting.RED);
public static final Component HINT = MinigameTexts.KEYS.add("hint", "Click to show a hint, displaying the position of a random number of ingredients");
public static final TranslationCollector.Fun1 HINTS_LEFT = MinigameTexts.KEYS.add1("hints_left", "You have %s hints left");
public static final Component HINT = KEYS.add("hint", "Click to show a hint, displaying the position of a random number of ingredients");
public static final TranslationCollector.Fun1 HINTS_LEFT = KEYS.add1("hints_left", "You have %s hints left");
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,11 @@

import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import com.mojang.datafixers.util.Either;
import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
import net.minecraft.Util;
import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.ShapedRecipe;
import net.minecraft.world.item.crafting.ShapelessRecipe;

import java.util.List;

Expand All @@ -25,20 +18,6 @@ public interface RecipeSelector {

MapCodec<? extends RecipeSelector> getType();

record SelectedRecipe(ResourceLocation id, Either<ShapedRecipe, ShapelessRecipe> recipe) {
public SelectedRecipe(RecipeHolder<?> holder) {
this(holder.id(), holder.value() instanceof ShapedRecipe sr ? Either.left(sr) : Either.right((ShapelessRecipe) holder.value()));
}

public ItemStack getResult(RegistryAccess access) {
return recipe.map(rp -> rp.getResultItem(access), rp -> rp.getResultItem(access));
}

public List<Ingredient> decompose() {
return recipe.map(ShapedRecipe::getIngredients, ShapelessRecipe::getIngredients);
}
}

record FromList(List<ResourceLocation> recipes) implements RecipeSelector {
public static final MapCodec<FromList> CODEC = ResourceLocation.CODEC.listOf().fieldOf("recipes")
.xmap(FromList::new, FromList::recipes);
Expand Down
Loading

0 comments on commit b7877d5

Please sign in to comment.