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