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
20 changes: 20 additions & 0 deletions src/main/java/betterquesting/api/questing/IQuest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,38 @@
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraftforge.common.util.Constants;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.UUID;

public interface IQuest extends INBTSaveLoad<NBTTagCompound>, INBTProgress<NBTTagCompound>, IPropertyContainer {
String LAST_COMPLETED_AT_TAG = "last_completed_at";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

as much as it would be beneficial to do this, we currently dont, and so shouldnt store the name in a field.


EnumQuestState getState(EntityPlayer player);

@Nullable
NBTTagCompound getCompletionInfo(UUID uuid);

/**
* Get the timestamp of the last time this quest was completed by the given player.
* @param uuid The questing UUID for the player.
* @return Timestamp of last completion.
* For quests completed before this was added, it will return the timestamp value (may be inaccurate for repeatable quests).
* For quests not completed, it will return 0.
*/
default long getLastCompletedAt(UUID uuid) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

move this to QuestInstance

NBTTagCompound completionInfo = getCompletionInfo(uuid);
if (completionInfo == null) return 0;

if (completionInfo.hasKey(LAST_COMPLETED_AT_TAG, Constants.NBT.TAG_LONG)) {
return completionInfo.getLong(LAST_COMPLETED_AT_TAG);
}

return completionInfo.getLong("timestamp");
}

void setCompletionInfo(UUID uuid, @Nullable NBTTagCompound nbt);

