Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better SceneTags #2361

Merged
merged 11 commits into from
Sep 16, 2023
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package emu.grasscutter.command.commands;

import emu.grasscutter.command.*;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.scene.SceneTagData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.packet.send.*;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import lombok.val;

import java.util.*;

@Command(label = "setSceneTag", aliases = { "tag" }, usage = {
"<add|remove|unlockall> <sceneTagId>" }, permission = "player.setscenetag", permissionTargeted = "player.setscenetag.others")
public final class SetSceneTagCommand implements CommandHandler {
private final Int2ObjectMap<SceneTagData> sceneTagData = GameData.getSceneTagDataMap();

@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() == 0) {
sendUsageMessage(sender);
return;
}

val actionStr = args.get(0).toLowerCase();
var value = -1;

if (args.size() > 1) {
try {
value = Integer.parseInt(args.get(1));
} catch (NumberFormatException ignored) {
CommandHandler.sendTranslatedMessage(sender, "commands.execution.argument_error");
return;
}
} else {
if (actionStr.equals("unlockall")) {
unlockAllSceneTags(targetPlayer);
return;
} else {
CommandHandler.sendTranslatedMessage(sender, "commands.execution.argument_error");
return;
}
}

val userVal = value;

var sceneData = sceneTagData.values().stream().filter(sceneTag -> sceneTag.getId() == userVal).findFirst();
if (sceneData == null) {
CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.id");
return;
}
int scene = sceneData.get().getSceneId();

switch (actionStr) {
case "add", "set" -> addSceneTag(targetPlayer, scene, value);
case "remove", "del" -> removeSceneTag(targetPlayer, scene, value);
default -> CommandHandler.sendTranslatedMessage(sender, "commands.execution.argument_error");
}

CommandHandler.sendTranslatedMessage(sender, "commands.generic.set_to", value, actionStr);

}

private void addSceneTag(Player targetPlayer, int scene, int value) {
targetPlayer.getProgressManager().addSceneTag(scene, value);
}

private void removeSceneTag(Player targetPlayer, int scene, int value) {
targetPlayer.getProgressManager().delSceneTag(scene, value);
}

private void unlockAllSceneTags(Player targetPlayer) {
var allData = sceneTagData.values();

// Add all SceneTags
allData.stream().toList().forEach(sceneTag -> {
if (targetPlayer.getSceneTags().get(sceneTag.getSceneId()) == null) {
targetPlayer.getSceneTags().put(sceneTag.getSceneId(), new HashSet<>());
}
targetPlayer.getSceneTags().get(sceneTag.getSceneId()).add(sceneTag.getId());
});

// Remove default SceneTags, as most are "before" or "locked" states
allData.stream().filter(sceneTag -> sceneTag.isDefaultValid())
// Only remove for big world as some other scenes only have defaults
.filter(sceneTag -> sceneTag.getSceneId() == 3)
.forEach(sceneTag -> {
targetPlayer.getSceneTags().get(sceneTag.getSceneId()).remove(sceneTag.getId());
});

this.setSceneTags(targetPlayer);
}

