Skip to content

Commit

Permalink
Better SceneTags (#2361)
Browse files Browse the repository at this point in the history
* Add scene tag handling

* Move warns to debug

* Move to PlayerProgressManager

* Add success message

* Inline check

* Improve per-scene handling

* Update src/main/java/emu/grasscutter/command/commands/SetSceneTagCommand.java

Co-authored-by: Magix <27646710+KingRainbow44@users.noreply.github.com>

* Update src/main/java/emu/grasscutter/command/commands/SetSceneTagCommand.java

Co-authored-by: Magix <27646710+KingRainbow44@users.noreply.github.com>

* Fix types

* Small fix

* Update ScriptLib.java

---------

Co-authored-by: Magix <27646710+KingRainbow44@users.noreply.github.com>
  • Loading branch information
NotThorny and KingRainbow44 committed Sep 16, 2023
1 parent 5bd8f53 commit 0e44d18
Show file tree
Hide file tree
Showing 17 changed files with 221 additions and 61 deletions.
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

0 comments on commit 0e44d18

Please sign in to comment.