void update(EntityPlayer player);
Expand Down
1 change: 1 addition & 0 deletions src/main/java/betterquesting/api/storage/BQ_Settings.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public class BQ_Settings {
public static boolean claimAllConfirmation = true;
public static boolean lockTray = true;
public static boolean viewMode = false;
public static String historyRepeatableFilter = "SHOW_ALL";
public static boolean limitBack = false;
public static String defaultVisibility = "NORMAL";

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
package betterquesting.api2.client.gui.panels.lists;

import betterquesting.api.api.QuestingAPI;
import betterquesting.api.properties.NativeProps;
import betterquesting.api.questing.IQuest;
import betterquesting.api.questing.IQuestLine;
import betterquesting.api.questing.IQuestLineEntry;
import betterquesting.api2.client.gui.controls.PanelButtonCustom;
import betterquesting.api2.client.gui.controls.PanelButtonQuest;
import betterquesting.api2.client.gui.misc.GuiRectangle;
import betterquesting.api2.client.gui.misc.IGuiRect;
import betterquesting.api2.client.gui.panels.content.PanelTextBox;
import betterquesting.api2.client.gui.themes.presets.PresetColor;
import betterquesting.api2.storage.DBEntry;
import betterquesting.api2.utils.QuestTranslation;
import betterquesting.misc.QuestHistoryEntry;
import betterquesting.questing.QuestDatabase;
import betterquesting.questing.QuestLineDatabase;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTTagCompound;

import java.text.DateFormat;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Consumer;

public class CanvasQuestHistory extends CanvasSearch<QuestHistoryEntry, QuestHistoryEntry> {
private List<QuestHistoryEntry> historyList;
private Consumer<QuestHistoryEntry> questOpenCallback;
private final EntityPlayer player;
private RepeatableFilter repeatableFilter = RepeatableFilter.SHOW_ALL;

public CanvasQuestHistory(IGuiRect rect, EntityPlayer player) {
super(rect);
this.player = player;
}

@Override
protected Iterator<QuestHistoryEntry> getIterator() {
if (historyList == null) {
historyList = collectHistory();
}

return historyList.iterator();
}

private List<QuestHistoryEntry> collectHistory() {
Map<Integer, QuestHistoryEntry> historyEntries = new Int2ObjectOpenHashMap<>();
UUID questingUUID = QuestingAPI.getQuestingUUID(player);

for (DBEntry<IQuestLine> questLine : QuestLineDatabase.INSTANCE.getEntries()) {
for (DBEntry<IQuestLineEntry> questLineEntry : questLine.getValue().getEntries()) {
int questId = questLineEntry.getID();
if (historyEntries.containsKey(questId)) {
continue;
}

IQuest quest = QuestDatabase.INSTANCE.getValue(questId);
if (quest == null) {
continue;
}

NBTTagCompound completionInfo = quest.getCompletionInfo(questingUUID);
if (completionInfo == null) {
continue;
}

DBEntry<IQuest> questEntry = new DBEntry<>(questId, quest);
long timestamp = quest.getLastCompletedAt(questingUUID);
if (timestamp <= 0) {
continue;
}

boolean repeatable = quest.getProperty(NativeProps.REPEAT_TIME) >= 0;
boolean pendingRewards = repeatable && quest.canClaimBasically(player);
historyEntries.put(questId, new QuestHistoryEntry(questEntry, questLine, timestamp, repeatable, pendingRewards));
}
}

List<QuestHistoryEntry> sortedEntries = new ArrayList<>(historyEntries.values());
sortedEntries.sort(Comparator.comparingLong(QuestHistoryEntry::getCompletionTimestamp)
.reversed()
.thenComparingInt(entry -> entry.getQuest().getID()));
return sortedEntries;
}

@Override
protected void queryMatches(QuestHistoryEntry entry, String query, ArrayDeque<QuestHistoryEntry> results) {
if (entry.isRepeatable()) {
if (repeatableFilter == RepeatableFilter.HIDE) {
return;
}

if (repeatableFilter == RepeatableFilter.SHOW_PENDING_REWARDS && !entry.hasPendingRewards()) {
return;
}
}

results.add(entry);
}

@Override
protected boolean addResult(QuestHistoryEntry entry, int index, int cachedWidth) {
GuiRectangle buttonRect = new GuiRectangle(0, index * 32, cachedWidth, 32, 0);
PanelButtonCustom buttonContainer = new PanelButtonCustom(buttonRect, 2);
buttonContainer.setCallback(panelButtonCustom -> {
if (questOpenCallback != null) {
questOpenCallback.accept(entry);
}
});
this.addPanel(buttonContainer);

GuiRectangle questButtonRect = new GuiRectangle(2, 2, 28, 28);
PanelButtonQuest questButton = new PanelButtonQuest(questButtonRect, 0, "", entry.getQuest());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

need to update this regularly to have the correct color - test a quest that resets, have it be completed when the gui opens and then reset while the gui remains open. color will not change, but should.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes, I avoided refreshing in real time, as it an be kinda expensive on big quest books. I guess I can updates every X seconds, with X configurable, but the user can just as much close and reopen.

questButton.setCallback(value -> {
if (questOpenCallback != null) {
questOpenCallback.accept(entry);
}
});
buttonContainer.addPanel(questButton);

int repeatableLabelWidth = entry.isRepeatable() ? 96 : 0;
int questNameWidth = Math.max(0, cachedWidth - 36 - repeatableLabelWidth - (entry.isRepeatable() ? 8 : 0));
GuiRectangle questNameRect = new GuiRectangle(36, 6, questNameWidth, 12);
String questNameStr = entry.getQuest().getValue().getProperty(NativeProps.NAME);
PanelTextBox questName = new PanelTextBox(questNameRect, QuestTranslation.translate(questNameStr));
buttonContainer.addPanel(questName);

if (entry.isRepeatable()) {
GuiRectangle repeatableRect = new GuiRectangle(cachedWidth - repeatableLabelWidth - 4, 6, repeatableLabelWidth, 12);
PanelTextBox repeatableLabel = new PanelTextBox(repeatableRect, QuestTranslation.translate("betterquesting.gui.history.repeatable"));
repeatableLabel.setAlignment(2);
repeatableLabel.setColor(PresetColor.QUEST_LINE_COMPLETE.getColor());
buttonContainer.addPanel(repeatableLabel);
}

GuiRectangle timestampRect = new GuiRectangle(36, 20, cachedWidth - 36, 10);
PanelTextBox timestamp = new PanelTextBox(timestampRect, getHistoryDetails(entry));
timestamp.setColor(PresetColor.TEXT_AUX_0.getColor());
buttonContainer.addPanel(timestamp);
return true;
}

private String getHistoryDetails(QuestHistoryEntry entry) {
String timestamp = formatTimestamp(entry.getCompletionTimestamp());
if (!entry.isRepeatable()) {
return QuestTranslation.translate("betterquesting.gui.history.completed_at", timestamp);
}

if (entry.hasPendingRewards()) {
return QuestTranslation.translate("betterquesting.gui.history.pending_rewards_at", timestamp);
}

return QuestTranslation.translate("betterquesting.gui.history.last_completed_at", timestamp);
}

private String formatTimestamp(long timestamp) {
if (timestamp <= 0) {
return "-";
}

return DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT)
.format(new Date(timestamp));
}

public void setQuestOpenCallback(Consumer<QuestHistoryEntry> questOpenCallback) {
this.questOpenCallback = questOpenCallback;
}

public void setRepeatableFilter(RepeatableFilter repeatableFilter) {
if (this.repeatableFilter == repeatableFilter) {
return;
}

this.repeatableFilter = repeatableFilter;
refreshSearch();
updatePanelScroll();
}

public enum RepeatableFilter {
HIDE("betterquesting.gui.history.repeatable_filter.hide"),
SHOW_PENDING_REWARDS("betterquesting.gui.history.repeatable_filter.pending_rewards"),
SHOW_ALL("betterquesting.gui.history.repeatable_filter.all");
Comment on lines +187 to +190

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

what about expanding this to filter everything - mainly, i would like to filter by "pending rewards" for all quests.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

So 2 buttons or drop hiding repeatables altogether?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

i think i would be fine with either, but would prefer two buttons


private static final RepeatableFilter[] VALUES = values();

private final String translationKey;

RepeatableFilter(String translationKey) {
this.translationKey = translationKey;
}

public String getTranslationKey() {
return translationKey;
}

public static RepeatableFilter fromName(String name) {
for (RepeatableFilter value : VALUES) {
if (value.name().equalsIgnoreCase(name)) {
return value;
}
}

return SHOW_ALL;
}

public RepeatableFilter next() {
return VALUES[(ordinal() + 1) % VALUES.length];
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,9 @@ public static void registerTextures(IThemeRegistry reg) {
reg.setDefaultTexture(BTN_CLEAN_1.key, new SlicedTexture(TX_SIMPLE, new GuiRectangle(120, 0, 12, 12), new GuiPadding(2, 2, 2, 2)));
reg.setDefaultTexture(BTN_CLEAN_2.key, new SlicedTexture(TX_SIMPLE, new GuiRectangle(132, 0, 12, 12), new GuiPadding(2, 2, 2, 2)));

reg.setDefaultTexture(BTN_ALT_0.key, new SlicedTexture(TX_SIMPLE, new GuiRectangle(144, 0, 12, 12), new GuiPadding(2, 2, 2, 2)));
reg.setDefaultTexture(BTN_ALT_1.key, new SlicedTexture(TX_SIMPLE, new GuiRectangle(156, 0, 12, 12), new GuiPadding(2, 2, 2, 2)));
reg.setDefaultTexture(BTN_ALT_2.key, new SlicedTexture(TX_SIMPLE, new GuiRectangle(178, 0, 12, 12), new GuiPadding(2, 2, 2, 2)));
reg.setDefaultTexture(BTN_ALT_0.key, new SlicedTexture(TX_SIMPLE, new GuiRectangle(144, 0, 12, 12), new GuiPadding(2, 2, 2, 3)));
reg.setDefaultTexture(BTN_ALT_1.key, new SlicedTexture(TX_SIMPLE, new GuiRectangle(156, 0, 12, 12), new GuiPadding(2, 2, 2, 3)));
reg.setDefaultTexture(BTN_ALT_2.key, new SlicedTexture(TX_SIMPLE, new GuiRectangle(168, 0, 12, 12), new GuiPadding(2, 2, 2, 3)));

reg.setDefaultTexture(HOTBAR_0.key, new SlicedTexture(TX_SIMPLE, new GuiRectangle(190, 0, 12, 12), new GuiPadding(3, 3, 2, 2)));
reg.setDefaultTexture(HOTBAR_1.key, new SlicedTexture(TX_SIMPLE, new GuiRectangle(202, 0, 12, 12), new GuiPadding(3, 3, 3, 3)));
Expand Down
Loading