Skip to content

Commit

Permalink
Use a collection illustration type instead of lists
Browse files Browse the repository at this point in the history
This allows more flexible illustration definitions in the map.
  • Loading branch information
SekoiaTree committed Feb 12, 2024
1 parent 5d430e0 commit 0ebf44a
Show file tree
Hide file tree
Showing 13 changed files with 151 additions and 48 deletions.
3 changes: 3 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ Updated to 1.20.4
**Mod Interactions**
- REI no longer appears on the RPG dialogue screen variant

**Additions**
- Dialogues can now render extra features, such as entities (either non-existent, or via a selector), or items.

------------------------------------------------------
Version 1.3.1
------------------------------------------------------
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/ladysnake/blabber/Blabber.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.ladysnake.blabber.api.DialogueActionV2;
import org.ladysnake.blabber.api.DialogueIllustrationType;
import org.ladysnake.blabber.impl.common.*;
import org.ladysnake.blabber.impl.common.illustrations.DialogueIllustrationCollection;
import org.ladysnake.blabber.impl.common.illustrations.DialogueIllustrationItem;
import org.ladysnake.blabber.impl.common.illustrations.DialogueIllustrationNbtEntity;
import org.ladysnake.blabber.impl.common.illustrations.DialogueIllustrationSelectorEntity;
Expand Down Expand Up @@ -152,6 +153,7 @@ public void onInitialize() {
BlabberRegistrar.init();
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> BlabberCommand.register(dispatcher));
registerAction(id("command"), CommandDialogueAction.CODEC);
registerIllustration(id("list"), DialogueIllustrationCollection.TYPE);
registerIllustration(id("item"), DialogueIllustrationItem.TYPE);
registerIllustration(id("entity_nbt"), DialogueIllustrationNbtEntity.TYPE);
registerIllustration(id("entity"), DialogueIllustrationSelectorEntity.TYPE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@

import com.mojang.serialization.Codec;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.util.dynamic.Codecs;
import org.jetbrains.annotations.ApiStatus;
import org.ladysnake.blabber.impl.common.BlabberRegistrar;
import org.ladysnake.blabber.impl.common.illustrations.DialogueIllustrationCollection;

import java.util.function.BiConsumer;
import java.util.function.Function;
Expand All @@ -30,8 +32,13 @@
* @param <T> the DialogueIllustration type this type creates
*/
public final class DialogueIllustrationType<T extends DialogueIllustration> {
public static final Codec<DialogueIllustration> CODEC = BlabberRegistrar.ILLUSTRATION_REGISTRY.getCodec()
.dispatch("type", DialogueIllustration::getType, DialogueIllustrationType::getCodec);
public static final Codec<DialogueIllustration> CODEC = Codecs.createRecursive("illustration_type", self ->
Codecs.alternatively(
BlabberRegistrar.ILLUSTRATION_REGISTRY.getCodec()
.dispatch("type", DialogueIllustration::getType, DialogueIllustrationType::getCodec),
Codec.list(self).xmap(DialogueIllustrationCollection::new, DialogueIllustrationCollection::sub)
)
);

private final Codec<T> codec;
private final Function<PacketByteBuf, T> read;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,11 +221,9 @@ public void render(DrawContext context, int mouseX, int mouseY, float tickDelta)
int y = mainTextMinY;

for (String illustrationList : this.handler.getCurrentIllustrations()) {
List<DialogueIllustration> illustrations = this.handler.getIllustrations().get(illustrationList);
if (illustrations != null) {
for (DialogueIllustration illustration : illustrations) {
illustration.render(context, this.textRenderer, 0, 0, mouseX, mouseY, tickDelta);
}
DialogueIllustration illustration = this.handler.getIllustrations().get(illustrationList);
if (illustration != null) {
illustration.render(context, this.textRenderer, 0, 0, mouseX, mouseY, tickDelta);
}
}

Expand All @@ -243,11 +241,9 @@ public void render(DrawContext context, int mouseX, int mouseY, float tickDelta)
context.drawTextWrapped(this.textRenderer, choice.text(), choiceListMinX, y, choiceListMaxWidth, choiceColor);

for (String illustrationList : choice.illustrations()) {
List<DialogueIllustration> illustrations = this.handler.getIllustrations().get(illustrationList);
if (illustrations != null) {
for (DialogueIllustration illustration : illustrations) {
illustration.render(context, this.textRenderer, choiceListMinX, y, mouseX, mouseY, tickDelta);
}
DialogueIllustration illustration = this.handler.getIllustrations().get(illustrationList);
if (illustration != null) {
illustration.render(context, this.textRenderer, choiceListMinX, y, mouseX, mouseY, tickDelta);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public List<String> getCurrentIllustrations() {
return this.dialogue.getCurrentIllustrations();
}

public Map<String, List<DialogueIllustration>> getIllustrations() {
public Map<String, DialogueIllustration> getIllustrations() {
return this.dialogue.getIllustrations();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Blabber
* Copyright (C) 2022-2024 Ladysnake
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; If not, see <https://www.gnu.org/licenses>.
*/
package org.ladysnake.blabber.impl.common.illustrations;

import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.entity.Entity;
import net.minecraft.server.command.ServerCommandSource;
import org.jetbrains.annotations.Nullable;
import org.ladysnake.blabber.api.DialogueIllustration;
import org.ladysnake.blabber.api.DialogueIllustrationType;
import org.ladysnake.blabber.impl.common.BlabberRegistrar;

import java.util.ArrayList;
import java.util.List;

public record DialogueIllustrationCollection(List<DialogueIllustration> sub) implements DialogueIllustration {
private static final Codec<DialogueIllustrationCollection> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Codec.list(DialogueIllustrationType.CODEC).fieldOf("sub").forGetter(DialogueIllustrationCollection::sub)
).apply(instance, DialogueIllustrationCollection::new));

public static final DialogueIllustrationType<DialogueIllustrationCollection> TYPE = new DialogueIllustrationType<>(
CODEC,
buf -> new DialogueIllustrationCollection(buf.readList(b -> {
DialogueIllustrationType<?> type = b.readRegistryValue(BlabberRegistrar.ILLUSTRATION_REGISTRY);
assert type != null;
return type.readFromPacket(b);
})),
(buf, item) ->
buf.writeCollection(item.sub, (b, i) -> {
// Write the type, then the packet itself.
b.writeRegistryValue(BlabberRegistrar.ILLUSTRATION_REGISTRY, i.getType());
i.getType().writeToPacketUnsafe(b, i);
})
);

@Override
public void render(DrawContext context, TextRenderer textRenderer, int x, int y, int mouseX, int mouseY, float tickDelta) {
for (DialogueIllustration i : sub) {
i.render(context, textRenderer, x, y, mouseX, mouseY, tickDelta);
}
}

@Override
public DialogueIllustrationType<? extends DialogueIllustration> getType() {
return TYPE;
}

@Override
public DialogueIllustration parseText(@Nullable ServerCommandSource source, @Nullable Entity sender) {
List<DialogueIllustration> parsedSub = new ArrayList<>(sub.size());
for (DialogueIllustration illustration : sub) {
parsedSub.add(illustration.parseText(source, sender));
}
return new DialogueIllustrationCollection(parsedSub);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public List<String> getCurrentIllustrations() {
return this.getCurrentState().illustrations();
}

public Map<String, List<DialogueIllustration>> getIllustrations() {
public Map<String, DialogueIllustration> getIllustrations() {
return this.template.illustrations();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,37 +30,33 @@

import java.util.*;

public record DialogueTemplate(String start, boolean unskippable, Map<String, DialogueState> states, Map<String, List<DialogueIllustration>> illustrations, DialogueLayout layout) {
public record DialogueTemplate(String start, boolean unskippable, Map<String, DialogueState> states, Map<String, DialogueIllustration> illustrations, DialogueLayout layout) {
public static final Codec<DialogueTemplate> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Codec.STRING.fieldOf("start_at").forGetter(DialogueTemplate::start),
Codec.BOOL.optionalFieldOf("unskippable", false).forGetter(DialogueTemplate::unskippable),
Codec.unboundedMap(Codec.STRING, DialogueState.CODEC).fieldOf("states").forGetter(DialogueTemplate::states),
Codec.unboundedMap(Codec.STRING, Codec.list(DialogueIllustrationType.CODEC)).optionalFieldOf("illustrations", Collections.emptyMap()).forGetter(DialogueTemplate::illustrations),
Codec.unboundedMap(Codec.STRING, DialogueIllustrationType.CODEC).optionalFieldOf("illustrations", Collections.emptyMap()).forGetter(DialogueTemplate::illustrations),
DialogueLayout.CODEC.optionalFieldOf("layout", DialogueLayout.DEFAULT).forGetter(DialogueTemplate::layout)
).apply(instance, DialogueTemplate::new));

public static void writeToPacket(PacketByteBuf buf, DialogueTemplate dialogue) {
buf.writeString(dialogue.start());
buf.writeBoolean(dialogue.unskippable());
buf.writeMap(dialogue.states(), PacketByteBuf::writeString, DialogueState::writeToPacket);
buf.writeMap(dialogue.illustrations(), PacketByteBuf::writeString, (buf1, list) ->
buf1.writeCollection(list, (b, i) -> {
// Write the type, then the packet itself.
b.writeRegistryValue(BlabberRegistrar.ILLUSTRATION_REGISTRY, i.getType());
i.getType().writeToPacketUnsafe(b, i);
})
);
buf.writeMap(dialogue.illustrations(), PacketByteBuf::writeString, (b, i) -> {
// Write the type, then the packet itself.
b.writeRegistryValue(BlabberRegistrar.ILLUSTRATION_REGISTRY, i.getType());
i.getType().writeToPacketUnsafe(b, i);
});
DialogueLayout.writeToPacket(buf, dialogue.layout());
}

public DialogueTemplate(PacketByteBuf buf) {
this(buf.readString(), buf.readBoolean(), buf.readMap(PacketByteBuf::readString, DialogueState::new), buf.readMap(PacketByteBuf::readString, buf1 ->
buf1.readCollection(ArrayList::new, b -> {
DialogueIllustrationType<?> type = b.readRegistryValue(BlabberRegistrar.ILLUSTRATION_REGISTRY);
assert type != null;
return type.readFromPacket(b);
})
), new DialogueLayout(buf));
this(buf.readString(), buf.readBoolean(), buf.readMap(PacketByteBuf::readString, DialogueState::new), buf.readMap(PacketByteBuf::readString, b -> {
DialogueIllustrationType<?> type = b.readRegistryValue(BlabberRegistrar.ILLUSTRATION_REGISTRY);
assert type != null;
return type.readFromPacket(b);
}), new DialogueLayout(buf));
}

public DialogueTemplate parseText(@Nullable ServerCommandSource source, @Nullable Entity sender) throws CommandSyntaxException {
Expand All @@ -70,13 +66,9 @@ public DialogueTemplate parseText(@Nullable ServerCommandSource source, @Nullabl
parsedStates.put(state.getKey(), state.getValue().parseText(source, sender));
}

Map<String, List<DialogueIllustration>> parsedIllustrations = new HashMap<>(illustrations().size());
for (Map.Entry<String, List<DialogueIllustration>> list : illustrations().entrySet()) {
List<DialogueIllustration> parsedIllustrationsList = new ArrayList<>(list.getValue().size());
for (DialogueIllustration illustration : list.getValue()) {
parsedIllustrationsList.add(illustration.parseText(source, sender));
}
parsedIllustrations.put(list.getKey(), parsedIllustrationsList);
Map<String, DialogueIllustration> parsedIllustrations = new HashMap<>(illustrations().size());
for (Map.Entry<String, DialogueIllustration> illustration : illustrations().entrySet()) {
parsedIllustrations.put(illustration.getKey(), illustration.getValue().parseText(source, sender));
}

return new DialogueTemplate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public static ValidationResult validateStructure(DialogueTemplate dialogue) {
for (Map.Entry<String, DialogueState> state : dialogue.states().entrySet()) {
for (String illustration : state.getValue().illustrations()) {
if (!dialogue.illustrations().containsKey(illustration)) {
return new ValidationResult.Error.NonexistentIllustrationList(state.getKey(), illustration);
return new ValidationResult.Error.NonexistentIllustration(state.getKey(), illustration);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@ public String message() {
}
}

record NonexistentIllustrationList(String state, String illustration) implements Error {
record NonexistentIllustration(String state, String illustration) implements Error {
@Override
public String message() {
return state() + " references non-existent illustration list " + illustration();
return state() + " references non-existent illustration " + illustration();
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ public void validationFailsOnLoopingDialogue() {
Assertions.assertTrue(DialogueValidator.validateStructure(dialogue) instanceof ValidationResult.Error.SoftLock, "Dialogue validation should detect looping dialogues");
}

@Test
public void validationFailsOnInvalidReference() {
DialogueTemplate dialogue = loadDialogue("/invalid_reference.json");
Assertions.assertTrue(DialogueValidator.validateStructure(dialogue) instanceof ValidationResult.Error.NonexistentIllustration, "Dialogue validation should detect invalid illustration reference");
}

private static DialogueTemplate loadDialogue(String name) {
return Util.getResult(DialogueTemplate.CODEC.parse(JsonOps.INSTANCE, new Gson().fromJson(new InputStreamReader(Objects.requireNonNull(DialogueValidatorTest.class.getResourceAsStream(name))), JsonElement.class)), s -> {
throw new GameTestException(s);
Expand Down
20 changes: 20 additions & 0 deletions src/test/resources/invalid_reference.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"states": {
"start": {
"text": "It takes determination to ascend to the top of this world, mortal. What do you want?",
"choices": [
{
"text": "I came here to demand a favour.",
"next": "b"
}
],
"illustrations": ["nonexistent"]
},
"b": {
"text": "",
"choices": [],
"type": "end_dialogue"
}
},
"start_at": "start"
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
}
}
}
]
],
"illustrations": ["discussion"]
},
"bargain": {
"text": "Tell me.",
Expand Down Expand Up @@ -65,7 +66,8 @@
"text": "I want a friend.",
"next": "friendship"
}
]
],
"illustrations": ["discussion"]
},
"friendship": {
"text": [
Expand All @@ -78,7 +80,8 @@
"text": [{"text":"My name is "},{"selector":"@s"}],
"next": "end"
}
]
],
"illustrations": ["discussion"]
}
},
"illustrations": {
Expand All @@ -87,21 +90,21 @@
"type": "blabber:entity",
"entity": "@interlocutor",
"x1": 0,
"y1": 50,
"y1": 80,
"x2": 100,
"y2": 150,
"y2": 180,
"size": 100,
"y_offset": 0.5,
"stare_at_x": 50,
"stare_at_y": 50
"stare_at_y": 0
},
{
"type": "blabber:entity",
"entity": "@s",
"x1": 100,
"y1": 50,
"y1": 80,
"x2": 200,
"y2": 150,
"y2": 180,
"size": 100,
"y_offset": 0.5,
"stare_at_x": -50,
Expand Down

0 comments on commit 0ebf44a

Please sign in to comment.