From 6c49fab137e3ea37e5c7c5eec02df578d95a6df9 Mon Sep 17 00:00:00 2001 From: zhaodice <63996691+zhaodice@users.noreply.github.com> Date: Mon, 18 Jul 2022 18:13:55 +0800 Subject: [PATCH] Block loader (sort and merge gadgets into different blocks) (#1517) Original commits: * block loader * fix * fix * fix foolish bug * add scales * rename * set to 600 * nitpick Co-authored-by: AnimeGitB <AnimeGitB@bigblueball.in> --- build.gradle | 2 +- .../java/emu/grasscutter/data/GameDepot.java | 40 +++++------ .../emu/grasscutter/data/ResourceLoader.java | 54 +++++++------- .../emu/grasscutter/game/world/Scene.java | 46 ++++++------ .../game/world/SpawnDataEntry.java | 71 +++++++++++++++++-- 5 files changed, 135 insertions(+), 78 deletions(-) diff --git a/build.gradle b/build.gradle index 6ff91114..52477830 100644 --- a/build.gradle +++ b/build.gradle @@ -82,7 +82,7 @@ dependencies { implementation group: 'dev.morphia.morphia', name: 'morphia-core', version: '2.2.7' implementation group: 'org.greenrobot', name: 'eventbus-java', version: '3.3.1' - implementation group: 'org.danilopianini', name: 'java-quadtree', version: '0.1.9' + //implementation group: 'org.danilopianini', name: 'java-quadtree', version: '0.1.9' implementation group: 'org.quartz-scheduler', name: 'quartz', version: '2.3.2' implementation group: 'org.quartz-scheduler', name: 'quartz-jobs', version: '2.3.2' diff --git a/src/main/java/emu/grasscutter/data/GameDepot.java b/src/main/java/emu/grasscutter/data/GameDepot.java index 0b44ac44..76b9b7c6 100644 --- a/src/main/java/emu/grasscutter/data/GameDepot.java +++ b/src/main/java/emu/grasscutter/data/GameDepot.java @@ -5,29 +5,27 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.danilopianini.util.FlexibleQuadTree; -import org.danilopianini.util.SpatialIndex; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.ResourceLoader.AvatarConfig; -import emu.grasscutter.data.ResourceLoader.AvatarConfigAbility; import emu.grasscutter.data.excels.ReliquaryAffixData; import emu.grasscutter.data.excels.ReliquaryMainPropData; import emu.grasscutter.game.world.SpawnDataEntry; -import emu.grasscutter.game.world.SpawnDataEntry.SpawnGroupEntry; import emu.grasscutter.utils.WeightedList; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; public class GameDepot { + public static final int[] BLOCK_SIZE = new int[]{50,500};//Scales + private static Int2ObjectMap<WeightedList<ReliquaryMainPropData>> relicRandomMainPropDepot = new Int2ObjectOpenHashMap<>(); private static Int2ObjectMap<List<ReliquaryMainPropData>> relicMainPropDepot = new Int2ObjectOpenHashMap<>(); private static Int2ObjectMap<List<ReliquaryAffixData>> relicAffixDepot = new Int2ObjectOpenHashMap<>(); - + private static Map<String, AvatarConfig> playerAbilities = new HashMap<>(); - private static Int2ObjectMap<SpatialIndex<SpawnGroupEntry>> spawnLists = new Int2ObjectOpenHashMap<>(); - - public static void load() { + private static HashMap<SpawnDataEntry.GridBlockId, ArrayList<SpawnDataEntry>> spawnLists = new HashMap<>(); + + public static void load() { for (ReliquaryMainPropData data : GameData.getReliquaryMainPropDataMap().values()) { if (data.getWeight() <= 0 || data.getPropDepotId() <= 0) { continue; @@ -49,7 +47,7 @@ public class GameDepot { Grasscutter.getLogger().error("Relic properties are missing weights! Please check your ReliquaryMainPropExcelConfigData or ReliquaryAffixExcelConfigData files in your ExcelBinOutput folder."); } } - + public static ReliquaryMainPropData getRandomRelicMainProp(int depot) { WeightedList<ReliquaryMainPropData> depotList = relicRandomMainPropDepot.get(depot); if (depotList == null) { @@ -57,7 +55,7 @@ public class GameDepot { } return depotList.next(); } - + public static List<ReliquaryMainPropData> getRelicMainPropList(int depot) { return relicMainPropDepot.get(depot); } @@ -65,20 +63,20 @@ public class GameDepot { public static List<ReliquaryAffixData> getRelicAffixList(int depot) { return relicAffixDepot.get(depot); } - - public static Int2ObjectMap<SpatialIndex<SpawnGroupEntry>> getSpawnLists() { - return spawnLists; - } - - public static SpatialIndex<SpawnGroupEntry> getSpawnListById(int sceneId) { - return getSpawnLists().computeIfAbsent(sceneId, id -> new FlexibleQuadTree<>()); - } - public static Map<String, AvatarConfig> getPlayerAbilities() { - return playerAbilities; - } + public static HashMap<SpawnDataEntry.GridBlockId, ArrayList<SpawnDataEntry>> getSpawnLists() { + return spawnLists; + } + + public static void addSpawnListById(HashMap<SpawnDataEntry.GridBlockId, ArrayList<SpawnDataEntry>> data) { + spawnLists.putAll(data); + } public static void setPlayerAbilities(Map<String, AvatarConfig> playerAbilities) { GameDepot.playerAbilities = playerAbilities; } + + public static Map<String, AvatarConfig> getPlayerAbilities() { + return playerAbilities; + } } diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java index 29e05add..5d93b724 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -9,8 +9,8 @@ import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; -import com.google.gson.Gson; import emu.grasscutter.data.binout.*; +import emu.grasscutter.game.world.SpawnDataEntry; import emu.grasscutter.scripts.SceneIndexManager; import emu.grasscutter.utils.Utils; import lombok.SneakyThrows; @@ -28,7 +28,6 @@ import emu.grasscutter.data.common.PointData; import emu.grasscutter.data.common.ScenePointConfig; import emu.grasscutter.game.world.SpawnDataEntry.*; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import static emu.grasscutter.Configuration.*; import static emu.grasscutter.utils.Language.translate; @@ -309,34 +308,39 @@ public class ResourceLoader { private static void loadSpawnData() { String[] spawnDataNames = {"Spawns.json", "GadgetSpawns.json"}; - Int2ObjectMap<SpawnGroupEntry> spawnEntryMap = new Int2ObjectOpenHashMap<>(); - - for (String name : spawnDataNames) { - // Load spawn entries from file - try (InputStream spawnDataEntries = DataLoader.load(name)) { - Type type = TypeToken.getParameterized(Collection.class, SpawnGroupEntry.class).getType(); - List<SpawnGroupEntry> list = Grasscutter.getGsonFactory().fromJson(new InputStreamReader(spawnDataEntries), type); - - // Add spawns to group if it already exists in our spawn group map - for (SpawnGroupEntry group : list) { - if (spawnEntryMap.containsKey(group.getGroupId())) { - spawnEntryMap.get(group.getGroupId()).getSpawns().addAll(group.getSpawns()); - } else { - spawnEntryMap.put(group.getGroupId(), group); - } - } - } catch (Exception ignored) {} - } - + ArrayList<SpawnGroupEntry> spawnEntryMap = new ArrayList<>(); + + for (String name : spawnDataNames) { + // Load spawn entries from file + try (InputStream spawnDataEntries = DataLoader.load(name)) { + Type type = TypeToken.getParameterized(Collection.class, SpawnGroupEntry.class).getType(); + List<SpawnGroupEntry> list = Grasscutter.getGsonFactory().fromJson(new InputStreamReader(spawnDataEntries), type); + + // Add spawns to group if it already exists in our spawn group map + spawnEntryMap.addAll(list); + } catch (Exception ignored) {} + } + if (spawnEntryMap.isEmpty()) { Grasscutter.getLogger().error("No spawn data loaded!"); return; } - for (SpawnGroupEntry entry : spawnEntryMap.values()) { - entry.getSpawns().forEach(s -> s.setGroup(entry)); - GameDepot.getSpawnListById(entry.getSceneId()).insert(entry, entry.getPos().getX(), entry.getPos().getZ()); - } + HashMap<GridBlockId, ArrayList<SpawnDataEntry>> areaSort = new HashMap<>(); + //key = sceneId,x,z , value = ArrayList<SpawnDataEntry> + for (SpawnGroupEntry entry : spawnEntryMap) { + entry.getSpawns().forEach( + s -> { + s.setGroup(entry); + GridBlockId point = s.getBlockId(); + if(!areaSort.containsKey(point)) { + areaSort.put(point, new ArrayList<>()); + } + areaSort.get(point).add(s); + } + ); + } + GameDepot.addSpawnListById(areaSort); } private static void loadOpenConfig() { diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index 82f534e5..d3af36c9 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -9,12 +9,10 @@ import emu.grasscutter.game.dungeons.DungeonSettleListener; import emu.grasscutter.game.entity.*; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.TeamInfo; -import emu.grasscutter.game.props.ClimateType; import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.LifeState; import emu.grasscutter.game.props.SceneType; import emu.grasscutter.game.quest.QuestGroupSuite; -import emu.grasscutter.game.world.SpawnDataEntry.SpawnGroupEntry; import emu.grasscutter.game.dungeons.challenge.WorldChallenge; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult; @@ -26,15 +24,10 @@ import emu.grasscutter.scripts.data.SceneGadget; import emu.grasscutter.scripts.data.SceneGroup; import emu.grasscutter.server.packet.send.*; import emu.grasscutter.utils.Position; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import org.danilopianini.util.SpatialIndex; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.stream.Collectors; public class Scene { private final World world; @@ -44,6 +37,7 @@ public class Scene { private final Set<SpawnDataEntry> spawnedEntities; private final Set<SpawnDataEntry> deadSpawnedEntities; private final Set<SceneBlock> loadedBlocks; + private Set<SpawnDataEntry.GridBlockId> loadedGridBlocks; private boolean dontDestroyWhenEmpty; private int autoCloseTime; @@ -68,6 +62,7 @@ public class Scene { this.spawnedEntities = ConcurrentHashMap.newKeySet(); this.deadSpawnedEntities = ConcurrentHashMap.newKeySet(); this.loadedBlocks = ConcurrentHashMap.newKeySet(); + this.loadedGridBlocks = new HashSet<>(); this.npcBornEntrySet = ConcurrentHashMap.newKeySet(); this.scriptManager = new SceneScriptManager(this); } @@ -461,25 +456,24 @@ public class Scene { this.npcBornEntrySet = npcBornEntries; } - // TODO - Test - public synchronized void checkSpawns() { - int RANGE = 100; - - SpatialIndex<SpawnGroupEntry> list = GameDepot.getSpawnListById(this.getId()); - Set<SpawnDataEntry> visible = new HashSet<>(); - - for (Player player : this.getPlayers()) { - Position position = player.getPos(); - Collection<SpawnGroupEntry> entries = list.query( - new double[] {position.getX() - RANGE, position.getZ() - RANGE}, - new double[] {position.getX() + RANGE, position.getZ() + RANGE} - ); - for (SpawnGroupEntry entry : entries) { - for (SpawnDataEntry spawnData : entry.getSpawns()) { - visible.add(spawnData); - } - } - } + public synchronized void checkSpawns() { + Set<SpawnDataEntry.GridBlockId> loadedGridBlocks = new HashSet<>(); + for (Player player : this.getPlayers()) { + for (SpawnDataEntry.GridBlockId block : SpawnDataEntry.GridBlockId.getAdjacentGridBlockIds(player.getSceneId(), player.getPos())) + loadedGridBlocks.add(block); + } + if (this.loadedGridBlocks.containsAll(loadedGridBlocks)) { // Don't recalculate static spawns if nothing has changed + return; + } + this.loadedGridBlocks = loadedGridBlocks; + var spawnLists = GameDepot.getSpawnLists(); + Set<SpawnDataEntry> visible = new HashSet<>(); + for (var block : loadedGridBlocks) { + var spawns = spawnLists.get(block); + if(spawns!=null) { + visible.addAll(spawns); + } + } // World level WorldLevelData worldLevelData = GameData.getWorldLevelDataMap().get(getWorld().getWorldLevel()); diff --git a/src/main/java/emu/grasscutter/game/world/SpawnDataEntry.java b/src/main/java/emu/grasscutter/game/world/SpawnDataEntry.java index 3789f71c..456197c7 100644 --- a/src/main/java/emu/grasscutter/game/world/SpawnDataEntry.java +++ b/src/main/java/emu/grasscutter/game/world/SpawnDataEntry.java @@ -1,7 +1,9 @@ package emu.grasscutter.game.world; import java.util.List; +import java.util.Objects; +import emu.grasscutter.data.GameDepot; import emu.grasscutter.utils.Position; public class SpawnDataEntry { @@ -60,11 +62,18 @@ public class SpawnDataEntry { return rot; } + public GridBlockId getBlockId(){ + int scale = GridBlockId.getScale(gadgetId); + return new GridBlockId(group.sceneId,scale, + (int)(pos.getX() / GameDepot.BLOCK_SIZE[scale]), + (int)(pos.getZ() / GameDepot.BLOCK_SIZE[scale]) + ); + } + public static class SpawnGroupEntry { private int sceneId; private int groupId; private int blockId; - private Position pos; private List<SpawnDataEntry> spawns; public int getSceneId() { @@ -83,12 +92,64 @@ public class SpawnDataEntry { this.blockId = blockId; } - public Position getPos() { - return pos; - } - public List<SpawnDataEntry> getSpawns() { return spawns; } } + + public static class GridBlockId { + int sceneId; + int scale; + int x; + int z; + + public GridBlockId(int sceneId, int scale, int x, int z) { + this.sceneId = sceneId; + this.scale = scale; + this.x = x; + this.z = z; + } + + @Override + public String toString() { + return "SpawnDataEntryScaledPoint{" + + "sceneId=" + sceneId + + ", scale=" + scale + + ", x=" + x + + ", z=" + z + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GridBlockId that = (GridBlockId) o; + return sceneId == that.sceneId && scale == that.scale && x == that.x && z == that.z; + } + + @Override + public int hashCode() { + return Objects.hash(sceneId, scale, x, z); + } + + public static GridBlockId[] getAdjacentGridBlockIds(int sceneId, Position pos){ + GridBlockId[] results = new GridBlockId[5*5*GameDepot.BLOCK_SIZE.length]; + int t=0; + for (int scale = 0; scale < GameDepot.BLOCK_SIZE.length; scale++) { + int x = ((int)(pos.getX()/GameDepot.BLOCK_SIZE[scale])); + int z = ((int)(pos.getZ()/GameDepot.BLOCK_SIZE[scale])); + for (int i=x-2; i<x+3; i++) { + for (int j=z-2; j<z+3; j++) { + results[t++] = new GridBlockId(sceneId, scale, i, j); + } + } + } + return results; + } + + public static int getScale(int gadgetId){ + return 0;//you should implement here,this is index of GameDepot.BLOCK_SIZE + } + } } -- GitLab