private void setSceneTags(Player targetPlayer) {
targetPlayer.sendPacket(new PacketPlayerWorldSceneInfoListNotify(targetPlayer));
}

}
21 changes: 21 additions & 0 deletions src/main/java/emu/grasscutter/game/player/Player.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ public class Player implements PlayerHook, FieldFetch {
@Getter private Map<Integer, ActiveCookCompoundData> activeCookCompounds;
@Getter private Map<Integer, Integer> questGlobalVariables;
@Getter private Map<Integer, Integer> openStates;
@Getter private Map<Integer, Set<Integer>> sceneTags;
@Getter @Setter private Map<Integer, Set<Integer>> unlockedSceneAreas;
@Getter @Setter private Map<Integer, Set<Integer>> unlockedScenePoints;
@Getter @Setter private List<Integer> chatEmojiIdList;
Expand Down Expand Up @@ -244,6 +245,7 @@ public Player() {
this.unlockedRecipies = new HashMap<>();
this.questGlobalVariables = new HashMap<>();
this.openStates = new HashMap<>();
this.sceneTags = new HashMap<>();
this.unlockedSceneAreas = new HashMap<>();
this.unlockedScenePoints = new HashMap<>();
this.chatEmojiIdList = new ArrayList<>();
Expand Down Expand Up @@ -295,6 +297,7 @@ public Player(GameSession session) {
this.codex = new PlayerCodex(this);

this.applyProperties();
this.applyStartingSceneTags();
this.getFlyCloakList().add(140001);
this.getNameCardList().add(210001);

Expand Down Expand Up @@ -587,6 +590,20 @@ private void applyProperties() {
this.getProperty(PlayerProperty.PROP_DIVE_MAX_STAMINA));
}

/**
* Applies all default scenetags to the player.
*/
private void applyStartingSceneTags() {
GameData.getSceneTagDataMap().values().stream()
.filter(sceneTag -> sceneTag.isDefaultValid())
.forEach(sceneTag -> {
if (this.getSceneTags().get(sceneTag.getSceneId()) == null) {
this.getSceneTags().put(sceneTag.getSceneId(), new HashSet<>());
}
this.getSceneTags().get(sceneTag.getSceneId()).add(sceneTag.getId());
});
}

/**
* Applies a property to the player if it doesn't exist in the database.
*
Expand Down Expand Up @@ -1384,6 +1401,10 @@ public void onLogin() {
}
*/

// Ensure the player has valid scenetags, allows old accounts to work
if (this.getSceneTags().isEmpty() || this.getSceneTags() == null) {
this.applyStartingSceneTags();
}

if (GameHome.HOME_SCENE_IDS.contains(this.getSceneId())) {
this.setSceneId(this.prevScene <= 0 ? 3 : this.prevScene); // if the player in home, make the player go back.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.packet.send.*;

import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -313,4 +315,28 @@ public void addItemObtainedHistory(int id, int count) {
player.save();
player.getQuestManager().queueEvent(QuestCond.QUEST_COND_HISTORY_GOT_ANY_ITEM, id, newCount);
}

/******************************************************************************************************************
******************************************************************************************************************
* SCENETAGS
******************************************************************************************************************
*****************************************************************************************************************/
public void addSceneTag(int sceneId, int sceneTagId) {
player.getSceneTags().computeIfAbsent(sceneId, k -> new HashSet<>()).add(sceneTagId);
player.sendPacket(new PacketPlayerWorldSceneInfoListNotify(player));
}

public void delSceneTag(int sceneId, int sceneTagId) {
// Sanity check
if (player.getSceneTags().get(sceneId) == null) {
// Can't delete something that doesn't exist
return;
}
player.getSceneTags().get(sceneId).remove(sceneTagId);
player.sendPacket(new PacketPlayerWorldSceneInfoListNotify(player));
}

public boolean checkSceneTag(int sceneId, int sceneTagId) {
return player.getSceneTags().get(sceneId).contains(sceneTagId);
}
}
14 changes: 7 additions & 7 deletions src/main/java/emu/grasscutter/scripts/ScriptLib.java
Original file line number Diff line number Diff line change
Expand Up @@ -1013,22 +1013,22 @@ public int ChangeToTargetLevelTag(int var1){
}

public int AddSceneTag(int sceneId, int sceneTagId){
logger.warn("[LUA] Call unimplemented AddSceneTag with {}, {}", sceneId, sceneTagId);
//TODO implement
logger.debug("[LUA] Call AddSceneTag with {}, {}", sceneId, sceneTagId);
getSceneScriptManager().getScene().getHost().getProgressManager().addSceneTag(sceneId, sceneTagId);
return 0;
}

public int DelSceneTag(int sceneId, int sceneTagId){
logger.warn("[LUA] Call unimplemented DelSceneTag with {}, {}", sceneId, sceneTagId);
//TODO implement
logger.debug("[LUA] Call DelSceneTag with {}, {}", sceneId, sceneTagId);
getSceneScriptManager().getScene().getHost().getProgressManager().delSceneTag(sceneId, sceneTagId);
return 0;
}

public boolean CheckSceneTag(int sceneId, int sceneTagId){
logger.warn("[LUA] Call unimplemented CheckSceneTag with {}, {}", sceneId, sceneTagId);
//TODO implement
return false;
logger.debug("[LUA] Call CheckSceneTag with {}, {}", sceneId, sceneTagId);
return getSceneScriptManager().getScene().getHost().getProgressManager().checkSceneTag(sceneId, sceneTagId);
}

public int StartHomeGallery(int galleryId, int uid){
logger.warn("[LUA] Call unimplemented StartHomeGallery with {} {}", galleryId, uid);
//TODO implement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public void handle(GameSession session, byte[] header, byte[] payload) throws Ex
session.send(new PacketServerTimeNotify());
session.send(new PacketWorldPlayerInfoNotify(world));
session.send(new PacketWorldDataNotify(world));
session.send(new PacketPlayerWorldSceneInfoListNotify());
session.send(new PacketPlayerWorldSceneInfoListNotify(player));
session.send(new BasePacket(PacketOpcodes.SceneForceUnlockNotify));
session.send(new PacketHostPlayerNotify(world));

Expand Down
Original file line number Diff line number Diff line change
@@ -1,46 +1,48 @@
package emu.grasscutter.server.packet.send;

import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.scene.SceneTagData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.*;
import emu.grasscutter.net.proto.MapLayerInfoOuterClass;
import emu.grasscutter.net.proto.PlayerWorldSceneInfoListNotifyOuterClass.PlayerWorldSceneInfoListNotify;
import emu.grasscutter.net.proto.PlayerWorldSceneInfoOuterClass.PlayerWorldSceneInfo;
import java.util.stream.IntStream;

import java.util.Map;

public class PacketPlayerWorldSceneInfoListNotify extends BasePacket {

public PacketPlayerWorldSceneInfoListNotify() {
public PacketPlayerWorldSceneInfoListNotify(Player player) {
super(PacketOpcodes.PlayerWorldSceneInfoListNotify); // Rename opcode later

var sceneTags = player.getSceneTags();

PlayerWorldSceneInfoListNotify.Builder proto =
PlayerWorldSceneInfoListNotify.newBuilder()
.addInfoList(PlayerWorldSceneInfo.newBuilder().setSceneId(1).setIsLocked(false).build())
.addInfoList(
PlayerWorldSceneInfo.newBuilder()
.setSceneId(3)
.setIsLocked(false)
.addInfoList(PlayerWorldSceneInfo.newBuilder().setSceneId(1).setIsLocked(false).build());

// Iterate over all scenes
for (int scene : GameData.getSceneDataMap().keySet()) {
var worldInfoBuilder = PlayerWorldSceneInfo.newBuilder()
.setSceneId(scene)
.setIsLocked(false);

/** Add scene-specific data */

// Scenetags
if (sceneTags.keySet().contains(scene)) {
worldInfoBuilder
.addAllSceneTagIdList(
GameData.getSceneTagDataMap().values().stream()
.filter(sceneTag -> sceneTag.getSceneId() == 3)
.filter(
sceneTag ->
sceneTag.isDefaultValid()
|| sceneTag.getCond().get(0).getCondType() != null)
.map(SceneTagData::getId)
.toList())
// .addSceneTagIdList(102) // Jade chamber (alr added)
// .addSceneTagIdList(113)
// .addSceneTagIdList(117)
// .addSceneTagIdList(1093) // 3.0 Vana_real
.addSceneTagIdList(1094) // 3.0 Vana_dream
// .addSceneTagIdList(1095) // 3.0 Vana_first
// .addSceneTagIdList(1096) // 3.0 Vana_festival
.addSceneTagIdList(152) // 3.1 event
.addSceneTagIdList(153) // 3.1 event
.addSceneTagIdList(1164) // Desert Arena (XMSM_CWLTop)
.addSceneTagIdList(1166) // Desert Pyramid (CWL_Trans_02)
.setMapLayerInfo(
sceneTags.entrySet().stream()
.filter(e -> e.getKey().equals(scene))
.map(Map.Entry::getValue)
.toList()
.get(0)
);
}

// Map layer information (Big world)
if (scene == 3) {
worldInfoBuilder.setMapLayerInfo(
MapLayerInfoOuterClass.MapLayerInfo.newBuilder()
.addAllUnlockedMapLayerIdList(
GameData.getMapLayerDataMap().keySet()) // MapLayer Ids
Expand All @@ -49,31 +51,11 @@ public PacketPlayerWorldSceneInfoListNotify() {
.addAllUnlockedMapLayerGroupIdList(
GameData.getMapLayerGroupDataMap()
.keySet()) // will show MapLayer options when hovered over
.build()) // map layer test
.build())
.addInfoList(
PlayerWorldSceneInfo.newBuilder()
.setSceneId(4)
.setIsLocked(false)
.addSceneTagIdList(106)
.addSceneTagIdList(109)
.addSceneTagIdList(117)
.build())
.addInfoList(PlayerWorldSceneInfo.newBuilder().setSceneId(5).setIsLocked(false).build())
.addInfoList(PlayerWorldSceneInfo.newBuilder().setSceneId(6).setIsLocked(false).build())
.addInfoList(PlayerWorldSceneInfo.newBuilder().setSceneId(7).setIsLocked(false).build())
.addInfoList(
PlayerWorldSceneInfo.newBuilder()
.setSceneId(9)
.setIsLocked(false)
.addAllSceneTagIdList(IntStream.range(0, 3000).boxed().toList())
.build())
.addInfoList(
PlayerWorldSceneInfo.newBuilder()
.setSceneId(10)
.setIsLocked(false)
.addAllSceneTagIdList(IntStream.range(0, 3000).boxed().toList())
.build()); // 3.8
.build()); // map layer test
}

proto.addInfoList(worldInfoBuilder.build());
}

this.setData(proto);
}
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/languages/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,9 @@
"setProp": {
"description": "Sets accountwide properties. Things like godmode can be enabled this way, as well as changing things like unlocked abyss floor and battle pass progress.\n\tValues for <prop> (case-insensitive): GodMode | UnlimitedStamina | UnlimitedEnergy | TowerLevel | WorldLevel | BPLevel | SetOpenState | UnsetOpenState | UnlockMap\n\t(cont.) see PlayerProperty enum for other possible values, of the form PROP_MAX_SPRING_VOLUME -> max_spring_volume"
},
"setSceneTag":{
"description": "Sets account-specific scene tags. This controls things like rocks blocking doors, buildings being visible, and other (usually quest-related) things that affect what is visible in your world."
},
"setStats": {
"description": "Sets fight property for your current active character\n\tValues for <stat>: hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(cont.) Elemental DMG Bonus: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) Elemental RES: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys",
"locked_to": "%s locked to %s.",
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/languages/es-ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,9 @@
"setProp": {
"description": "Establece propiedades de la cuenta. Cosas como el modo Dios pueden ser establecidos con este comando, además de cambiar cosas como desbloquear pisos del abismo o progreso del pase de batalla.\n\tValores para <prop>: godmode | nostamina | unlimitedenergy | abyss | worldlevel | bplevel\n\t(cont.) Observa PlayerProperty enum para ver otros posibles valores, de la forma PROP_MAX_SPRING_VOLUME -> max_spring_volume"
},
"setSceneTag":{
"description": "Establece etiquetas de escena específicas de la cuenta. Esto controla cosas como rocas bloqueando puertas, edificios visibles y otras cosas (normalmente relacionadas con misiones) que afectan a lo que es visible en tu mundo."
},
"setStats": {
"description": "Establece propiedades de combate para tu personaje actual\n\tValores para <estado>: hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(cont.) Bonus de daño elemental: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) Resistencia elemental: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys",
"locked_to": "%s fijado a %s.",
Expand Down