Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,13 @@ public void claimAllowedRewards(ServerPlayer player, String id) {
QuestsProgress progresses = QuestProgressHandler.getProgress(player.server, player.getUUID());
QuestProgress progress = progresses.getProgress(id);
if (progress == null) return;
if (!progress.isUnlocked()) return;
if (!progress.isComplete() && !this.tasks.isEmpty()) return;
claimRewards(player, id, progresses, progress);
}

public void claimRewards(ServerPlayer player, String id, QuestsProgress progresses, QuestProgress progress) {
if (!progress.isUnlocked()) return;
if (progress.isClaimed(this)) return;

claimRewards(
Expand All @@ -71,6 +73,7 @@ public void claimRewards(ServerPlayer player, String id, QuestsProgress progress
public void claimAllowedReward(ServerPlayer player, String id, String rewardId) {
QuestsProgress progress = QuestProgressHandler.getProgress(player.server, player.getUUID());
if (!progress.isComplete(id) && !this.tasks.isEmpty()) return;
if (!progress.isUnlocked(id)) return;
if (progress.isClaimed(id, this)) return;

var questProgress = progress.getProgress(id);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package earth.terrarium.heracles.api.quests;

import net.minecraft.util.StringRepresentable;
import org.jetbrains.annotations.NotNull;

import java.util.Locale;

/**
* Specifies when a player can make progress on a quest.
* <p>
* This is inspired by <a href="https://docs.feed-the-beast.com/docs/mods/suite/Quests/Developer/Quests/Settings#progression-mode">a similar setting in FTB Quests</a>.
*/
public enum QuestProgressionMode implements StringRepresentable {
/**
* Players can make progress on this quest’s tasks only if the quest is unlocked.
*/
LINEAR,
/**
* Players can make progress on this quest’s tasks at any time
* but can claim the rewards only once it is unlocked.
*/
FLEXIBLE;

@Override
public @NotNull String getSerializedName() {
return "quest.heracles.%s".formatted(name().toLowerCase(Locale.ROOT));
}

public boolean isFlexible() {
return this == FLEXIBLE;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ public final class QuestSettings {
Codec.BOOL.fieldOf("unlockNotification").orElse(false).forGetter(QuestSettings::unlockNotification),
Codec.BOOL.fieldOf("showDependencyArrow").orElse(true).forGetter(QuestSettings::showDependencyArrow),
Codec.BOOL.fieldOf("repeatable").orElse(false).forGetter(QuestSettings::repeatable),
Codec.BOOL.fieldOf("autoClaimRewards").orElse(false).forGetter(QuestSettings::autoClaimRewards)
Codec.BOOL.fieldOf("autoClaimRewards").orElse(false).forGetter(QuestSettings::autoClaimRewards),
EnumCodec.of(QuestProgressionMode.class).fieldOf("progression_mode").orElse(QuestProgressionMode.LINEAR).forGetter(QuestSettings::progressionMode)
).apply(instance, QuestSettings::new));

private boolean individualProgress;
Expand All @@ -22,18 +23,20 @@ public final class QuestSettings {
private boolean showDependencyArrow;
private boolean repeatable;
private boolean autoClaimRewards;
private QuestProgressionMode progressionMode;

public QuestSettings(boolean individualProgress, QuestDisplayStatus hiddenUntil, boolean unlockNotification, boolean showDependencyArrow, boolean repeatable, boolean autoClaimRewards) {
public QuestSettings(boolean individualProgress, QuestDisplayStatus hiddenUntil, boolean unlockNotification, boolean showDependencyArrow, boolean repeatable, boolean autoClaimRewards, QuestProgressionMode progressionMode) {
this.individualProgress = individualProgress;
this.hiddenUntil = hiddenUntil;
this.unlockNotification = unlockNotification;
this.showDependencyArrow = showDependencyArrow;
this.repeatable = repeatable;
this.autoClaimRewards = autoClaimRewards;
this.progressionMode = progressionMode;
}

public static QuestSettings createDefault() {
return new QuestSettings(false, QuestDisplayStatus.LOCKED, false, true, false, false);
return new QuestSettings(false, QuestDisplayStatus.LOCKED, false, true, false, false, QuestProgressionMode.LINEAR);
}

public boolean individualProgress() {
Expand All @@ -60,6 +63,10 @@ public boolean autoClaimRewards() {
return autoClaimRewards;
}

public QuestProgressionMode progressionMode() {
return progressionMode;
}

@Override
public boolean equals(Object obj) {
if (obj == this) return true;
Expand All @@ -71,12 +78,13 @@ public boolean equals(Object obj) {
this.unlockNotification == that.unlockNotification &&
this.showDependencyArrow == that.showDependencyArrow &&
this.repeatable == that.repeatable &&
this.autoClaimRewards == that.autoClaimRewards;
this.autoClaimRewards == that.autoClaimRewards &&
this.progressionMode == that.progressionMode;
}

@Override
public int hashCode() {
return Objects.hash(individualProgress, hiddenUntil, unlockNotification, showDependencyArrow, repeatable, autoClaimRewards);
return Objects.hash(individualProgress, hiddenUntil, unlockNotification, showDependencyArrow, repeatable, autoClaimRewards, progressionMode);
}

public void update(QuestSettings newSettings) {
Expand All @@ -85,6 +93,7 @@ public void update(QuestSettings newSettings) {
this.unlockNotification = newSettings.unlockNotification;
this.showDependencyArrow = newSettings.showDependencyArrow;
this.repeatable = newSettings.repeatable;
this.progressionMode = newSettings.progressionMode;
}

public void setIndividualProgress(boolean individualProgress) {
Expand All @@ -111,4 +120,8 @@ public void setAutoClaimRewards(boolean autoClaimRewards) {
this.autoClaimRewards = autoClaimRewards;
}

public void setProgressionMode(QuestProgressionMode progressionMode) {
this.progressionMode = progressionMode;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,22 @@
import net.minecraft.nbt.Tag;
import net.minecraft.server.level.ServerPlayer;

/**
* A task of a quest.
* <p>
* This contains the data about the particular task in addition to the generic type.
*
* @see QuestTaskType
* @param <I> the type of the input to test against
* @param <S> the type of the NBT tag used to record progress
* @param <T> the type implementing this interface
*/
public interface QuestTask<I, S extends Tag, T extends QuestTask<I, S, T>> {

/**
* The id of the task.
* <p>
* This is only unique within a single quest.
*
* @return The id.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ public static void displayItemsRewardedToast(String id, List<Item> items) {
QuestClaimedToast.addOrUpdate(Minecraft.getInstance().getToasts(), id, items);
}

public static void displayQuestCompleteToast(String id) {
QuestCompletedToast.add(Minecraft.getInstance().getToasts(), id);
public static void displayQuestCompleteToast(String id, boolean provisional) {
QuestCompletedToast.add(Minecraft.getInstance().getToasts(), id, provisional);
}

public static void displayQuestUnlockedToast(String id) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,12 @@ protected void renderBg(GuiGraphics graphics, float partialTick, int mouseX, int

@Override
public @NotNull Component getTitle() {
return content.progress().isComplete() ? Component.translatable("gui.heracles.quest.title.complete", super.getTitle()) : super.getTitle();
if (content.progress().isComplete()) {
return Component.translatable(
content.progress().isUnlocked() ? "gui.heracles.quest.title.complete" : "gui.heracles.quest.title.provisional",
super.getTitle());
}
return super.getTitle();
}

public Quest quest() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ public void update(String group, String id, Quest quest) {

DisplayWidget widget = QuestRewardWidgets.create(reward);
if (widget == null) continue;
if (progress.canClaim(reward.id())) {
if (!progress.isUnlocked()) {
locked.add(new MutablePair<>(reward, widget));
} else if (progress.canClaim(reward.id())) {
available.add(new MutablePair<>(reward, widget));
} else if (progress.isComplete()) {
claimed.add(new MutablePair<>(reward, widget));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import earth.terrarium.heracles.api.client.settings.base.BooleanSetting;
import earth.terrarium.heracles.api.client.settings.base.EnumSetting;
import earth.terrarium.heracles.api.quests.QuestDisplayStatus;
import earth.terrarium.heracles.api.quests.QuestProgressionMode;
import earth.terrarium.heracles.api.quests.QuestSettings;
import org.jetbrains.annotations.Nullable;

Expand All @@ -20,6 +21,7 @@ public CreationData create(@Nullable QuestSettings object) {
settings.put("show_dependency_arrow", BooleanSetting.TRUE, object != null && object.showDependencyArrow());
settings.put("repeatable", BooleanSetting.FALSE, object != null && object.repeatable());
settings.put("auto_claim_rewards", BooleanSetting.FALSE, object != null && object.autoClaimRewards());
settings.put("progression_mode", new EnumSetting<>(QuestProgressionMode.class, QuestProgressionMode.LINEAR), object != null ? object.progressionMode() : QuestProgressionMode.LINEAR);
return settings;
}

Expand All @@ -31,7 +33,8 @@ public QuestSettings create(String id, QuestSettings object, Data data) {
data.get("unlock_notification", BooleanSetting.FALSE).orElse(object != null && object.unlockNotification()),
data.get("show_dependency_arrow", BooleanSetting.TRUE).orElse(object != null && object.showDependencyArrow()),
data.get("repeatable", BooleanSetting.FALSE).orElse(object != null && object.repeatable()),
data.get("auto_claim_rewards", BooleanSetting.FALSE).orElse(object != null && object.autoClaimRewards())
data.get("auto_claim_rewards", BooleanSetting.FALSE).orElse(object != null && object.autoClaimRewards()),
data.get("progression_mode", new EnumSetting<>(QuestProgressionMode.class, QuestProgressionMode.LINEAR)).orElse(object != null ? object.progressionMode() : QuestProgressionMode.LINEAR)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

public class QuestWidget {

private static final int NUM_STATUSES = ModUtils.QuestStatus.values().length;
private final ClientQuests.QuestEntry entry;
private final Quest quest;
private final ModUtils.QuestStatus status;
Expand All @@ -45,15 +46,15 @@ public void render(GuiGraphics graphics, ScissorBoxStack scissor, int x, int y,
x + x() + info.xOffset(), y + y() + info.yOffset(),
status.ordinal() * info.width(), 0,
info.width(), info.height(),
info.width() * 5, info.height()
info.width() * (NUM_STATUSES + 1), info.height()
);

if (hovered) {
graphics.blit(quest.display().iconBackground(),
x + x() + info.xOffset(), y + y() + info.yOffset(),
4 * info.width(), 0,
NUM_STATUSES * info.width(), 0,
info.width(), info.height(),
info.width() * 5, info.height()
info.width() * (NUM_STATUSES + 1), info.height()
);
}
RenderSystem.disableBlend();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ public SelectQuestWidget(int x, int y, int width, int height, QuestsWidget widge
.unlockNotification(questSettings.unlockNotification())
.showDependencyArrow(questSettings.showDependencyArrow())
.repeatable(questSettings.repeatable())
.autoClaimRewards(questSettings.autoClaimRewards());
.autoClaimRewards(questSettings.autoClaimRewards())
.progressionMode(questSettings.progressionMode());
})
);
edit.setTitle(Component.translatable("gui.heracles.quests.edit_quest_settings"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,16 @@ public class QuestCompletedToast extends WrappingHintToast implements Toast {
private static final Component TITLE_TEXT = Component.translatable("quest.heracles.toast");
private static final Component KEY_HINT = Component.translatable("quest.heracles.toast.desc", Component.keybind("key.heracles.open_quests")
.withStyle(style -> style.withBold(true).withColor(ToastsTheme.getKeybinding())));
private static final Component TITLE_TEXT_PROVISIONAL = Component.translatable("quest.heracles.toast.provisional");
private static final Component KEY_HINT_PROVISIONAL = Component.translatable("quest.heracles.toast.provisional.desc");
private final QuestIcon<?> icon;

public QuestCompletedToast(Quest quest) {
super(TITLE_TEXT, List.of(quest.display().title()), List.of(KEY_HINT),5000L);
public QuestCompletedToast(Quest quest, boolean provisional) {
super(
provisional ? TITLE_TEXT_PROVISIONAL : TITLE_TEXT,
List.of(quest.display().title()),
List.of(provisional ? KEY_HINT_PROVISIONAL : KEY_HINT),
5000L);
this.icon = quest.display().icon();
}

Expand All @@ -32,9 +38,9 @@ public Toast.Visibility render(GuiGraphics graphics, ToastComponent toastCompone
return visible;
}

public static void add(ToastComponent toastComponent, String quest) {
public static void add(ToastComponent toastComponent, String quest, boolean provisional) {
ClientQuests.get(quest).ifPresent(entry ->
toastComponent.addToast(new QuestCompletedToast(entry.value()))
toastComponent.addToast(new QuestCompletedToast(entry.value(), provisional))
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static Set<String> getPinned(ServerPlayer player) {
return read(player.server).pinned.computeIfAbsent(player.getUUID(), u -> new LinkedHashSet<>());
}

public static void syncIfChanged(ServerPlayer player, Collection<String> pinned) {
public static void syncIfChanged(ServerPlayer player, Iterable<String> pinned) {
Set<String> pinnedSet = getPinned(player);
for (String s : pinned) {
if (!pinnedSet.contains(s)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,25 @@

import java.util.*;

/**
* Records a player’s progress for an individual quest.
*/
public class QuestProgress {

private final Map<String, TaskProgress<?>> tasks = new HashMap<>();
private final Set<String> claimed = new HashSet<>();
private boolean complete;
private boolean unlocked;

public QuestProgress() {
this.complete = false;
this.unlocked = false;
}

public QuestProgress(Quest quest, CompoundTag tag) {
if (tag == null) return;
this.complete = tag.getBoolean("complete");
this.unlocked = tag.getBoolean("unlocked");
this.claimed.addAll(TagUtils.mapToCollection(ArrayList::new, tag.getList("rewards", 8), Tag::getAsString));
var compound = tag.getCompound("tasks");
for (String taskKey : compound.getAllKeys()) {
Expand Down Expand Up @@ -64,6 +70,14 @@ public void setComplete(boolean complete) {
this.complete = complete;
}

public boolean isUnlocked() {
return unlocked;
}

public void setUnlocked(boolean unlocked) {
this.unlocked = unlocked;
}

public void claimReward(String reward) {
claimed.add(reward);
}
Expand All @@ -85,7 +99,7 @@ public boolean isClaimed(Quest quest) {
}

public boolean canClaim(String reward) {
return !claimed.contains(reward) && complete;
return !claimed.contains(reward) && complete && unlocked;
}

@SuppressWarnings("unchecked")
Expand All @@ -103,12 +117,14 @@ public void copyFrom(QuestProgress progress) {
tasks.clear();
tasks.putAll(progress.tasks);
complete = progress.complete;
unlocked = progress.unlocked;
checkComplete();
}

public CompoundTag save() {
CompoundTag tag = new CompoundTag();
tag.putBoolean("complete", complete);
tag.putBoolean("unlocked", unlocked);
tag.put("rewards", TagUtils.mapToListTag(claimed, StringTag::valueOf));
CompoundTag tasks = new CompoundTag();
for (var entry : this.tasks.entrySet()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public static void setupChanger() {
});
}

public static void sync(ServerPlayer player, Collection<String> quests) {
public static void sync(ServerPlayer player, Iterable<String> quests) {
Map<String, QuestProgress> progress = new LinkedHashMap<>();
quests.forEach(id -> {
Quest quest = QuestHandler.get(id);
Expand Down
Loading