Skip to content

Commit

Permalink
feat: Add ExtendedSeasonLeaderboardFeature (#2431)
Browse files Browse the repository at this point in the history
  • Loading branch information
kristofbolyai committed May 5, 2024
1 parent ec77450 commit 5e80f8e
Show file tree
Hide file tree
Showing 25 changed files with 524 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.wynntils.core.persisted.config.Category;
import com.wynntils.core.persisted.config.ConfigCategory;
import com.wynntils.features.DiscordRichPresenceFeature;
import com.wynntils.features.ExtendedSeasonLeaderboardFeature;
import com.wynntils.features.LootrunFeature;
import com.wynntils.features.MythicFoundFeature;
import com.wynntils.features.TerritoryDefenseMessageFeature;
Expand Down Expand Up @@ -372,6 +373,7 @@ public void init() {

// region uncategorized
registerFeature(new DiscordRichPresenceFeature());
registerFeature(new ExtendedSeasonLeaderboardFeature());
registerFeature(new MythicFoundFeature());
registerFeature(new TerritoryDefenseMessageFeature());
// endregion
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © Wynntils 2023.
* Copyright © Wynntils 2023-2024.
* This file is released under LGPLv3. See LICENSE for full license details.
*/
package com.wynntils.core.persisted.storage;
Expand Down Expand Up @@ -130,8 +130,12 @@ private void writeToJson() {
JsonObject storageJson = new JsonObject();

storages.forEach((jsonName, storage) -> {
JsonElement jsonElem = Managers.Json.GSON.toJsonTree(storage.get(), storageTypes.get(storage));
storageJson.add(jsonName, jsonElem);
try {
JsonElement jsonElem = Managers.Json.GSON.toJsonTree(storage.get(), storageTypes.get(storage));
storageJson.add(jsonName, jsonElem);
} catch (Throwable t) {
WynntilsMod.error("Failed to save storage " + jsonName, t);
}
});

Managers.Json.savePreciousJson(userStorageFile, storageJson);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* Copyright © Wynntils 2024.
* This file is released under LGPLv3. See LICENSE for full license details.
*/
package com.wynntils.features;

import com.wynntils.core.components.Models;
import com.wynntils.core.consumers.features.Feature;
import com.wynntils.core.persisted.Persisted;
import com.wynntils.core.persisted.config.Config;
import com.wynntils.handlers.labels.event.EntityLabelChangedEvent;
import com.wynntils.handlers.labels.event.LabelIdentifiedEvent;
import com.wynntils.handlers.labels.event.LabelsRemovedEvent;
import com.wynntils.handlers.labels.type.LabelInfo;
import com.wynntils.models.players.label.GuildSeasonLeaderboardHeaderLabelInfo;
import com.wynntils.models.players.label.GuildSeasonLeaderboardLabelInfo;
import com.wynntils.models.players.profile.GuildProfile;
import com.wynntils.models.worlds.event.WorldStateEvent;
import com.wynntils.utils.StringUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraftforge.eventbus.api.SubscribeEvent;

public class ExtendedSeasonLeaderboardFeature extends Feature {
private final Map<Integer, GuildSeasonLeaderboardLabelInfo> labelInfos = new HashMap<>();

@Persisted
private final Config<Boolean> useShortSeasonRankingStrings = new Config<>(false);

@SubscribeEvent
public void onLabelIdentified(LabelIdentifiedEvent event) {
if (event.getLabelInfo() instanceof GuildSeasonLeaderboardLabelInfo guildSeasonLeaderboardLabelInfo) {
labelInfos.put(guildSeasonLeaderboardLabelInfo.getPlace(), guildSeasonLeaderboardLabelInfo);
}
}

@SubscribeEvent
public void onLabelChanged(EntityLabelChangedEvent event) {
if (event.getLabelInfo().isEmpty()) return;

LabelInfo labelInfo = event.getLabelInfo().get();

if (labelInfo instanceof GuildSeasonLeaderboardHeaderLabelInfo) {
// Reset every label that is for a place higher than 10
// A page only has 10 entries, the header only changes when we switch seasons
// so we do this to make sure the 10th place is not calculated from the old 11th place data
List<GuildSeasonLeaderboardLabelInfo> labelsToRemove = labelInfos.values().stream()
.filter(info -> info.getPlace() > 10)
.toList();

labelsToRemove.forEach(labelInfoToRemove -> labelInfos.remove(labelInfoToRemove.getPlace()));

// We also need to update the "last" displayed label, as it is the one that is displayed on the 10th place
labelInfos.values().stream()
.filter(info -> info.getPlace() % 10 == 0)
.forEach(this::updateLeaderboardEntityName);

return;
}

if (!(labelInfo instanceof GuildSeasonLeaderboardLabelInfo guildSeasonLeaderboardLabelInfo)) {
return;
}

// Update the changed label data
labelInfos.put(guildSeasonLeaderboardLabelInfo.getPlace(), guildSeasonLeaderboardLabelInfo);

// Cancel the event to prevent the label from being changed
event.setCanceled(true);

// Update this entity's name
updateLeaderboardEntityName(guildSeasonLeaderboardLabelInfo);

// Also update the name of the entity that is "before" this entity in ranking
// But only, if this is not the "first" entity in ranking (the first element that is displayed on the page)
if (guildSeasonLeaderboardLabelInfo.getPlace() % 10 != 1) {
labelInfos.values().stream()
.filter(info -> info.getPlace() == guildSeasonLeaderboardLabelInfo.getPlace() - 1)
.findFirst()
.ifPresent(this::updateLeaderboardEntityName);
}
}

private void updateLeaderboardEntityName(GuildSeasonLeaderboardLabelInfo guildSeasonLeaderboardLabelInfo) {
// Find the label info that is "after" the current label info, in ranking
GuildSeasonLeaderboardLabelInfo nextLabelInfo = labelInfos.values().stream()
.filter(info -> info.getPlace() == guildSeasonLeaderboardLabelInfo.getPlace() + 1)
.findFirst()
.orElse(null);

Optional<GuildProfile> guildProfile = Models.Guild.getGuildProfile(guildSeasonLeaderboardLabelInfo.getGuild());

// Update the current label info with the additional information

// Place color:
// 1-3 - Gold
// 4-6 - Yellow
// 7-9 - White
// 10+ - Gray
ChatFormatting placeColor = ChatFormatting.GRAY;
if (guildSeasonLeaderboardLabelInfo.getPlace() <= 3) {
placeColor = ChatFormatting.GOLD;
} else if (guildSeasonLeaderboardLabelInfo.getPlace() <= 6) {
placeColor = ChatFormatting.YELLOW;
} else if (guildSeasonLeaderboardLabelInfo.getPlace() <= 9) {
placeColor = ChatFormatting.WHITE;
}

String scoreString;
String scoreDiffString;
if (useShortSeasonRankingStrings.get()) {
scoreString = StringUtils.integerToShortString(guildSeasonLeaderboardLabelInfo.getScore());
scoreDiffString = StringUtils.integerToShortString(
nextLabelInfo == null ? 0 : guildSeasonLeaderboardLabelInfo.getScore() - nextLabelInfo.getScore());
} else {
scoreString = String.format("%,d", guildSeasonLeaderboardLabelInfo.getScore());
scoreDiffString = String.format(
"%,d",
nextLabelInfo == null ? 0 : guildSeasonLeaderboardLabelInfo.getScore() - nextLabelInfo.getScore());
}

// §<color><place>§7 - §b<guild name> [<guild tag>]§d (<formatted score> SR) §a(+<score diff to next>)
MutableComponent newLabel = Component.empty()
.append(Component.literal(String.valueOf(guildSeasonLeaderboardLabelInfo.getPlace()))
.withStyle(
guildSeasonLeaderboardLabelInfo.getPlace() == 1
? ChatFormatting.BOLD
: ChatFormatting.RESET)
.withStyle(placeColor))
.append(Component.literal(" - ").withStyle(ChatFormatting.GRAY))
.append(Component.literal(guildSeasonLeaderboardLabelInfo.getGuild())
.withStyle(ChatFormatting.AQUA))
.append(Component.literal(
" [" + guildProfile.map(GuildProfile::prefix).orElse("???") + "]")
.withStyle(ChatFormatting.AQUA))
.append(Component.literal(" (" + scoreString + " SR)").withStyle(ChatFormatting.LIGHT_PURPLE))
.append(Component.literal(" (+" + (nextLabelInfo == null ? "???" : scoreDiffString) + ")")
.withStyle(ChatFormatting.GREEN));

guildSeasonLeaderboardLabelInfo.getEntity().setCustomName(newLabel);
}

@SubscribeEvent
public void onLabelsRemoved(LabelsRemovedEvent event) {
event.getRemovedLabels().stream()
.filter(labelInfo -> labelInfo instanceof GuildSeasonLeaderboardLabelInfo)
.forEach(labelInfo -> labelInfos.remove(((GuildSeasonLeaderboardLabelInfo) labelInfo).getPlace()));
}

@SubscribeEvent
public void onWorldStateChange(WorldStateEvent event) {
labelInfos.clear();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © Wynntils 2023.
* Copyright © Wynntils 2023-2024.
* This file is released under LGPLv3. See LICENSE for full license details.
*/
package com.wynntils.handlers.labels;
Expand All @@ -10,16 +10,21 @@
import com.wynntils.handlers.labels.event.EntityLabelChangedEvent;
import com.wynntils.handlers.labels.event.EntityLabelVisibilityEvent;
import com.wynntils.handlers.labels.event.LabelIdentifiedEvent;
import com.wynntils.handlers.labels.event.LabelsRemovedEvent;
import com.wynntils.handlers.labels.type.LabelInfo;
import com.wynntils.handlers.labels.type.LabelParser;
import com.wynntils.mc.event.RemoveEntitiesEvent;
import com.wynntils.mc.event.SetEntityDataEvent;
import com.wynntils.models.worlds.event.WorldStateEvent;
import com.wynntils.utils.mc.McUtils;
import com.wynntils.utils.mc.type.Location;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import net.minecraft.core.Position;
import net.minecraft.network.chat.Component;
import net.minecraft.network.syncher.EntityDataSerializer;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.world.entity.Entity;
import net.minecraftforge.eventbus.api.EventPriority;
Expand All @@ -28,11 +33,16 @@
public class LabelHandler extends Handler {
private final List<LabelParser> parsers = new ArrayList<>();

private final Map<Integer, LabelInfo> liveLabels = new HashMap<>();

@SubscribeEvent(priority = EventPriority.HIGHEST)
public void onEntitySetData(SetEntityDataEvent event) {
Entity entity = McUtils.mc().level.getEntity(event.getId());
if (entity == null) return;

SynchedEntityData.DataValue<?> oldNameData = null;
SynchedEntityData.DataValue<Optional<Component>> newCustomNameData = null;

for (SynchedEntityData.DataValue<?> packedItem : event.getPackedItems()) {
if (packedItem.id() == Entity.DATA_CUSTOM_NAME_VISIBLE.getId()) {
WynntilsMod.postEvent(new EntityLabelVisibilityEvent(entity, (Boolean) packedItem.value()));
Expand All @@ -41,34 +51,84 @@ public void onEntitySetData(SetEntityDataEvent event) {

if (packedItem.id() == Entity.DATA_CUSTOM_NAME.getId()) {
Optional<Component> value = (Optional<Component>) packedItem.value();
if (value.isEmpty()) return;
if (value.isEmpty()) continue;

Component oldNameComponent = entity.getCustomName();
StyledText oldName =
oldNameComponent != null ? StyledText.fromComponent(oldNameComponent) : StyledText.EMPTY;
StyledText newName = StyledText.fromComponent(value.get());

// Sometimes there is no actual change; ignore it then
if (newName.equals(oldName)) return;
if (newName.equals(oldName)) continue;

LabelInfo labelInfo = tryIdentifyLabel(newName, entity);
if (labelInfo != null) {
liveLabels.put(entity.getId(), labelInfo);
}

EntityLabelChangedEvent labelChangedEvent =
new EntityLabelChangedEvent(entity, newName, oldName, labelInfo);
WynntilsMod.postEvent(labelChangedEvent);

tryIdentifyLabel(newName, entity.position());
WynntilsMod.postEvent(new EntityLabelChangedEvent(entity, newName, oldName));
// If the event was cancelled, remove the name change data
if (labelChangedEvent.isCanceled()) {
oldNameData = packedItem;
continue;
}

// If the event changed the name, update the data
if (!labelChangedEvent.getName().equals(newName)) {
oldNameData = packedItem;
newCustomNameData = new SynchedEntityData.DataValue<>(
Entity.DATA_CUSTOM_NAME.getId(),
(EntityDataSerializer<Optional<Component>>) packedItem.serializer(),
Optional.of(labelChangedEvent.getName().getComponent()));
}
}
}

// If the name was removed, remove the old data
if (oldNameData != null) {
event.removePackedItem(oldNameData);
}

// If the name was changed, add the new data
if (newCustomNameData != null) {
event.addPackedItem(newCustomNameData);
}
}

@SubscribeEvent(priority = EventPriority.HIGHEST)
public void onEntitiesRemoved(RemoveEntitiesEvent event) {
List<LabelInfo> removedLabels = liveLabels.values().stream()
.filter(label -> event.getEntityIds().contains(label.getEntity().getId()))
.toList();

removedLabels.forEach(label -> liveLabels.remove(label.getEntity().getId()));
WynntilsMod.postEvent(new LabelsRemovedEvent(removedLabels));
}

@SubscribeEvent
public void onWorldStateChange(WorldStateEvent event) {
List<LabelInfo> oldLabels = new ArrayList<>(liveLabels.values());
liveLabels.clear();
WynntilsMod.postEvent(new LabelsRemovedEvent(oldLabels));
}

public void registerParser(LabelParser labelParser) {
parsers.add(labelParser);
}

private void tryIdentifyLabel(StyledText name, Position position) {
private LabelInfo tryIdentifyLabel(StyledText name, Entity entity) {
for (LabelParser parser : parsers) {
LabelInfo info = parser.getInfo(name, Location.containing(position));
LabelInfo info = parser.getInfo(name, Location.containing(entity.position()), entity);

if (info == null) continue;

WynntilsMod.postEvent(new LabelIdentifiedEvent(info));
return;
return info;
}

return null;
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
/*
* Copyright © Wynntils 2023.
* Copyright © Wynntils 2023-2024.
* This file is released under LGPLv3. See LICENSE for full license details.
*/
package com.wynntils.handlers.labels.event;

import com.wynntils.core.text.StyledText;
import com.wynntils.handlers.labels.type.LabelInfo;
import java.util.Optional;
import net.minecraft.world.entity.Entity;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;

@Cancelable
public class EntityLabelChangedEvent extends Event {
private final Entity entity;
private final StyledText name;
private final StyledText oldName;
private final LabelInfo labelInfo;

public EntityLabelChangedEvent(Entity entity, StyledText name, StyledText oldName) {
private StyledText name;

public EntityLabelChangedEvent(Entity entity, StyledText name, StyledText oldName, LabelInfo labelInfo) {
this.entity = entity;
this.name = name;
this.oldName = oldName;
this.labelInfo = labelInfo;
}

public Entity getEntity() {
Expand All @@ -27,7 +34,15 @@ public StyledText getName() {
return name;
}

public void setName(StyledText name) {
this.name = name;
}

public StyledText getOldName() {
return oldName;
}

public Optional<LabelInfo> getLabelInfo() {
return Optional.ofNullable(labelInfo);
}
}

0 comments on commit 5e80f8e

Please sign in to comment.