Run spotlessApply

This commit is contained in:
KingRainbow44 2023-04-18 17:17:55 -04:00
parent 75f163f704
commit ee298235c2
No known key found for this signature in database
GPG Key ID: FC2CB64B00D257BE
44 changed files with 598 additions and 424 deletions

View File

@ -189,26 +189,33 @@ public final class GameData {
@Getter @Getter
private static final Int2ObjectMap<CookRecipeData> cookRecipeDataMap = private static final Int2ObjectMap<CookRecipeData> cookRecipeDataMap =
new Int2ObjectOpenHashMap<>(); new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<CompoundData> compoundDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<CompoundData> compoundDataMap = new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<DailyDungeonData> dailyDungeonDataMap = private static final Int2ObjectMap<DailyDungeonData> dailyDungeonDataMap =
new Int2ObjectOpenHashMap<>(); new Int2ObjectOpenHashMap<>();
@Getter private static final Int2ObjectMap<DropTableData> dropTableDataMap = new Int2ObjectOpenHashMap<>();
@Getter private static final Int2ObjectMap<DropMaterialData> dropMaterialDataMap = new Int2ObjectOpenHashMap<>(); @Getter
private static final Int2ObjectMap<DropTableData> dropTableDataMap =
new Int2ObjectOpenHashMap<>();
@Getter
private static final Int2ObjectMap<DropMaterialData> dropMaterialDataMap =
new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<DungeonData> dungeonDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<DungeonData> dungeonDataMap = new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<DungeonEntryData> dungeonEntryDataMap = private static final Int2ObjectMap<DungeonEntryData> dungeonEntryDataMap =
new Int2ObjectOpenHashMap<>(); new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<EnvAnimalGatherConfigData> envAnimalGatherConfigDataMap = private static final Int2ObjectMap<EnvAnimalGatherConfigData> envAnimalGatherConfigDataMap =
new Int2ObjectOpenHashMap<>(); new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<EquipAffixData> equipAffixDataMap = private static final Int2ObjectMap<EquipAffixData> equipAffixDataMap =

View File

@ -44,11 +44,10 @@ import java.util.concurrent.CopyOnWriteArraySet;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import lombok.val;
import org.reflections.Reflections;
import javax.script.Bindings; import javax.script.Bindings;
import javax.script.CompiledScript; import javax.script.CompiledScript;
import lombok.val;
import org.reflections.Reflections;
public final class ResourceLoader { public final class ResourceLoader {
@ -666,27 +665,31 @@ public final class ResourceLoader {
val pattern = Pattern.compile("ConfigLevelEntity_(.+?)\\.json"); val pattern = Pattern.compile("ConfigLevelEntity_(.+?)\\.json");
try { try {
var stream = Files.newDirectoryStream(getResourcePath("BinOutput/LevelEntity/"), "ConfigLevelEntity_*.json"); var stream =
stream.forEach(path -> { Files.newDirectoryStream(
val matcher = pattern.matcher(path.getFileName().toString()); getResourcePath("BinOutput/LevelEntity/"), "ConfigLevelEntity_*.json");
if (!matcher.find()) return; stream.forEach(
Map<String, ConfigLevelEntity> config; path -> {
val matcher = pattern.matcher(path.getFileName().toString());
if (!matcher.find()) return;
Map<String, ConfigLevelEntity> config;
try { try {
config = JsonUtils.loadToMap(path, String.class, ConfigLevelEntity.class); config = JsonUtils.loadToMap(path, String.class, ConfigLevelEntity.class);
} catch (Exception e) { } catch (Exception e) {
Grasscutter.getLogger().error("Error loading player ability embryos:", e); Grasscutter.getLogger().error("Error loading player ability embryos:", e);
return; return;
} }
GameData.getConfigLevelEntityDataMap().putAll(config); GameData.getConfigLevelEntityDataMap().putAll(config);
}); });
stream.close(); stream.close();
} catch (IOException e) { } catch (IOException e) {
Grasscutter.getLogger().error("Error loading config level entity: no files found"); Grasscutter.getLogger().error("Error loading config level entity: no files found");
return; return;
} }
if (GameData.getConfigLevelEntityDataMap() == null || GameData.getConfigLevelEntityDataMap().isEmpty()) { if (GameData.getConfigLevelEntityDataMap() == null
|| GameData.getConfigLevelEntityDataMap().isEmpty()) {
Grasscutter.getLogger().error("No config level entity loaded!"); Grasscutter.getLogger().error("No config level entity loaded!");
return; return;
} }
@ -698,33 +701,52 @@ public final class ResourceLoader {
try { try {
var bindings = ScriptLoader.getEngine().createBindings(); var bindings = ScriptLoader.getEngine().createBindings();
var stream = Files.newDirectoryStream(getResourcePath("Scripts/Quest/Share/"), "Q*ShareConfig.lua"); var stream =
stream.forEach(path -> { Files.newDirectoryStream(getResourcePath("Scripts/Quest/Share/"), "Q*ShareConfig.lua");
val matcher = pattern.matcher(path.getFileName().toString()); stream.forEach(
if (!matcher.find()) return; path -> {
val matcher = pattern.matcher(path.getFileName().toString());
if (!matcher.find()) return;
var cs = ScriptLoader.getScript("Quest/Share/" + path.getFileName().toString()); var cs = ScriptLoader.getScript("Quest/Share/" + path.getFileName().toString());
if (cs == null) return; if (cs == null) return;
try{ try {
cs.eval(bindings); cs.eval(bindings);
// these are Map<String, class> // these are Map<String, class>
var teleportDataMap = ScriptLoader.getSerializer().toMap(TeleportData.class, bindings.get("quest_data")); var teleportDataMap =
var rewindDataMap = ScriptLoader.getSerializer().toMap(RewindData.class, bindings.get("rewind_data")); ScriptLoader.getSerializer()
// convert them to Map<Integer, class> and cache .toMap(TeleportData.class, bindings.get("quest_data"));
GameData.getTeleportDataMap().putAll(teleportDataMap.entrySet().stream().collect(Collectors.toMap(entry -> Integer.valueOf(entry.getKey()), Entry::getValue))); var rewindDataMap =
GameData.getRewindDataMap().putAll(rewindDataMap.entrySet().stream().collect(Collectors.toMap(entry -> Integer.valueOf(entry.getKey()), Entry::getValue))); ScriptLoader.getSerializer().toMap(RewindData.class, bindings.get("rewind_data"));
} catch (Throwable e){ // convert them to Map<Integer, class> and cache
Grasscutter.getLogger().error("Error while loading Quest Share Config: {}", path.getFileName().toString()); GameData.getTeleportDataMap()
} .putAll(
}); teleportDataMap.entrySet().stream()
.collect(
Collectors.toMap(
entry -> Integer.valueOf(entry.getKey()), Entry::getValue)));
GameData.getRewindDataMap()
.putAll(
rewindDataMap.entrySet().stream()
.collect(
Collectors.toMap(
entry -> Integer.valueOf(entry.getKey()), Entry::getValue)));
} catch (Throwable e) {
Grasscutter.getLogger()
.error(
"Error while loading Quest Share Config: {}", path.getFileName().toString());
}
});
stream.close(); stream.close();
} catch (IOException e) { } catch (IOException e) {
Grasscutter.getLogger().error("Error loading Quest Share Config: no files found"); Grasscutter.getLogger().error("Error loading Quest Share Config: no files found");
return; return;
} }
if (GameData.getTeleportDataMap() == null || GameData.getTeleportDataMap().isEmpty() if (GameData.getTeleportDataMap() == null
|| GameData.getRewindDataMap() == null || GameData.getRewindDataMap().isEmpty()) { || GameData.getTeleportDataMap().isEmpty()
|| GameData.getRewindDataMap() == null
|| GameData.getRewindDataMap().isEmpty()) {
Grasscutter.getLogger().error("No Quest Share Config loaded!"); Grasscutter.getLogger().error("No Quest Share Config loaded!");
return; return;
} }
@ -735,8 +757,9 @@ public final class ResourceLoader {
val gadgetMap = GameData.getGadgetMappingMap(); val gadgetMap = GameData.getGadgetMappingMap();
try { try {
JsonUtils.loadToList(getResourcePath("Server/GadgetMapping.json"), GadgetMapping.class) JsonUtils.loadToList(getResourcePath("Server/GadgetMapping.json"), GadgetMapping.class)
.forEach(entry -> gadgetMap.put(entry.getGadgetId(), entry)); .forEach(entry -> gadgetMap.put(entry.getGadgetId(), entry));
} catch (IOException | NullPointerException ignored) {} } catch (IOException | NullPointerException ignored) {
}
Grasscutter.getLogger().debug("Loaded {} gadget mappings.", gadgetMap.size()); Grasscutter.getLogger().debug("Loaded {} gadget mappings.", gadgetMap.size());
} catch (Exception e) { } catch (Exception e) {
Grasscutter.getLogger().error("Unable to load gadget mappings.", e); Grasscutter.getLogger().error("Unable to load gadget mappings.", e);
@ -747,8 +770,11 @@ public final class ResourceLoader {
try { try {
val gadgetMap = GameData.getActivityCondGroupMap(); val gadgetMap = GameData.getActivityCondGroupMap();
try { try {
JsonUtils.loadToList(getResourcePath("Server/ActivityCondGroups.json"), ActivityCondGroup.class).forEach(entry -> gadgetMap.put(entry.getCondGroupId(), entry)); JsonUtils.loadToList(
} catch (IOException | NullPointerException ignored) {} getResourcePath("Server/ActivityCondGroups.json"), ActivityCondGroup.class)
.forEach(entry -> gadgetMap.put(entry.getCondGroupId(), entry));
} catch (IOException | NullPointerException ignored) {
}
Grasscutter.getLogger().debug("Loaded {} ActivityCondGroups.", gadgetMap.size()); Grasscutter.getLogger().debug("Loaded {} ActivityCondGroups.", gadgetMap.size());
} catch (Exception e) { } catch (Exception e) {
Grasscutter.getLogger().error("Unable to load ActivityCondGroups.", e); Grasscutter.getLogger().error("Unable to load ActivityCondGroups.", e);
@ -760,40 +786,47 @@ public final class ResourceLoader {
String pathName = "CustomResources/TrialAvatarExcels/"; String pathName = "CustomResources/TrialAvatarExcels/";
try { try {
JsonUtils.loadToList( JsonUtils.loadToList(
getResourcePath(pathName + "TrialAvatarActivityDataExcelConfigData.json"), getResourcePath(pathName + "TrialAvatarActivityDataExcelConfigData.json"),
TrialAvatarActivityDataData.class).forEach(instance -> { TrialAvatarActivityDataData.class)
instance.onLoad(); .forEach(
GameData.getTrialAvatarActivityDataCustomData() instance -> {
.put(instance.getTrialAvatarIndexId(), instance); instance.onLoad();
}); GameData.getTrialAvatarActivityDataCustomData()
} catch (IOException | NullPointerException ignored) {} .put(instance.getTrialAvatarIndexId(), instance);
});
} catch (IOException | NullPointerException ignored) {
}
Grasscutter.getLogger().debug("Loaded trial activity custom data."); Grasscutter.getLogger().debug("Loaded trial activity custom data.");
try { try {
JsonUtils.loadToList( JsonUtils.loadToList(
getResourcePath(pathName + "TrialAvatarActivityExcelConfigData.json"), getResourcePath(pathName + "TrialAvatarActivityExcelConfigData.json"),
TrialAvatarActivityCustomData.class).forEach(instance -> { TrialAvatarActivityCustomData.class)
instance.onLoad(); .forEach(
GameData.getTrialAvatarActivityCustomData() instance -> {
.put(instance.getScheduleId(), instance); instance.onLoad();
}); GameData.getTrialAvatarActivityCustomData()
} catch (IOException | NullPointerException ignored) {} .put(instance.getScheduleId(), instance);
});
} catch (IOException | NullPointerException ignored) {
}
Grasscutter.getLogger().debug("Loaded trial activity schedule custom data."); Grasscutter.getLogger().debug("Loaded trial activity schedule custom data.");
try { try {
JsonUtils.loadToList( JsonUtils.loadToList(
getResourcePath(pathName + "TrialAvatarData.json"), getResourcePath(pathName + "TrialAvatarData.json"), TrialAvatarCustomData.class)
TrialAvatarCustomData.class).forEach(instance -> { .forEach(
instance.onLoad(); instance -> {
GameData.getTrialAvatarCustomData() instance.onLoad();
.put(instance.getTrialAvatarId(), instance); GameData.getTrialAvatarCustomData().put(instance.getTrialAvatarId(), instance);
}); });
} catch (IOException | NullPointerException ignored) {} } catch (IOException | NullPointerException ignored) {
}
Grasscutter.getLogger().debug("Loaded trial avatar custom data."); Grasscutter.getLogger().debug("Loaded trial avatar custom data.");
} catch (Exception e) { } catch (Exception e) {
Grasscutter.getLogger().error("Unable to load trial avatar custom data.", e); Grasscutter.getLogger().error("Unable to load trial avatar custom data.", e);
} }
} }
private static void loadGroupReplacements(){ private static void loadGroupReplacements() {
Bindings bindings = ScriptLoader.getEngine().createBindings(); Bindings bindings = ScriptLoader.getEngine().createBindings();
CompiledScript cs = ScriptLoader.getScript("Scene/groups_replacement.lua"); CompiledScript cs = ScriptLoader.getScript("Scene/groups_replacement.lua");
@ -802,21 +835,29 @@ public final class ResourceLoader {
return; return;
} }
try{ try {
cs.eval(bindings); cs.eval(bindings);
// these are Map<String, class> // these are Map<String, class>
var replacementsMap = ScriptLoader.getSerializer().toMap(GroupReplacementData.class, bindings.get("replacements")); var replacementsMap =
ScriptLoader.getSerializer()
.toMap(GroupReplacementData.class, bindings.get("replacements"));
// convert them to Map<Integer, class> and cache // convert them to Map<Integer, class> and cache
GameData.getGroupReplacements().putAll(replacementsMap.entrySet().stream().collect(Collectors.toMap(entry -> Integer.valueOf(entry.getValue().getId()), Entry::getValue))); GameData.getGroupReplacements()
.putAll(
replacementsMap.entrySet().stream()
.collect(
Collectors.toMap(
entry -> Integer.valueOf(entry.getValue().getId()), Entry::getValue)));
} catch (Throwable e){ } catch (Throwable e) {
Grasscutter.getLogger().error("Error while loading Group Replacements"); Grasscutter.getLogger().error("Error while loading Group Replacements");
} }
if (GameData.getGroupReplacements() == null || GameData.getGroupReplacements().isEmpty()) { if (GameData.getGroupReplacements() == null || GameData.getGroupReplacements().isEmpty()) {
Grasscutter.getLogger().error("No Group Replacements loaded!"); Grasscutter.getLogger().error("No Group Replacements loaded!");
} else { } else {
Grasscutter.getLogger().debug("Loaded {} group replacements.", GameData.getGroupReplacements().size()); Grasscutter.getLogger()
.debug("Loaded {} group replacements.", GameData.getGroupReplacements().size());
} }
} }

View File

@ -5,8 +5,9 @@ import lombok.Getter;
@Getter @Getter
public class DropItemData { public class DropItemData {
@SerializedName(value="itemId") @SerializedName(value = "itemId")
private int id; private int id;
private String countRange; private String countRange;
private int weight; private int weight;
} }

View File

@ -9,6 +9,7 @@ import lombok.Getter;
public class DropMaterialData extends GameResource { public class DropMaterialData extends GameResource {
@Getter(onMethod_ = @Override) @Getter(onMethod_ = @Override)
private int id; private int id;
private boolean useOnGain; private boolean useOnGain;
private boolean disableFirstGainHint; private boolean disableFirstGainHint;
private boolean autoPick; private boolean autoPick;

View File

@ -4,15 +4,17 @@ import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.ResourceType.LoadPriority; import emu.grasscutter.data.ResourceType.LoadPriority;
import emu.grasscutter.data.common.DropItemData; import emu.grasscutter.data.common.DropItemData;
import java.util.List;
import lombok.Getter; import lombok.Getter;
import java.util.List; @ResourceType(
name = {"DropTableExcelConfigData.json", "DropSubTableExcelConfigData.json"},
@ResourceType(name ={"DropTableExcelConfigData.json","DropSubTableExcelConfigData.json"} , loadPriority = LoadPriority.HIGH) loadPriority = LoadPriority.HIGH)
@Getter @Getter
public class DropTableData extends GameResource { public class DropTableData extends GameResource {
@Getter(onMethod_ = @Override) @Getter(onMethod_ = @Override)
private int id; private int id;
private int randomType; private int randomType;
private int dropLevel; private int dropLevel;
private List<DropItemData> dropVec; private List<DropItemData> dropVec;

View File

@ -5,10 +5,9 @@ import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.excels.RewardPreviewData; import emu.grasscutter.data.excels.RewardPreviewData;
import emu.grasscutter.game.dungeons.enums.*; import emu.grasscutter.game.dungeons.enums.*;
import java.util.List;
import emu.grasscutter.scripts.data.SceneMeta; import emu.grasscutter.scripts.data.SceneMeta;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import java.util.List;
import lombok.Getter; import lombok.Getter;
@ResourceType(name = "DungeonExcelConfigData.json") @ResourceType(name = "DungeonExcelConfigData.json")

View File

@ -3,7 +3,6 @@ package emu.grasscutter.data.server;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.utils.GridPosition; import emu.grasscutter.utils.GridPosition;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
@ -11,15 +10,11 @@ import java.util.Set;
public class Grid { public class Grid {
public Map<String, Set<Integer>> grid; public Map<String, Set<Integer>> grid;
public Map<GridPosition, Set<Integer>> gridMap public Map<GridPosition, Set<Integer>> gridMap = new LinkedHashMap<>();
= new LinkedHashMap<>();
/** /** Loads the correct grid map. */
* Loads the correct grid map.
*/
public void load() { public void load() {
this.grid.forEach((position, groups) -> this.grid.forEach((position, groups) -> this.gridMap.put(new GridPosition(position), groups));
this.gridMap.put(new GridPosition(position), groups));
} }
/** /**

View File

@ -45,7 +45,6 @@ import java.util.*;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@ -138,17 +137,17 @@ public class Avatar {
// Combat properties // Combat properties
Stream.of(FightProperty.values()) Stream.of(FightProperty.values())
.map(FightProperty::getId) .map(FightProperty::getId)
.filter(id -> (id > 0) && (id < 3000)) .filter(id -> (id > 0) && (id < 3000))
.forEach(id -> this.setFightProperty(id, 0f)); .forEach(id -> this.setFightProperty(id, 0f));
this.setSkillDepotData(switch (this.getAvatarId()) { this.setSkillDepotData(
case GameConstants.MAIN_CHARACTER_MALE -> switch (this.getAvatarId()) {
GameData.getAvatarSkillDepotDataMap().get(501); case GameConstants.MAIN_CHARACTER_MALE -> GameData.getAvatarSkillDepotDataMap().get(501);
case GameConstants.MAIN_CHARACTER_FEMALE -> case GameConstants.MAIN_CHARACTER_FEMALE -> GameData.getAvatarSkillDepotDataMap()
GameData.getAvatarSkillDepotDataMap().get(701); .get(701);
default -> data.getSkillDepot(); default -> data.getSkillDepot();
}); });
// Set stats // Set stats
this.recalcStats(); this.recalcStats();
@ -180,10 +179,8 @@ public class Avatar {
* @return True if the avatar is a main character. * @return True if the avatar is a main character.
*/ */
public boolean isMainCharacter() { public boolean isMainCharacter() {
return List.of( return List.of(GameConstants.MAIN_CHARACTER_MALE, GameConstants.MAIN_CHARACTER_FEMALE)
GameConstants.MAIN_CHARACTER_MALE, .contains(this.getAvatarId());
GameConstants.MAIN_CHARACTER_FEMALE
).contains(this.getAvatarId());
} }
public Player getPlayer() { public Player getPlayer() {
@ -289,8 +286,8 @@ public class Avatar {
} }
/** /**
* Changes the avatar's element to the target element. * Changes the avatar's element to the target element. Only applies if the avatar has the element
* Only applies if the avatar has the element in its 'candSkillDepot's. * in its 'candSkillDepot's.
* *
* @param newElement The new element to change to. * @param newElement The new element to change to.
* @return True if the element was changed, false otherwise. * @return True if the element was changed, false otherwise.
@ -300,8 +297,8 @@ public class Avatar {
} }
/** /**
* Changes the avatar's element to the target element. * Changes the avatar's element to the target element. Only applies if the avatar has the element
* Only applies if the avatar has the element in its 'candSkillDepot's. * in its 'candSkillDepot's.
* *
* @param elementTypeToChange The new element to change to. * @param elementTypeToChange The new element to change to.
* @param notify Whether to notify the player of the change. * @param notify Whether to notify the player of the change.

View File

@ -18,7 +18,6 @@ import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketDropHintNotify; import emu.grasscutter.server.packet.send.PacketDropHintNotify;
import emu.grasscutter.server.packet.send.PacketGadgetAutoPickDropInfoNotify; import emu.grasscutter.server.packet.send.PacketGadgetAutoPickDropInfoNotify;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import java.util.*; import java.util.*;
public final class DropSystem extends BaseGameSystem { public final class DropSystem extends BaseGameSystem {
@ -47,7 +46,8 @@ public final class DropSystem extends BaseGameSystem {
chestReward.get(i.getIndex()).add(i); chestReward.get(i.getIndex()).add(i);
} }
} catch (Exception ignored) { } catch (Exception ignored) {
Grasscutter.getLogger().error("Unable to load chest drop data. Please place ChestDrop.json in data folder."); Grasscutter.getLogger()
.error("Unable to load chest drop data. Please place ChestDrop.json in data folder.");
} }
try { try {
@ -59,7 +59,8 @@ public final class DropSystem extends BaseGameSystem {
monsterDrop.get(i.getIndex()).add(i); monsterDrop.get(i.getIndex()).add(i);
} }
} catch (Exception ignored) { } catch (Exception ignored) {
Grasscutter.getLogger().error("Unable to load monster drop data. Please place MonsterDrop.json in data folder."); Grasscutter.getLogger()
.error("Unable to load monster drop data. Please place MonsterDrop.json in data folder.");
} }
} }
@ -97,7 +98,8 @@ public final class DropSystem extends BaseGameSystem {
List<GameItem> items = new ArrayList<>(); List<GameItem> items = new ArrayList<>();
processDrop(dropData, 1, items); processDrop(dropData, 1, items);
if (dropData.isFallToGround()) { if (dropData.isFallToGround()) {
dropItems(items, ActionReason.MonsterDie, monster, monster.getScene().getPlayers().get(0), true); dropItems(
items, ActionReason.MonsterDie, monster, monster.getScene().getPlayers().get(0), true);
} else { } else {
for (Player p : monster.getScene().getPlayers()) { for (Player p : monster.getScene().getPlayers()) {
p.getInventory().addItems(items, ActionReason.MonsterDie); p.getInventory().addItems(items, ActionReason.MonsterDie);
@ -137,8 +139,8 @@ public final class DropSystem extends BaseGameSystem {
} }
private void processDrop(DropTableData dropData, int count, List<GameItem> items) { private void processDrop(DropTableData dropData, int count, List<GameItem> items) {
//TODO:Not clear on the meaning of some fields,like "dropLevel".Will ignore them. // TODO:Not clear on the meaning of some fields,like "dropLevel".Will ignore them.
//TODO:solve drop limits,like everydayLimit. // TODO:solve drop limits,like everydayLimit.
if (count > 1) { if (count > 1) {
for (int i = 0; i < count; i++) processDrop(dropData, 1, items); for (int i = 0; i < count; i++) processDrop(dropData, 1, items);
return; return;
@ -158,7 +160,7 @@ public final class DropSystem extends BaseGameSystem {
if (id == 0) continue; if (id == 0) continue;
sum += i.getWeight(); sum += i.getWeight();
if (weight < sum) { if (weight < sum) {
//win the item // win the item
int amount = calculateDropAmount(i) * count; int amount = calculateDropAmount(i) * count;
if (amount <= 0) break; if (amount <= 0) break;
if (dropTable.containsKey(id)) { if (dropTable.containsKey(id)) {
@ -220,17 +222,25 @@ public final class DropSystem extends BaseGameSystem {
/** /**
* @param share Whether other players in the scene could see the drop items. * @param share Whether other players in the scene could see the drop items.
*/ */
private void dropItem(GameItem item, ActionReason reason, Player player, GameEntity bornFrom, boolean share) { private void dropItem(
GameItem item, ActionReason reason, Player player, GameEntity bornFrom, boolean share) {
DropMaterialData drop = GameData.getDropMaterialDataMap().get(item.getItemId()); DropMaterialData drop = GameData.getDropMaterialDataMap().get(item.getItemId());
if ((drop != null && drop.isAutoPick()) || (item.getItemData().getItemType() == ItemType.ITEM_VIRTUAL && item.getItemData().getGadgetId() == 0)) { if ((drop != null && drop.isAutoPick())
|| (item.getItemData().getItemType() == ItemType.ITEM_VIRTUAL
&& item.getItemData().getGadgetId() == 0)) {
giveItem(item, reason, player, share); giveItem(item, reason, player, share);
} else { } else {
//TODO:solve share problem // TODO:solve share problem
player.getScene().addDropEntity(item, bornFrom, player, share); player.getScene().addDropEntity(item, bornFrom, player, share);
} }
} }
private void dropItems(List<GameItem> items, ActionReason reason, GameEntity bornFrom, Player player, boolean share) { private void dropItems(
List<GameItem> items,
ActionReason reason,
GameEntity bornFrom,
Player player,
boolean share) {
for (var i : items) { for (var i : items) {
dropItem(i, reason, player, bornFrom, share); dropItem(i, reason, player, bornFrom, share);
} }
@ -249,7 +259,7 @@ public final class DropSystem extends BaseGameSystem {
} }
private void giveItems(List<GameItem> items, ActionReason reason, Player player, boolean share) { private void giveItems(List<GameItem> items, ActionReason reason, Player player, boolean share) {
//don't know whether we need PacketDropHintNotify. // don't know whether we need PacketDropHintNotify.
if (share) { if (share) {
for (var p : player.getScene().getPlayers()) { for (var p : player.getScene().getPlayers()) {
p.getInventory().addItems(items, reason); p.getInventory().addItems(items, reason);
@ -260,5 +270,4 @@ public final class DropSystem extends BaseGameSystem {
player.sendPacket(new PacketDropHintNotify(items, player.getPosition().toProto())); player.sendPacket(new PacketDropHintNotify(items, player.getPosition().toProto()));
} }
} }
} }

View File

@ -17,7 +17,6 @@ import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.List; import java.util.List;
public class DropSystemLegacy extends BaseGameSystem { public class DropSystemLegacy extends BaseGameSystem {
@ -49,20 +48,27 @@ public class DropSystemLegacy extends BaseGameSystem {
Grasscutter.getLogger().error("Unable to load drop data.", e); Grasscutter.getLogger().error("Unable to load drop data.", e);
} }
} }
private void addDropEntity(DropData dd, Scene dropScene, ItemData itemData, Position pos, int num, Player target) {
if (!dd.isGive() && (itemData.getItemType() != ItemType.ITEM_VIRTUAL || itemData.getGadgetId() != 0)) { private void addDropEntity(
DropData dd, Scene dropScene, ItemData itemData, Position pos, int num, Player target) {
if (!dd.isGive()
&& (itemData.getItemType() != ItemType.ITEM_VIRTUAL || itemData.getGadgetId() != 0)) {
EntityItem entity = new EntityItem(dropScene, target, itemData, pos, num, dd.isShare()); EntityItem entity = new EntityItem(dropScene, target, itemData, pos, num, dd.isShare());
if (!dd.isShare()) if (!dd.isShare()) dropScene.addEntityToSingleClient(target, entity);
dropScene.addEntityToSingleClient(target, entity); else dropScene.addEntity(entity);
else
dropScene.addEntity(entity);
} else { } else {
if (target != null) { if (target != null) {
target.getInventory().addItem(new GameItem(itemData, num), ActionReason.SubfieldDrop, true); target.getInventory().addItem(new GameItem(itemData, num), ActionReason.SubfieldDrop, true);
} else { } else {
// target is null if items will be added are shared. no one could pick it up because of the combination(give + shared) // target is null if items will be added are shared. no one could pick it up because of the
// combination(give + shared)
// so it will be sent to all players' inventories directly. // so it will be sent to all players' inventories directly.
dropScene.getPlayers().forEach(x -> x.getInventory().addItem(new GameItem(itemData, num), ActionReason.SubfieldDrop, true)); dropScene
.getPlayers()
.forEach(
x ->
x.getInventory()
.addItem(new GameItem(itemData, num), ActionReason.SubfieldDrop, true));
} }
} }
} }
@ -93,8 +99,7 @@ public class DropSystemLegacy extends BaseGameSystem {
int id = em.getMonsterData().getId(); int id = em.getMonsterData().getId();
if (getDropData().containsKey(id)) { if (getDropData().containsKey(id)) {
for (DropData dd : getDropData().get(id)) { for (DropData dd : getDropData().get(id)) {
if (dd.isShare()) if (dd.isShare()) processDrop(dd, em, null);
processDrop(dd, em, null);
else { else {
for (Player gp : em.getScene().getPlayers()) { for (Player gp : em.getScene().getPlayers()) {
processDrop(dd, em, gp); processDrop(dd, em, gp);

View File

@ -28,31 +28,46 @@ public class GadgetChest extends GadgetContent {
* @return Whether we should remove the gadget. * @return Whether we should remove the gadget.
*/ */
public boolean onInteract(Player player, GadgetInteractReq req) { public boolean onInteract(Player player, GadgetInteractReq req) {
//If bigWorldScript enabled,use new drop system. // If bigWorldScript enabled,use new drop system.
if (Grasscutter.getConfig().server.game.enableScriptInBigWorld) { if (Grasscutter.getConfig().server.game.enableScriptInBigWorld) {
SceneGadget chest = getGadget().getMetaGadget(); SceneGadget chest = getGadget().getMetaGadget();
DropSystem dropSystem = player.getServer().getDropSystem(); DropSystem dropSystem = player.getServer().getDropSystem();
if (chest.boss_chest != null && chest.drop_tag != null) { if (chest.boss_chest != null && chest.drop_tag != null) {
//Boss chest drop // Boss chest drop
//TODO:check for blossom chests // TODO:check for blossom chests
if (req.getOpType() == InterOpType.INTER_OP_TYPE_START) { if (req.getOpType() == InterOpType.INTER_OP_TYPE_START) {
//Two steps // Two steps
player.sendPacket(new PacketGadgetInteractRsp(getGadget(), InteractType.INTERACT_TYPE_OPEN_CHEST, InterOpType.INTER_OP_TYPE_START)); player.sendPacket(
new PacketGadgetInteractRsp(
getGadget(),
InteractType.INTERACT_TYPE_OPEN_CHEST,
InterOpType.INTER_OP_TYPE_START));
return false; return false;
} }
//TODO:check for take_num.(some boss rewards can only be claimed once a week.). Handle boss respawn. // TODO:check for take_num.(some boss rewards can only be claimed once a week.). Handle boss
//TODO:should return Retcode.RET_RESIN_NOT_ENOUGH ? // respawn.
if (player.getResinManager().useResin(chest.boss_chest.resin) && dropSystem.handleBossChestDrop(chest.drop_tag, player)) { // TODO:should return Retcode.RET_RESIN_NOT_ENOUGH ?
//Is it correct? if (player.getResinManager().useResin(chest.boss_chest.resin)
player.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_WORLD_BOSS_REWARD, chest.boss_chest.monster_config_id, 1); && dropSystem.handleBossChestDrop(chest.drop_tag, player)) {
// Is it correct?
player
.getBattlePassManager()
.triggerMission(
WatcherTriggerType.TRIGGER_WORLD_BOSS_REWARD,
chest.boss_chest.monster_config_id,
1);
getGadget().updateState(ScriptGadgetState.ChestOpened); getGadget().updateState(ScriptGadgetState.ChestOpened);
player.sendPacket(new PacketGadgetInteractRsp(this.getGadget(), InteractTypeOuterClass.InteractType.INTERACT_TYPE_OPEN_CHEST, InterOpType.INTER_OP_TYPE_FINISH)); player.sendPacket(
new PacketGadgetInteractRsp(
this.getGadget(),
InteractTypeOuterClass.InteractType.INTERACT_TYPE_OPEN_CHEST,
InterOpType.INTER_OP_TYPE_FINISH));
return true; return true;
} }
//if failed,fallback to legacy drop system. // if failed,fallback to legacy drop system.
} else { } else {
//Normal chest drop // Normal chest drop
//only the owner of the world can open chests. // only the owner of the world can open chests.
if (player != player.getWorld().getHost()) return false; if (player != player.getWorld().getHost()) return false;
boolean status = false; boolean status = false;
if (chest.drop_tag != null) { if (chest.drop_tag != null) {
@ -62,31 +77,56 @@ public class GadgetChest extends GadgetContent {
} }
if (status) { if (status) {
getGadget().updateState(ScriptGadgetState.ChestOpened); getGadget().updateState(ScriptGadgetState.ChestOpened);
player.sendPacket(new PacketGadgetInteractRsp(getGadget(), InteractType.INTERACT_TYPE_OPEN_CHEST, InterOpType.INTER_OP_TYPE_FINISH)); player.sendPacket(
player.sendPacket(new PacketWorldChestOpenNotify(getGadget().getGroupId(), player.getSceneId(), chest.config_id)); new PacketGadgetInteractRsp(
getGadget(),
InteractType.INTERACT_TYPE_OPEN_CHEST,
InterOpType.INTER_OP_TYPE_FINISH));
player.sendPacket(
new PacketWorldChestOpenNotify(
getGadget().getGroupId(), player.getSceneId(), chest.config_id));
return true; return true;
} }
//if failed,fallback to legacy drop system. // if failed,fallback to legacy drop system.
} }
Grasscutter.getLogger().warn("Can not solve chest drop: chest_drop_id = {} , drop_tag = {}.Fallback to legacy drop system.", chest.chest_drop_id, chest.drop_tag); Grasscutter.getLogger()
.warn(
"Can not solve chest drop: chest_drop_id = {} , drop_tag = {}.Fallback to legacy drop system.",
chest.chest_drop_id,
chest.drop_tag);
} }
//Legacy chest drop system // Legacy chest drop system
var chestInteractHandlerMap = getGadget().getScene().getWorld().getServer().getWorldDataSystem().getChestInteractHandlerMap(); var chestInteractHandlerMap =
getGadget()
.getScene()
.getWorld()
.getServer()
.getWorldDataSystem()
.getChestInteractHandlerMap();
var handler = chestInteractHandlerMap.get(getGadget().getGadgetData().getJsonName()); var handler = chestInteractHandlerMap.get(getGadget().getGadgetData().getJsonName());
if (handler == null) { if (handler == null) {
Grasscutter.getLogger().warn("Could not found the handler of this type of Chests {}", getGadget().getGadgetData().getJsonName()); Grasscutter.getLogger()
.warn(
"Could not found the handler of this type of Chests {}",
getGadget().getGadgetData().getJsonName());
return false; return false;
} }
if (req.getOpType() == InterOpType.INTER_OP_TYPE_START && handler.isTwoStep()) { if (req.getOpType() == InterOpType.INTER_OP_TYPE_START && handler.isTwoStep()) {
player.sendPacket(new PacketGadgetInteractRsp(getGadget(), InteractType.INTERACT_TYPE_OPEN_CHEST, InterOpType.INTER_OP_TYPE_START)); player.sendPacket(
new PacketGadgetInteractRsp(
getGadget(), InteractType.INTERACT_TYPE_OPEN_CHEST, InterOpType.INTER_OP_TYPE_START));
return false; return false;
} else { } else {
boolean success; boolean success;
if (handler instanceof BossChestInteractHandler bossChestInteractHandler) { if (handler instanceof BossChestInteractHandler bossChestInteractHandler) {
success = bossChestInteractHandler.onInteract(this, player, success =
req.getResinCostType() == ResinCostTypeOuterClass.ResinCostType.RESIN_COST_TYPE_CONDENSE); bossChestInteractHandler.onInteract(
this,
player,
req.getResinCostType()
== ResinCostTypeOuterClass.ResinCostType.RESIN_COST_TYPE_CONDENSE);
} else { } else {
success = handler.onInteract(this, player); success = handler.onInteract(this, player);
} }
@ -95,7 +135,11 @@ public class GadgetChest extends GadgetContent {
} }
getGadget().updateState(ScriptGadgetState.ChestOpened); getGadget().updateState(ScriptGadgetState.ChestOpened);
player.sendPacket(new PacketGadgetInteractRsp(this.getGadget(), InteractTypeOuterClass.InteractType.INTERACT_TYPE_OPEN_CHEST, InterOpType.INTER_OP_TYPE_FINISH)); player.sendPacket(
new PacketGadgetInteractRsp(
this.getGadget(),
InteractTypeOuterClass.InteractType.INTERACT_TYPE_OPEN_CHEST,
InterOpType.INTER_OP_TYPE_FINISH));
return true; return true;
} }
@ -110,13 +154,13 @@ public class GadgetChest extends GadgetContent {
if (bossChest != null) { if (bossChest != null) {
var players = getGadget().getScene().getPlayers().stream().map(Player::getUid).toList(); var players = getGadget().getScene().getPlayers().stream().map(Player::getUid).toList();
gadgetInfo.setBossChest(BossChestInfo.newBuilder() gadgetInfo.setBossChest(
.setMonsterConfigId(bossChest.monster_config_id) BossChestInfo.newBuilder()
.setResin(bossChest.resin) .setMonsterConfigId(bossChest.monster_config_id)
.addAllQualifyUidList(players) .setResin(bossChest.resin)
.addAllRemainUidList(players) .addAllQualifyUidList(players)
.build()); .addAllRemainUidList(players)
.build());
} }
} }
} }

View File

@ -1,5 +1,7 @@
package emu.grasscutter.game.managers; package emu.grasscutter.game.managers;
import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.BasePlayerManager; import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
@ -11,12 +13,10 @@ import emu.grasscutter.server.packet.send.PacketItemAddHintNotify;
import emu.grasscutter.server.packet.send.PacketResinChangeNotify; import emu.grasscutter.server.packet.send.PacketResinChangeNotify;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
public class ResinManager extends BasePlayerManager { public class ResinManager extends BasePlayerManager {
public static final int MAX_RESIN_BUYING_COUNT = 6; public static final int MAX_RESIN_BUYING_COUNT = 6;
public static final int AMOUNT_TO_ADD = 60; public static final int AMOUNT_TO_ADD = 60;
public static final int[] HCOIN_NUM_TO_BUY_RESIN = new int[]{50, 100, 100, 150, 200, 200}; public static final int[] HCOIN_NUM_TO_BUY_RESIN = new int[] {50, 100, 100, 150, 200, 200};
public ResinManager(Player player) { public ResinManager(Player player) {
super(player); super(player);
@ -53,7 +53,10 @@ public class ResinManager extends BasePlayerManager {
this.player.sendPacket(new PacketResinChangeNotify(this.player)); this.player.sendPacket(new PacketResinChangeNotify(this.player));
// Battle Pass trigger // Battle Pass trigger
this.player.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_COST_MATERIAL, 106, amount); // Resin item id = 106 this.player
.getBattlePassManager()
.triggerMission(
WatcherTriggerType.TRIGGER_COST_MATERIAL, 106, amount); // Resin item id = 106
return true; return true;
} }
@ -110,7 +113,11 @@ public class ResinManager extends BasePlayerManager {
// Calculate how much resin we need to refill and update player. // Calculate how much resin we need to refill and update player.
// Note that this can be more than one in case the player // Note that this can be more than one in case the player
// logged off with uncapped resin and is now logging in again. // logged off with uncapped resin and is now logging in again.
int recharge = 1 + (int)((currentTime - this.player.getNextResinRefresh()) / GAME_OPTIONS.resinOptions.rechargeTime); int recharge =
1
+ (int)
((currentTime - this.player.getNextResinRefresh())
/ GAME_OPTIONS.resinOptions.rechargeTime);
int newResin = Math.min(GAME_OPTIONS.resinOptions.cap, currentResin + recharge); int newResin = Math.min(GAME_OPTIONS.resinOptions.cap, currentResin + recharge);
int resinChange = newResin - currentResin; int resinChange = newResin - currentResin;
@ -120,9 +127,9 @@ public class ResinManager extends BasePlayerManager {
// Set to zero to disable recharge (because on/over cap.) // Set to zero to disable recharge (because on/over cap.)
if (newResin >= GAME_OPTIONS.resinOptions.cap) { if (newResin >= GAME_OPTIONS.resinOptions.cap) {
this.player.setNextResinRefresh(0); this.player.setNextResinRefresh(0);
} } else {
else { int nextRecharge =
int nextRecharge = this.player.getNextResinRefresh() + resinChange * GAME_OPTIONS.resinOptions.rechargeTime; this.player.getNextResinRefresh() + resinChange * GAME_OPTIONS.resinOptions.rechargeTime;
this.player.setNextResinRefresh(nextRecharge); this.player.setNextResinRefresh(nextRecharge);
} }
@ -158,7 +165,10 @@ public class ResinManager extends BasePlayerManager {
return RetcodeOuterClass.Retcode.RET_RESIN_BOUGHT_COUNT_EXCEEDED_VALUE; return RetcodeOuterClass.Retcode.RET_RESIN_BOUGHT_COUNT_EXCEEDED_VALUE;
} }
var res = this.player.getInventory().payItem(201, HCOIN_NUM_TO_BUY_RESIN[this.player.getResinBuyCount()]); var res =
this.player
.getInventory()
.payItem(201, HCOIN_NUM_TO_BUY_RESIN[this.player.getResinBuyCount()]);
if (!res) { if (!res) {
return RetcodeOuterClass.Retcode.RET_HCOIN_NOT_ENOUGH_VALUE; return RetcodeOuterClass.Retcode.RET_HCOIN_NOT_ENOUGH_VALUE;
} }
@ -166,7 +176,8 @@ public class ResinManager extends BasePlayerManager {
this.player.setResinBuyCount(this.player.getResinBuyCount() + 1); this.player.setResinBuyCount(this.player.getResinBuyCount() + 1);
this.player.setProperty(PlayerProperty.PROP_PLAYER_WAIT_SUB_HCOIN, 0); this.player.setProperty(PlayerProperty.PROP_PLAYER_WAIT_SUB_HCOIN, 0);
this.addResin(AMOUNT_TO_ADD); this.addResin(AMOUNT_TO_ADD);
this.player.sendPacket(new PacketItemAddHintNotify(new GameItem(106, AMOUNT_TO_ADD), ActionReason.BuyResin)); this.player.sendPacket(
new PacketItemAddHintNotify(new GameItem(106, AMOUNT_TO_ADD), ActionReason.BuyResin));
return 0; return 0;
} }

View File

@ -15,11 +15,15 @@ public class ItemUseAddItem extends ItemUseInt {
super(useParam); super(useParam);
try { try {
this.count = Integer.parseInt(useParam[1]); this.count = Integer.parseInt(useParam[1]);
} catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) {} } catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) {
}
} }
@Override @Override
public boolean useItem(UseItemParams params) { public boolean useItem(UseItemParams params) {
return params.player.getInventory().addItem(this.i, this.count * params.count, ActionReason.PlayerUseItem); return params
.player
.getInventory()
.addItem(this.i, this.count * params.count, ActionReason.PlayerUseItem);
} }
} }

View File

@ -303,7 +303,15 @@ public class GameMainQuest {
return true; return true;
} }
public boolean hasTeleportPostion(int subId, List<Position> posAndRot) { /**
* Checks if the quest has a teleport position. Returns true if it does & adds the target position
* & rotation to the list.
*
* @param subId The sub-quest ID.
* @param posAndRot A list which will contain the position & rotation if the quest has a teleport.
* @return True if the quest has a teleport position. False otherwise.
*/
public boolean hasTeleportPosition(int subId, List<Position> posAndRot) {
TeleportData questTransmit = GameData.getTeleportDataMap().get(subId); TeleportData questTransmit = GameData.getTeleportDataMap().get(subId);
if (questTransmit == null) return false; if (questTransmit == null) return false;
@ -338,7 +346,7 @@ public class GameMainQuest {
1, 1,
new Position( new Position(
transmitPosRot.get(0), transmitPosRot.get(1), transmitPosRot.get(2))); // rotation transmitPosRot.get(0), transmitPosRot.get(1), transmitPosRot.get(2))); // rotation
Grasscutter.getLogger().info("Succesfully loaded teleport data for subQuest {}", subId); Grasscutter.getLogger().debug("Successfully loaded teleport data for sub-quest {}.", subId);
return true; return true;
} }

View File

@ -20,12 +20,11 @@ import emu.grasscutter.server.packet.send.PacketChapterStateNotify;
import emu.grasscutter.server.packet.send.PacketDelQuestNotify; import emu.grasscutter.server.packet.send.PacketDelQuestNotify;
import emu.grasscutter.server.packet.send.PacketQuestListUpdateNotify; import emu.grasscutter.server.packet.send.PacketQuestListUpdateNotify;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.IntIntImmutablePair;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.script.Bindings; import javax.script.Bindings;
import it.unimi.dsi.fastutil.ints.IntIntImmutablePair;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.val; import lombok.val;
@ -227,8 +226,9 @@ public class GameQuest {
} }
// Give items for completing the quest. // Give items for completing the quest.
this.getQuestData().getGainItems().forEach(item -> this.getQuestData()
this.getOwner().getInventory().addItem(item, ActionReason.QuestItem)); .getGainItems()
.forEach(item -> this.getOwner().getInventory().addItem(item, ActionReason.QuestItem));
this.save(); this.save();
Grasscutter.getLogger().debug("Quest {} was completed.", subQuestId); Grasscutter.getLogger().debug("Quest {} was completed.", subQuestId);
@ -283,23 +283,26 @@ public class GameQuest {
} }
/** /**
* @return A list of dungeon IDs associated with the quest's 'QUEST_CONTENT_ENTER_DUNGEON' triggers. * @return A list of dungeon IDs associated with the quest's 'QUEST_CONTENT_ENTER_DUNGEON'
* The first element of the pair is the dungeon ID. * triggers. The first element of the pair is the dungeon ID. The second element of the pair
* The second element of the pair is the dungeon's scene point. * is the dungeon's scene point.
*/ */
public List<IntIntImmutablePair> getDungeonIds() { public List<IntIntImmutablePair> getDungeonIds() {
var conditions = this.getQuestData().getFinishCond().stream() var conditions =
.filter(cond -> cond.getType() == QuestContent.QUEST_CONTENT_ENTER_DUNGEON) this.getQuestData().getFinishCond().stream()
.toList(); .filter(cond -> cond.getType() == QuestContent.QUEST_CONTENT_ENTER_DUNGEON)
.toList();
return conditions.stream() return conditions.stream()
.map(condition -> { .map(
var params = condition.getParam(); condition -> {
// The first parameter is the ID of the dungeon. var params = condition.getParam();
// The second parameter is the dungeon entry's scene point. // The first parameter is the ID of the dungeon.
// ex. [1, 1] = dungeon ID 1, scene point 1 or 'KaeyaDungeon'. // The second parameter is the dungeon entry's scene point.
return new IntIntImmutablePair(params[0], params[1]); // ex. [1, 1] = dungeon ID 1, scene point 1 or 'KaeyaDungeon'.
}).toList(); return new IntIntImmutablePair(params[0], params[1]);
})
.toList();
} }
public void save() { public void save() {

View File

@ -52,7 +52,9 @@ public class ExecNotifyGroupLua extends QuestExecHandler {
quest.getState() == QuestState.QUEST_STATE_FINISHED quest.getState() == QuestState.QUEST_STATE_FINISHED
? EventType.EVENT_QUEST_FINISH ? EventType.EVENT_QUEST_FINISH
: EventType.EVENT_QUEST_START; : EventType.EVENT_QUEST_START;
scriptManager.callEvent(new ScriptArgs(groupId, eventType, quest.getSubQuestId()).setEventSource(String.valueOf(quest.getSubQuestId()))); scriptManager.callEvent(
new ScriptArgs(groupId, eventType, quest.getSubQuestId())
.setEventSource(String.valueOf(quest.getSubQuestId())));
}); });
return true; return true;

View File

@ -40,17 +40,11 @@ import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.KahnsSort; import emu.grasscutter.utils.KahnsSort;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import lombok.Getter;
import lombok.Setter;
import lombok.val;
import javax.annotation.Nullable;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.val; import lombok.val;
@ -311,8 +305,7 @@ public final class Scene {
// TODO:optimize EntityItem.java. Maybe we should make other players can't see // TODO:optimize EntityItem.java. Maybe we should make other players can't see
// the ItemEntity. // the ItemEntity.
ItemData itemData = GameData.getItemDataMap().get(item.getItemId()); ItemData itemData = GameData.getItemDataMap().get(item.getItemId());
if (itemData == null) if (itemData == null) return;
return;
if (itemData.isEquip()) { if (itemData.isEquip()) {
float range = (1.5f + (.05f * item.getCount())); float range = (1.5f + (.05f * item.getCount()));
for (int j = 0; j < item.getCount(); j++) { for (int j = 0; j < item.getCount(); j++) {
@ -321,11 +314,16 @@ public final class Scene {
addEntity(entity); addEntity(entity);
} }
} else { } else {
EntityItem entity = new EntityItem(this, player, itemData, bornForm.getPosition().clone().addY(0.5f), EntityItem entity =
item.getCount(), share); new EntityItem(
this,
player,
itemData,
bornForm.getPosition().clone().addY(0.5f),
item.getCount(),
share);
addEntity(entity); addEntity(entity);
} }
} }
public void addEntities(Collection<? extends GameEntity> entities) { public void addEntities(Collection<? extends GameEntity> entities) {
@ -333,7 +331,7 @@ public final class Scene {
} }
public synchronized void addEntities( public synchronized void addEntities(
Collection<? extends GameEntity> entities, VisionType visionType) { Collection<? extends GameEntity> entities, VisionType visionType) {
if (entities == null || entities.isEmpty()) { if (entities == null || entities.isEmpty()) {
return; return;
} }
@ -448,8 +446,13 @@ public final class Scene {
// Reward drop // Reward drop
var world = this.getWorld(); var world = this.getWorld();
if (target instanceof EntityMonster monster && this.getSceneType() != SceneType.SCENE_DUNGEON) { if (target instanceof EntityMonster monster && this.getSceneType() != SceneType.SCENE_DUNGEON) {
if (monster.getMetaMonster() != null && !world.getServer().getDropSystem().handleMonsterDrop(monster)) { if (monster.getMetaMonster() != null
Grasscutter.getLogger().debug("Can not solve monster drop: drop_id = {}, drop_tag = {}. Falling back to legacy drop system.", monster.getMetaMonster().drop_id, monster.getMetaMonster().drop_tag); && !world.getServer().getDropSystem().handleMonsterDrop(monster)) {
Grasscutter.getLogger()
.debug(
"Can not solve monster drop: drop_id = {}, drop_tag = {}. Falling back to legacy drop system.",
monster.getMetaMonster().drop_id,
monster.getMetaMonster().drop_tag);
getWorld().getServer().getDropSystemLegacy().callDrop(monster); getWorld().getServer().getDropSystemLegacy().callDrop(monster);
} }
} }

View File

@ -279,10 +279,10 @@ public class World implements Iterable<Player> {
val sceneData = GameData.getSceneDataMap().get(sceneId); val sceneData = GameData.getSceneDataMap().get(sceneId);
if (dungeonData != null) { if (dungeonData != null) {
teleportProps.teleportTo(dungeonData.getStartPosition()) teleportProps
.teleportRot(dungeonData.getStartRotation()); .teleportTo(dungeonData.getStartPosition())
teleportProps.enterType(EnterType.ENTER_TYPE_DUNGEON) .teleportRot(dungeonData.getStartRotation());
.enterReason(EnterReason.DungeonEnter); teleportProps.enterType(EnterType.ENTER_TYPE_DUNGEON).enterReason(EnterReason.DungeonEnter);
} else if (player.getSceneId() == sceneId) { } else if (player.getSceneId() == sceneId) {
teleportProps.enterType(EnterType.ENTER_TYPE_GOTO); teleportProps.enterType(EnterType.ENTER_TYPE_GOTO);
} else if (sceneData != null && sceneData.getSceneType() == SceneType.SCENE_HOME_WORLD) { } else if (sceneData != null && sceneData.getSceneType() == SceneType.SCENE_HOME_WORLD) {

View File

@ -8,8 +8,6 @@ import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import javax.script.Bindings;
import javax.script.CompiledScript;
import lombok.val; import lombok.val;
public class EntityControllerScriptManager { public class EntityControllerScriptManager {
@ -21,23 +19,23 @@ public class EntityControllerScriptManager {
private static void cacheGadgetControllers() { private static void cacheGadgetControllers() {
try (var stream = Files.newDirectoryStream(getScriptPath("Gadget/"), "*.lua")) { try (var stream = Files.newDirectoryStream(getScriptPath("Gadget/"), "*.lua")) {
stream.forEach(path -> { stream.forEach(
val fileName = path.getFileName().toString(); path -> {
if (!fileName.endsWith(".lua")) return; val fileName = path.getFileName().toString();
if (!fileName.endsWith(".lua")) return;
val controllerName = fileName.substring(0, fileName.length() - 4); val controllerName = fileName.substring(0, fileName.length() - 4);
var cs = ScriptLoader.getScript("Gadget/" + fileName); var cs = ScriptLoader.getScript("Gadget/" + fileName);
var bindings = ScriptLoader.getEngine().createBindings(); var bindings = ScriptLoader.getEngine().createBindings();
if (cs == null) return; if (cs == null) return;
try { try {
cs.eval(bindings); cs.eval(bindings);
gadgetController.put(controllerName, new EntityController(cs, bindings)); gadgetController.put(controllerName, new EntityController(cs, bindings));
} catch (Throwable e) { } catch (Throwable e) {
Grasscutter.getLogger() Grasscutter.getLogger().error("Error while loading gadget controller: {}.", fileName);
.error("Error while loading gadget controller: {}.", fileName); }
} });
});
Grasscutter.getLogger().debug("Loaded {} gadget controllers", gadgetController.size()); Grasscutter.getLogger().debug("Loaded {} gadget controllers", gadgetController.size());
} catch (IOException e) { } catch (IOException e) {
Grasscutter.getLogger().error("Error loading gadget controller Lua scripts."); Grasscutter.getLogger().error("Error loading gadget controller Lua scripts.");

View File

@ -21,11 +21,13 @@ public class SceneGadget extends SceneObject {
public SceneBossChest boss_chest; public SceneBossChest boss_chest;
public int interact_id; public int interact_id;
/** /**
* Note: this field indicates whether the gadget should disappear permanently. * Note: this field indicates whether the gadget should disappear permanently. For example, if
* For example, if isOneOff=true, like most chests, it will disappear permanently after interacted. * isOneOff=true, like most chests, it will disappear permanently after interacted. If
* If isOneOff=false, like investigation points, it will disappear temporarily, and appear again in next big world resource refresh routine. * isOneOff=false, like investigation points, it will disappear temporarily, and appear again in
* next big world resource refresh routine.
*/ */
public boolean isOneoff; public boolean isOneoff;
public int area_id; public int area_id;
public int draft_id; public int draft_id;
public int route_id; public int route_id;

View File

@ -1,5 +1,8 @@
package emu.grasscutter.server.game; package emu.grasscutter.server.game;
import static emu.grasscutter.config.Configuration.GAME_INFO;
import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.GameConstants; import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.database.DatabaseHelper;
@ -34,18 +37,14 @@ import emu.grasscutter.server.event.internal.ServerStopEvent;
import emu.grasscutter.server.event.types.ServerEvent; import emu.grasscutter.server.event.types.ServerEvent;
import emu.grasscutter.server.scheduler.ServerTaskScheduler; import emu.grasscutter.server.scheduler.ServerTaskScheduler;
import emu.grasscutter.task.TaskMap; import emu.grasscutter.task.TaskMap;
import kcp.highway.ChannelConfig;
import kcp.highway.KcpServer;
import lombok.Getter;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.time.Instant; import java.time.Instant;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import kcp.highway.ChannelConfig;
import static emu.grasscutter.config.Configuration.GAME_INFO; import kcp.highway.KcpServer;
import static emu.grasscutter.utils.Language.translate; import lombok.Getter;
@Getter @Getter
public final class GameServer extends KcpServer { public final class GameServer extends KcpServer {

View File

@ -1,13 +1,15 @@
package emu.grasscutter.server.http.dispatch; package emu.grasscutter.server.http.dispatch;
import static emu.grasscutter.config.Configuration.*;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import emu.grasscutter.GameConstants; import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.Grasscutter.ServerRunMode; import emu.grasscutter.Grasscutter.ServerRunMode;
import emu.grasscutter.net.proto.QueryRegionListHttpRspOuterClass.QueryRegionListHttpRsp;
import emu.grasscutter.net.proto.QueryCurrRegionHttpRspOuterClass.QueryCurrRegionHttpRsp; import emu.grasscutter.net.proto.QueryCurrRegionHttpRspOuterClass.QueryCurrRegionHttpRsp;
import emu.grasscutter.net.proto.RegionSimpleInfoOuterClass.RegionSimpleInfo; import emu.grasscutter.net.proto.QueryRegionListHttpRspOuterClass.QueryRegionListHttpRsp;
import emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo; import emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo;
import emu.grasscutter.net.proto.RegionSimpleInfoOuterClass.RegionSimpleInfo;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode; import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.net.proto.StopServerInfoOuterClass.StopServerInfo; import emu.grasscutter.net.proto.StopServerInfoOuterClass.StopServerInfo;
import emu.grasscutter.server.event.dispatch.QueryAllRegionsEvent; import emu.grasscutter.server.event.dispatch.QueryAllRegionsEvent;
@ -18,23 +20,13 @@ import emu.grasscutter.utils.Crypto;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import io.javalin.Javalin; import io.javalin.Javalin;
import io.javalin.http.Context; import io.javalin.http.Context;
import java.time.Instant; import java.time.Instant;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.security.Signature;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.slf4j.Logger; import org.slf4j.Logger;
import static emu.grasscutter.config.Configuration.*; /** Handles requests related to region queries. */
import static emu.grasscutter.utils.Language.translate;
/**
* Handles requests related to region queries.
*/
public final class RegionHandler implements Router { public final class RegionHandler implements Router {
private static final Map<String, RegionData> regions = new ConcurrentHashMap<>(); private static final Map<String, RegionData> regions = new ConcurrentHashMap<>();
private static String regionListResponse; private static String regionListResponse;
@ -48,13 +40,15 @@ public final class RegionHandler implements Router {
} }
} }
/** /** Configures region data according to configuration. */
* Configures region data according to configuration.
*/
private void initialize() { private void initialize() {
String dispatchDomain = "http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://" String dispatchDomain =
+ lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress) + ":" "http"
+ lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort); + (HTTP_ENCRYPTION.useInRouting ? "s" : "")
+ "://"
+ lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress)
+ ":"
+ lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort);
// Create regions. // Create regions.
List<RegionSimpleInfo> servers = new ArrayList<>(); List<RegionSimpleInfo> servers = new ArrayList<>();
@ -62,61 +56,84 @@ public final class RegionHandler implements Router {
var configuredRegions = new ArrayList<>(List.of(DISPATCH_INFO.regions)); var configuredRegions = new ArrayList<>(List.of(DISPATCH_INFO.regions));
if (SERVER.runMode != ServerRunMode.HYBRID && configuredRegions.size() == 0) { if (SERVER.runMode != ServerRunMode.HYBRID && configuredRegions.size() == 0) {
Grasscutter.getLogger().error("[Dispatch] There are no game servers available. Exiting due to unplayable state."); Grasscutter.getLogger()
.error(
"[Dispatch] There are no game servers available. Exiting due to unplayable state.");
System.exit(1); System.exit(1);
} else if (configuredRegions.size() == 0) } else if (configuredRegions.size() == 0)
configuredRegions.add(new Region("os_usa", DISPATCH_INFO.defaultName, configuredRegions.add(
lr(GAME_INFO.accessAddress, GAME_INFO.bindAddress), new Region(
lr(GAME_INFO.accessPort, GAME_INFO.bindPort))); "os_usa",
DISPATCH_INFO.defaultName,
lr(GAME_INFO.accessAddress, GAME_INFO.bindAddress),
lr(GAME_INFO.accessPort, GAME_INFO.bindPort)));
configuredRegions.forEach(region -> { configuredRegions.forEach(
if (usedNames.contains(region.Name)) { region -> {
Grasscutter.getLogger().error("Region name already in use."); if (usedNames.contains(region.Name)) {
return; Grasscutter.getLogger().error("Region name already in use.");
} return;
}
// Create a region identifier. // Create a region identifier.
var identifier = RegionSimpleInfo.newBuilder() var identifier =
.setName(region.Name).setTitle(region.Title).setType("DEV_PUBLIC") RegionSimpleInfo.newBuilder()
.setDispatchUrl(dispatchDomain + "/query_cur_region/" + region.Name) .setName(region.Name)
.build(); .setTitle(region.Title)
usedNames.add(region.Name); servers.add(identifier); .setType("DEV_PUBLIC")
.setDispatchUrl(dispatchDomain + "/query_cur_region/" + region.Name)
.build();
usedNames.add(region.Name);
servers.add(identifier);
// Create a region info object. // Create a region info object.
var regionInfo = RegionInfo.newBuilder() var regionInfo =
.setGateserverIp(region.Ip).setGateserverPort(region.Port) RegionInfo.newBuilder()
.setSecretKey(ByteString.copyFrom(Crypto.DISPATCH_SEED)) .setGateserverIp(region.Ip)
.build(); .setGateserverPort(region.Port)
// Create an updated region query. .setSecretKey(ByteString.copyFrom(Crypto.DISPATCH_SEED))
var updatedQuery = QueryCurrRegionHttpRsp.newBuilder().setRegionInfo(regionInfo).build(); .build();
regions.put(region.Name, new RegionData(updatedQuery, Utils.base64Encode(updatedQuery.toByteString().toByteArray()))); // Create an updated region query.
}); var updatedQuery = QueryCurrRegionHttpRsp.newBuilder().setRegionInfo(regionInfo).build();
regions.put(
region.Name,
new RegionData(
updatedQuery, Utils.base64Encode(updatedQuery.toByteString().toByteArray())));
});
// Create a config object. // Create a config object.
byte[] customConfig = "{\"sdkenv\":\"2\",\"checkdevice\":\"false\",\"loadPatch\":\"false\",\"showexception\":\"false\",\"regionConfig\":\"pm|fk|add\",\"downloadMode\":\"0\"}".getBytes(); byte[] customConfig =
"{\"sdkenv\":\"2\",\"checkdevice\":\"false\",\"loadPatch\":\"false\",\"showexception\":\"false\",\"regionConfig\":\"pm|fk|add\",\"downloadMode\":\"0\"}"
.getBytes();
Crypto.xor(customConfig, Crypto.DISPATCH_KEY); // XOR the config with the key. Crypto.xor(customConfig, Crypto.DISPATCH_KEY); // XOR the config with the key.
// Create an updated region list. // Create an updated region list.
QueryRegionListHttpRsp updatedRegionList = QueryRegionListHttpRsp.newBuilder() QueryRegionListHttpRsp updatedRegionList =
.addAllRegionList(servers) QueryRegionListHttpRsp.newBuilder()
.setClientSecretKey(ByteString.copyFrom(Crypto.DISPATCH_SEED)) .addAllRegionList(servers)
.setClientCustomConfigEncrypted(ByteString.copyFrom(customConfig)) .setClientSecretKey(ByteString.copyFrom(Crypto.DISPATCH_SEED))
.setEnableLoginPc(true).build(); .setClientCustomConfigEncrypted(ByteString.copyFrom(customConfig))
.setEnableLoginPc(true)
.build();
// Set the region list response. // Set the region list response.
regionListResponse = Utils.base64Encode(updatedRegionList.toByteString().toByteArray()); regionListResponse = Utils.base64Encode(updatedRegionList.toByteString().toByteArray());
// CN // CN
// Create a config object. // Create a config object.
byte[] customConfigcn = "{\"sdkenv\":\"0\",\"checkdevice\":\"true\",\"loadPatch\":\"false\",\"showexception\":\"false\",\"regionConfig\":\"pm|fk|add\",\"downloadMode\":\"0\"}".getBytes(); byte[] customConfigcn =
"{\"sdkenv\":\"0\",\"checkdevice\":\"true\",\"loadPatch\":\"false\",\"showexception\":\"false\",\"regionConfig\":\"pm|fk|add\",\"downloadMode\":\"0\"}"
.getBytes();
Crypto.xor(customConfigcn, Crypto.DISPATCH_KEY); // XOR the config with the key. Crypto.xor(customConfigcn, Crypto.DISPATCH_KEY); // XOR the config with the key.
// Create an updated region list. // Create an updated region list.
QueryRegionListHttpRsp updatedRegionListcn = QueryRegionListHttpRsp.newBuilder() QueryRegionListHttpRsp updatedRegionListcn =
.addAllRegionList(servers) QueryRegionListHttpRsp.newBuilder()
.setClientSecretKey(ByteString.copyFrom(Crypto.DISPATCH_SEED)) .addAllRegionList(servers)
.setClientCustomConfigEncrypted(ByteString.copyFrom(customConfigcn)) .setClientSecretKey(ByteString.copyFrom(Crypto.DISPATCH_SEED))
.setEnableLoginPc(true).build(); .setClientCustomConfigEncrypted(ByteString.copyFrom(customConfigcn))
.setEnableLoginPc(true)
.build();
// Set the region list response. // Set the region list response.
regionListResponsecn = Utils.base64Encode(updatedRegionListcn.toByteString().toByteArray()); regionListResponsecn = Utils.base64Encode(updatedRegionListcn.toByteString().toByteArray());
@ -143,7 +160,8 @@ public final class RegionHandler implements Router {
String platformName = ctx.queryParam("platform"); String platformName = ctx.queryParam("platform");
// Determine the region list to use based on the version and platform. // Determine the region list to use based on the version and platform.
if ("CNRELiOS".equals(versionCode) || "CNRELWin".equals(versionCode) if ("CNRELiOS".equals(versionCode)
|| "CNRELWin".equals(versionCode)
|| "CNRELAndroid".equals(versionCode)) { || "CNRELAndroid".equals(versionCode)) {
// Use the CN region list. // Use the CN region list.
QueryAllRegionsEvent event = new QueryAllRegionsEvent(regionListResponsecn); QueryAllRegionsEvent event = new QueryAllRegionsEvent(regionListResponsecn);
@ -152,7 +170,8 @@ public final class RegionHandler implements Router {
// Respond with the event result. // Respond with the event result.
ctx.result(event.getRegionList()); ctx.result(event.getRegionList());
} else if ("OSRELiOS".equals(versionCode) || "OSRELWin".equals(versionCode) } else if ("OSRELiOS".equals(versionCode)
|| "OSRELWin".equals(versionCode)
|| "OSRELAndroid".equals(versionCode)) { || "OSRELAndroid".equals(versionCode)) {
// Use the OS region list. // Use the OS region list.
QueryAllRegionsEvent event = new QueryAllRegionsEvent(regionListResponse); QueryAllRegionsEvent event = new QueryAllRegionsEvent(regionListResponse);
@ -187,7 +206,8 @@ public final class RegionHandler implements Router {
ctx.result(event.getRegionList()); ctx.result(event.getRegionList());
} }
// Log the request to the console. // Log the request to the console.
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s request: query_region_list", ctx.ip())); Grasscutter.getLogger()
.info(String.format("[Dispatch] Client %s request: query_region_list", ctx.ip()));
} }
/** /**
@ -202,39 +222,53 @@ public final class RegionHandler implements Router {
// Get region data. // Get region data.
String regionData = "CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw=="; String regionData = "CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw==";
if (ctx.queryParamMap().values().size() > 0) { if (ctx.queryParamMap().values().size() > 0) {
if (region != null) if (region != null) regionData = region.getBase64();
regionData = region.getBase64();
} }
String clientVersion = versionName.replaceAll(Pattern.compile("[a-zA-Z]").pattern(), ""); String clientVersion = versionName.replaceAll(Pattern.compile("[a-zA-Z]").pattern(), "");
String[] versionCode = clientVersion.split("\\."); String[] versionCode = clientVersion.split("\\.");
int versionMajor = Integer.parseInt(versionCode[0]); int versionMajor = Integer.parseInt(versionCode[0]);
int versionMinor = Integer.parseInt(versionCode[1]); int versionMinor = Integer.parseInt(versionCode[1]);
int versionFix = Integer.parseInt(versionCode[2]); int versionFix = Integer.parseInt(versionCode[2]);
if (versionMajor >= 3 || (versionMajor == 2 && versionMinor == 7 && versionFix >= 50) || (versionMajor == 2 && versionMinor == 8)) { if (versionMajor >= 3
|| (versionMajor == 2 && versionMinor == 7 && versionFix >= 50)
|| (versionMajor == 2 && versionMinor == 8)) {
try { try {
QueryCurrentRegionEvent event = new QueryCurrentRegionEvent(regionData); event.call(); QueryCurrentRegionEvent event = new QueryCurrentRegionEvent(regionData);
event.call();
String key_id = ctx.queryParam("key_id"); String key_id = ctx.queryParam("key_id");
if (!clientVersion.equals(GameConstants.VERSION)) { // Reject clients when there is a version mismatch if (!clientVersion.equals(
GameConstants.VERSION)) { // Reject clients when there is a version mismatch
boolean updateClient = GameConstants.VERSION.compareTo(clientVersion) > 0; boolean updateClient = GameConstants.VERSION.compareTo(clientVersion) > 0;
QueryCurrRegionHttpRsp rsp = QueryCurrRegionHttpRsp.newBuilder() QueryCurrRegionHttpRsp rsp =
.setRetcode(Retcode.RET_STOP_SERVER_VALUE) QueryCurrRegionHttpRsp.newBuilder()
.setMsg("Connection Failed!") .setRetcode(Retcode.RET_STOP_SERVER_VALUE)
.setRegionInfo(RegionInfo.newBuilder()) .setMsg("Connection Failed!")
.setStopServer(StopServerInfo.newBuilder() .setRegionInfo(RegionInfo.newBuilder())
.setUrl("https://discord.gg/grasscutters") .setStopServer(
.setStopBeginTime((int) Instant.now().getEpochSecond()) StopServerInfo.newBuilder()
.setStopEndTime((int) Instant.now().getEpochSecond()*2) .setUrl("https://discord.gg/grasscutters")
.setContentMsg(updateClient ? "\nVersion mismatch outdated client! \n\nServer version: %s\nClient version: %s".formatted(GameConstants.VERSION, clientVersion) : "\nVersion mismatch outdated server! \n\nServer version: %s\nClient version: %s".formatted(GameConstants.VERSION, clientVersion)) .setStopBeginTime((int) Instant.now().getEpochSecond())
.build()) .setStopEndTime((int) Instant.now().getEpochSecond() * 2)
.buildPartial(); .setContentMsg(
updateClient
? "\nVersion mismatch outdated client! \n\nServer version: %s\nClient version: %s"
.formatted(GameConstants.VERSION, clientVersion)
: "\nVersion mismatch outdated server! \n\nServer version: %s\nClient version: %s"
.formatted(GameConstants.VERSION, clientVersion))
.build())
.buildPartial();
Grasscutter.getLogger().info(String.format("Connection denied for %s due to %s", ctx.ip(), updateClient ? "outdated client!" : "outdated server!")); Grasscutter.getLogger()
.info(
String.format(
"Connection denied for %s due to %s",
ctx.ip(), updateClient ? "outdated client!" : "outdated server!"));
ctx.json(Crypto.encryptAndSignRegionData(rsp.toByteArray(), key_id)); ctx.json(Crypto.encryptAndSignRegionData(rsp.toByteArray(), key_id));
return; return;
@ -251,28 +285,25 @@ public final class RegionHandler implements Router {
return; return;
} }
var regionInfo = Utils.base64Decode(event.getRegionInfo()); var regionInfo = Utils.base64Decode(event.getRegionInfo());
ctx.json(Crypto.encryptAndSignRegionData(regionInfo, key_id)); ctx.json(Crypto.encryptAndSignRegionData(regionInfo, key_id));
} } catch (Exception e) {
catch (Exception e) {
Grasscutter.getLogger().error("An error occurred while handling query_cur_region.", e); Grasscutter.getLogger().error("An error occurred while handling query_cur_region.", e);
} }
} } else {
else {
// Invoke event. // Invoke event.
QueryCurrentRegionEvent event = new QueryCurrentRegionEvent(regionData); event.call(); QueryCurrentRegionEvent event = new QueryCurrentRegionEvent(regionData);
event.call();
// Respond with event result. // Respond with event result.
ctx.result(event.getRegionInfo()); ctx.result(event.getRegionInfo());
} }
// Log to console. // Log to console.
Grasscutter.getLogger().info(String.format("Client %s request: query_cur_region/%s", ctx.ip(), regionName)); Grasscutter.getLogger()
.info(String.format("Client %s request: query_cur_region/%s", ctx.ip(), regionName));
} }
/** /** Region data container. */
* Region data container.
*/
public static class RegionData { public static class RegionData {
private final QueryCurrRegionHttpRsp regionQuery; private final QueryCurrRegionHttpRsp regionQuery;
private final String base64; private final String base64;
@ -293,6 +324,7 @@ public final class RegionHandler implements Router {
/** /**
* Gets the current region query. * Gets the current region query.
*
* @return A {@link QueryCurrRegionHttpRsp} object. * @return A {@link QueryCurrRegionHttpRsp} object.
*/ */
public static QueryCurrRegionHttpRsp getCurrentRegion() { public static QueryCurrRegionHttpRsp getCurrentRegion() {

View File

@ -15,6 +15,8 @@ public class HandlerChangeGameTimeReq extends PacketHandler {
var req = ChangeGameTimeReq.parseFrom(payload); var req = ChangeGameTimeReq.parseFrom(payload);
session.getPlayer().getWorld().changeTime(req.getGameTime(), req.getExtraDays()); session.getPlayer().getWorld().changeTime(req.getGameTime(), req.getExtraDays());
session.getPlayer().sendPacket(new PacketChangeGameTimeRsp(session.getPlayer(), req.getExtraDays())); session
.getPlayer()
.sendPacket(new PacketChangeGameTimeRsp(session.getPlayer(), req.getExtraDays()));
} }
} }

View File

@ -94,8 +94,10 @@ public class HandlerCombatInvocationsNotify extends PacketHandler {
} }
} }
// as long as one of these two packets be forwarded to client, the animation of avatar will be interrupted // as long as one of these two packets be forwarded to client, the animation of avatar
if (motionState == MotionState.MOTION_STATE_NOTIFY || motionState == MotionState.MOTION_STATE_FIGHT) { // will be interrupted
if (motionState == MotionState.MOTION_STATE_NOTIFY
|| motionState == MotionState.MOTION_STATE_FIGHT) {
continue; continue;
} }
} }

View File

@ -13,6 +13,9 @@ public class HandlerDungeonEntryInfoReq extends PacketHandler {
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
DungeonEntryInfoReq req = DungeonEntryInfoReq.parseFrom(payload); DungeonEntryInfoReq req = DungeonEntryInfoReq.parseFrom(payload);
session.getServer().getDungeonSystem().sendEntryInfoFor(session.getPlayer(), req.getPointId(), req.getSceneId()); session
.getServer()
.getDungeonSystem()
.sendEntryInfoFor(session.getPlayer(), req.getPointId(), req.getSceneId());
} }
} }

View File

@ -15,12 +15,13 @@ public class HandlerPlayerEnterDungeonReq extends PacketHandler {
// Auto template // Auto template
PlayerEnterDungeonReq req = PlayerEnterDungeonReq.parseFrom(payload); PlayerEnterDungeonReq req = PlayerEnterDungeonReq.parseFrom(payload);
var success = session var success =
.getServer() session
.getDungeonSystem() .getServer()
.enterDungeon(session.getPlayer(), req.getPointId(), req.getDungeonId()); .getDungeonSystem()
.enterDungeon(session.getPlayer(), req.getPointId(), req.getDungeonId());
session session
.getPlayer() .getPlayer()
.sendPacket(new PacketPlayerEnterDungeonRsp(req.getPointId(), req.getDungeonId(), success)); .sendPacket(new PacketPlayerEnterDungeonRsp(req.getPointId(), req.getDungeonId(), success));
} }
} }

View File

@ -1,6 +1,5 @@
package emu.grasscutter.server.packet.recv; package emu.grasscutter.server.packet.recv;
import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.game.quest.enums.QuestContent; import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.PacketHandler;
@ -17,8 +16,9 @@ public class HandlerPostEnterSceneReq extends PacketHandler {
var scene = player.getScene(); var scene = player.getScene();
var questManager = player.getQuestManager(); var questManager = player.getQuestManager();
switch (session.getPlayer().getScene().getSceneType()){ switch (session.getPlayer().getScene().getSceneType()) {
case SCENE_ROOM -> questManager.queueEvent(QuestContent.QUEST_CONTENT_ENTER_ROOM, scene.getId(), 0); case SCENE_ROOM -> questManager.queueEvent(
QuestContent.QUEST_CONTENT_ENTER_ROOM, scene.getId(), 0);
case SCENE_WORLD -> { case SCENE_WORLD -> {
questManager.queueEvent(QuestContent.QUEST_CONTENT_ENTER_MY_WORLD, scene.getId()); questManager.queueEvent(QuestContent.QUEST_CONTENT_ENTER_MY_WORLD, scene.getId());
questManager.queueEvent(QuestContent.QUEST_CONTENT_ENTER_MY_WORLD_SCENE, scene.getId()); questManager.queueEvent(QuestContent.QUEST_CONTENT_ENTER_MY_WORLD_SCENE, scene.getId());

View File

@ -1,7 +1,6 @@
package emu.grasscutter.server.packet.recv; package emu.grasscutter.server.packet.recv;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.game.quest.GameMainQuest;
import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
@ -10,21 +9,18 @@ import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketQuestTransmitRsp; import emu.grasscutter.server.packet.send.PacketQuestTransmitRsp;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import lombok.val;
@Opcodes(PacketOpcodes.QuestTransmitReq) @Opcodes(PacketOpcodes.QuestTransmitReq)
public class HandlerQuestTransmitReq extends PacketHandler { public class HandlerQuestTransmitReq extends PacketHandler {
@Override @Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
val req = QuestTransmitReq.parseFrom(payload); var req = QuestTransmitReq.parseFrom(payload);
GameMainQuest mainQuest = var mainQuest = session.getPlayer().getQuestManager().getMainQuestById(req.getQuestId() / 100);
session.getPlayer().getQuestManager().getMainQuestById(req.getQuestId() / 100);
List<Position> posAndRot = new ArrayList<>(); var posAndRot = new ArrayList<Position>();
boolean result = false; boolean result = false;
if (mainQuest.hasTeleportPostion(req.getQuestId(), posAndRot)) { if (mainQuest.hasTeleportPosition(req.getQuestId(), posAndRot)) {
int sceneId = var sceneId =
GameData.getTeleportDataMap() GameData.getTeleportDataMap()
.get(req.getQuestId()) .get(req.getQuestId())
.getTransmit_points() .getTransmit_points()
@ -36,6 +32,7 @@ public class HandlerQuestTransmitReq extends PacketHandler {
.getWorld() .getWorld()
.transferPlayerToScene(session.getPlayer(), sceneId, posAndRot.get(0)); .transferPlayerToScene(session.getPlayer(), sceneId, posAndRot.get(0));
} }
session.send(new PacketQuestTransmitRsp(result, req)); session.send(new PacketQuestTransmitRsp(result, req));
} }
} }

View File

@ -1,8 +1,5 @@
package emu.grasscutter.server.packet.recv; package emu.grasscutter.server.packet.recv;
import javax.lang.model.type.TypeMirror;
import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
@ -24,8 +21,9 @@ public class HandlerSceneAudioNotify extends PacketHandler {
int type = notify.getType(); int type = notify.getType();
List<Integer> param1 = notify.getParam1List(); List<Integer> param1 = notify.getParam1List();
session.getPlayer().getScene().broadcastPacket(new PacketSceneAudioNotify(sourceUid, param2, param3, type, param1)); session
.getPlayer()
.getScene()
.broadcastPacket(new PacketSceneAudioNotify(sourceUid, param2, param3, type, param1));
} }
} }

View File

@ -55,7 +55,7 @@ public class HandlerSetPlayerBornDataReq extends PacketHandler {
// Check if the default Anemo skill should be given. // Check if the default Anemo skill should be given.
if (!GAME_OPTIONS.questing) { if (!GAME_OPTIONS.questing) {
mainCharacter.setSkillDepotData( mainCharacter.setSkillDepotData(
GameData.getAvatarSkillDepotDataMap().get(startingSkillDepot)); GameData.getAvatarSkillDepotDataMap().get(startingSkillDepot));
} }
// Manually handle adding to team // Manually handle adding to team

View File

@ -17,9 +17,7 @@ public class HandlerUnlockPersonalLineReq extends PacketHandler {
var req = UnlockPersonalLineReq.parseFrom(payload); var req = UnlockPersonalLineReq.parseFrom(payload);
var data = GameData.getPersonalLineDataMap().get(req.getPersonalLineId()); var data = GameData.getPersonalLineDataMap().get(req.getPersonalLineId());
if (data == null) { if (data == null) {
session.send( session.send(new PacketUnlockPersonalLineRsp(req.getPersonalLineId(), Retcode.RET_FAIL));
new PacketUnlockPersonalLineRsp(
req.getPersonalLineId(), Retcode.RET_FAIL));
return; return;
} }

View File

@ -5,7 +5,6 @@ import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.AvatarFightPropUpdateNotifyOuterClass.AvatarFightPropUpdateNotify; import emu.grasscutter.net.proto.AvatarFightPropUpdateNotifyOuterClass.AvatarFightPropUpdateNotify;
import java.util.Map; import java.util.Map;
public class PacketAvatarFightPropUpdateNotify extends BasePacket { public class PacketAvatarFightPropUpdateNotify extends BasePacket {

View File

@ -14,11 +14,14 @@ public class PacketAvatarTeamUpdateNotify extends BasePacket {
var teamManager = player.getTeamManager(); var teamManager = player.getTeamManager();
if (teamManager.isUsingTrialTeam()) { if (teamManager.isUsingTrialTeam()) {
proto.addAllTempAvatarGuidList(teamManager.getActiveTeam().stream() proto.addAllTempAvatarGuidList(
.map(entity -> entity.getAvatar().getGuid()).toList()); teamManager.getActiveTeam().stream()
.map(entity -> entity.getAvatar().getGuid())
.toList());
} else { } else {
teamManager.getTeams().forEach((key, value) -> teamManager
proto.putAvatarTeamMap(key, value.toProto(player))); .getTeams()
.forEach((key, value) -> proto.putAvatarTeamMap(key, value.toProto(player)));
} }
this.setData(proto); this.setData(proto);

View File

@ -10,9 +10,10 @@ public class PacketBuyResinRsp extends BasePacket {
public PacketBuyResinRsp(Player player, int ret) { public PacketBuyResinRsp(Player player, int ret) {
super(PacketOpcodes.BuyResinRsp); super(PacketOpcodes.BuyResinRsp);
this.setData(BuyResinRspOuterClass.BuyResinRsp.newBuilder() this.setData(
.setCurValue(player.getProperty(PlayerProperty.PROP_PLAYER_RESIN)) BuyResinRspOuterClass.BuyResinRsp.newBuilder()
.setRetcode(ret) .setCurValue(player.getProperty(PlayerProperty.PROP_PLAYER_RESIN))
.build()); .setRetcode(ret)
.build());
} }
} }

View File

@ -12,9 +12,9 @@ public class PacketChangeGameTimeRsp extends BasePacket {
ChangeGameTimeRsp proto = ChangeGameTimeRsp proto =
ChangeGameTimeRsp.newBuilder() ChangeGameTimeRsp.newBuilder()
.setCurGameTime(player.getWorld().getGameTime()) .setCurGameTime(player.getWorld().getGameTime())
.setExtraDays(extraDays) .setExtraDays(extraDays)
.build(); .build();
this.setData(proto); this.setData(proto);
} }

View File

@ -10,9 +10,7 @@ public class PacketDropHintNotify extends BasePacket {
public PacketDropHintNotify(int itemId, Vector position) { public PacketDropHintNotify(int itemId, Vector position) {
super(PacketOpcodes.DropHintNotify); super(PacketOpcodes.DropHintNotify);
var proto = DropHintNotify.newBuilder() var proto = DropHintNotify.newBuilder().addItemIdList(itemId).setPosition(position);
.addItemIdList(itemId)
.setPosition(position);
this.setData(proto.build()); this.setData(proto.build());
} }

View File

@ -5,7 +5,6 @@ import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.DungeonEntryInfoOuterClass.DungeonEntryInfo; import emu.grasscutter.net.proto.DungeonEntryInfoOuterClass.DungeonEntryInfo;
import emu.grasscutter.net.proto.DungeonEntryInfoRspOuterClass.DungeonEntryInfoRsp; import emu.grasscutter.net.proto.DungeonEntryInfoRspOuterClass.DungeonEntryInfoRsp;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -36,19 +35,18 @@ public class PacketDungeonEntryInfoRsp extends BasePacket {
public PacketDungeonEntryInfoRsp(PointData pointData, List<Integer> additional) { public PacketDungeonEntryInfoRsp(PointData pointData, List<Integer> additional) {
super(PacketOpcodes.DungeonEntryInfoRsp); super(PacketOpcodes.DungeonEntryInfoRsp);
var packet = DungeonEntryInfoRsp.newBuilder() var packet = DungeonEntryInfoRsp.newBuilder().setPointId(pointData.getId());
.setPointId(pointData.getId());
// Add dungeon IDs from the point data. // Add dungeon IDs from the point data.
if (pointData.getDungeonIds() != null) { if (pointData.getDungeonIds() != null) {
Arrays.stream(pointData.getDungeonIds()) Arrays.stream(pointData.getDungeonIds())
.forEach(id -> packet.addDungeonEntryList( .forEach(
DungeonEntryInfo.newBuilder().setDungeonId(id))); id -> packet.addDungeonEntryList(DungeonEntryInfo.newBuilder().setDungeonId(id)));
} }
// Add additional dungeon IDs. // Add additional dungeon IDs.
additional.forEach(id -> packet.addDungeonEntryList( additional.forEach(
DungeonEntryInfo.newBuilder().setDungeonId(id))); id -> packet.addDungeonEntryList(DungeonEntryInfo.newBuilder().setDungeonId(id)));
this.setData(packet); this.setData(packet);
} }

View File

@ -8,14 +8,15 @@ import emu.grasscutter.net.proto.ResinChangeNotifyOuterClass.ResinChangeNotify;
public class PacketResinChangeNotify extends BasePacket { public class PacketResinChangeNotify extends BasePacket {
public PacketResinChangeNotify(Player player) { public PacketResinChangeNotify(Player player) {
super(PacketOpcodes.ResinChangeNotify); super(PacketOpcodes.ResinChangeNotify);
ResinChangeNotify proto = ResinChangeNotify.newBuilder() ResinChangeNotify proto =
.setCurValue(player.getProperty(PlayerProperty.PROP_PLAYER_RESIN)) ResinChangeNotify.newBuilder()
.setNextAddTimestamp(player.getNextResinRefresh()) .setCurValue(player.getProperty(PlayerProperty.PROP_PLAYER_RESIN))
.setCurBuyCount(player.getResinBuyCount()) .setNextAddTimestamp(player.getNextResinRefresh())
.build(); .setCurBuyCount(player.getResinBuyCount())
.build();
this.setData(proto); this.setData(proto);
} }

View File

@ -7,16 +7,18 @@ import java.util.List;
public class PacketSceneAudioNotify extends BasePacket { public class PacketSceneAudioNotify extends BasePacket {
public PacketSceneAudioNotify(int sourceUid, List<Float> param2, List<String> param3, int type, List<Integer> param1) { public PacketSceneAudioNotify(
int sourceUid, List<Float> param2, List<String> param3, int type, List<Integer> param1) {
super(PacketOpcodes.SceneAudioNotify); super(PacketOpcodes.SceneAudioNotify);
SceneAudioNotifyOuterClass.SceneAudioNotify proto = SceneAudioNotifyOuterClass.SceneAudioNotify.newBuilder() SceneAudioNotifyOuterClass.SceneAudioNotify proto =
.setSourceUid(sourceUid) SceneAudioNotifyOuterClass.SceneAudioNotify.newBuilder()
.addAllParam2(param2) .setSourceUid(sourceUid)
.addAllParam3(param3) .addAllParam2(param2)
.setType(type) .addAllParam3(param3)
.addAllParam1(param1) .setType(type)
.build(); .addAllParam1(param1)
.build();
this.setData(proto); this.setData(proto);
} }

View File

@ -10,8 +10,10 @@ public final class PacketServerGlobalValueChangeNotify extends BasePacket {
public PacketServerGlobalValueChangeNotify(GameEntity entity, String abilityHash, int value) { public PacketServerGlobalValueChangeNotify(GameEntity entity, String abilityHash, int value) {
super(PacketOpcodes.ServerGlobalValueChangeNotify); super(PacketOpcodes.ServerGlobalValueChangeNotify);
this.setData(ServerGlobalValueChangeNotify.newBuilder() this.setData(
.setEntityId(entity.getId()).setValue(value) ServerGlobalValueChangeNotify.newBuilder()
.setKeyHash(Utils.abilityHash(abilityHash))); .setEntityId(entity.getId())
.setValue(value)
.setKeyHash(Utils.abilityHash(abilityHash)));
} }
} }

View File

@ -5,13 +5,14 @@ import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.WorldChestOpenNotifyOuterClass.WorldChestOpenNotify; import emu.grasscutter.net.proto.WorldChestOpenNotifyOuterClass.WorldChestOpenNotify;
public class PacketWorldChestOpenNotify extends BasePacket { public class PacketWorldChestOpenNotify extends BasePacket {
public PacketWorldChestOpenNotify(int groupId,int sceneId,int configId){ public PacketWorldChestOpenNotify(int groupId, int sceneId, int configId) {
super(PacketOpcodes.WorldChestOpenNotify); super(PacketOpcodes.WorldChestOpenNotify);
this.setData(WorldChestOpenNotify.newBuilder() this.setData(
.setGroupId(groupId) WorldChestOpenNotify.newBuilder()
.setSceneId(sceneId) .setGroupId(groupId)
.setConfigId(configId) .setSceneId(sceneId)
.build()); .setConfigId(configId)
.build());
} }
} }

View File

@ -56,7 +56,8 @@ public final class AnnouncementTask extends TaskHandler {
.toList(); .toList();
Grasscutter.getGameServer().getAnnouncementSystem().broadcast(toSend); Grasscutter.getGameServer().getAnnouncementSystem().broadcast(toSend);
Grasscutter.getLogger().trace("Broadcast {} announcement(s) to all online players", toSend.size()); Grasscutter.getLogger()
.trace("Broadcast {} announcement(s) to all online players", toSend.size());
// clear the interval count // clear the interval count
toSend.forEach(i -> intervalMap.put(i.getTemplateId(), 0)); toSend.forEach(i -> intervalMap.put(i.getTemplateId(), 0));

View File

@ -1,8 +1,8 @@
package emu.grasscutter.utils; package emu.grasscutter.utils;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.server.http.objects.QueryCurRegionRspJson; import emu.grasscutter.server.http.objects.QueryCurRegionRspJson;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.KeyFactory; import java.security.KeyFactory;
import java.security.PrivateKey; import java.security.PrivateKey;
@ -12,11 +12,9 @@ import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec; import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import emu.grasscutter.Grasscutter;
import javax.crypto.Cipher; import javax.crypto.Cipher;
public final class Crypto { public final class Crypto {
@ -42,8 +40,10 @@ public final class Crypto {
ENCRYPT_SEED_BUFFER = FileUtils.readResource("/keys/secretKeyBuffer.bin"); ENCRYPT_SEED_BUFFER = FileUtils.readResource("/keys/secretKeyBuffer.bin");
try { try {
CUR_SIGNING_KEY = KeyFactory.getInstance("RSA") CUR_SIGNING_KEY =
.generatePrivate(new PKCS8EncodedKeySpec(FileUtils.readResource("/keys/SigningKey.der"))); KeyFactory.getInstance("RSA")
.generatePrivate(
new PKCS8EncodedKeySpec(FileUtils.readResource("/keys/SigningKey.der")));
Pattern pattern = Pattern.compile("([0-9]*)_Pub\\.der"); Pattern pattern = Pattern.compile("([0-9]*)_Pub\\.der");
for (Path path : FileUtils.getPathsFromResource("/keys/game_keys")) { for (Path path : FileUtils.getPathsFromResource("/keys/game_keys")) {
@ -52,8 +52,9 @@ public final class Crypto {
var m = pattern.matcher(path.getFileName().toString()); var m = pattern.matcher(path.getFileName().toString());
if (m.matches()) { if (m.matches()) {
var key = KeyFactory.getInstance("RSA") var key =
.generatePublic(new X509EncodedKeySpec(FileUtils.read(path))); KeyFactory.getInstance("RSA")
.generatePublic(new X509EncodedKeySpec(FileUtils.read(path)));
EncryptionKeys.put(Integer.valueOf(m.group(1)), key); EncryptionKeys.put(Integer.valueOf(m.group(1)), key);
} }
@ -80,7 +81,8 @@ public final class Crypto {
return bytes; return bytes;
} }
public static QueryCurRegionRspJson encryptAndSignRegionData(byte[] regionInfo, String key_id) throws Exception { public static QueryCurRegionRspJson encryptAndSignRegionData(byte[] regionInfo, String key_id)
throws Exception {
if (key_id == null) { if (key_id == null) {
throw new Exception("Key ID was not set"); throw new Exception("Key ID was not set");
} }
@ -88,17 +90,18 @@ public final class Crypto {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, EncryptionKeys.get(Integer.valueOf(key_id))); cipher.init(Cipher.ENCRYPT_MODE, EncryptionKeys.get(Integer.valueOf(key_id)));
//Encrypt regionInfo in chunks // Encrypt regionInfo in chunks
ByteArrayOutputStream encryptedRegionInfoStream = new ByteArrayOutputStream(); ByteArrayOutputStream encryptedRegionInfoStream = new ByteArrayOutputStream();
//Thank you so much GH Copilot // Thank you so much GH Copilot
int chunkSize = 256 - 11; int chunkSize = 256 - 11;
int regionInfoLength = regionInfo.length; int regionInfoLength = regionInfo.length;
int numChunks = (int) Math.ceil(regionInfoLength / (double) chunkSize); int numChunks = (int) Math.ceil(regionInfoLength / (double) chunkSize);
for (int i = 0; i < numChunks; i++) { for (int i = 0; i < numChunks; i++) {
byte[] chunk = Arrays.copyOfRange(regionInfo, i * chunkSize, byte[] chunk =
Math.min((i + 1) * chunkSize, regionInfoLength)); Arrays.copyOfRange(
regionInfo, i * chunkSize, Math.min((i + 1) * chunkSize, regionInfoLength));
byte[] encryptedChunk = cipher.doFinal(chunk); byte[] encryptedChunk = cipher.doFinal(chunk);
encryptedRegionInfoStream.write(encryptedChunk); encryptedRegionInfoStream.write(encryptedChunk);
} }

View File

@ -1,8 +1,8 @@
package emu.grasscutter.utils; package emu.grasscutter.utils;
import static emu.grasscutter.config.Configuration.FALLBACK_LANGUAGE; import static emu.grasscutter.config.Configuration.FALLBACK_LANGUAGE;
import static emu.grasscutter.utils.FileUtils.getResourcePath;
import static emu.grasscutter.utils.FileUtils.getCachePath; import static emu.grasscutter.utils.FileUtils.getCachePath;
import static emu.grasscutter.utils.FileUtils.getResourcePath;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
@ -19,7 +19,6 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.io.*; import java.io.*;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
@ -290,8 +289,10 @@ public final class Language {
private static void saveTextMapsCache(Int2ObjectMap<TextStrings> input) throws IOException { private static void saveTextMapsCache(Int2ObjectMap<TextStrings> input) throws IOException {
Files.createDirectories(TEXTMAP_CACHE_PATH.getParent()); Files.createDirectories(TEXTMAP_CACHE_PATH.getParent());
try (var file = new ObjectOutputStream(new BufferedOutputStream( try (var file =
Files.newOutputStream(TEXTMAP_CACHE_PATH, StandardOpenOption.CREATE), 0x100000))) { new ObjectOutputStream(
new BufferedOutputStream(
Files.newOutputStream(TEXTMAP_CACHE_PATH, StandardOpenOption.CREATE), 0x100000))) {
file.writeInt(TEXTMAP_CACHE_VERSION); file.writeInt(TEXTMAP_CACHE_VERSION);
file.writeObject(input); file.writeObject(input);
} }