diff --git a/build.gradle b/build.gradle index 8801b3da0..f47a771d0 100644 --- a/build.gradle +++ b/build.gradle @@ -86,8 +86,8 @@ dependencies { implementation group: 'org.luaj', name: 'luaj-jse', version: '3.0.1' - implementation group: 'ch.ethz.globis.phtree', name : 'phtree', version: '2.5.0' implementation group: 'com.esotericsoftware', name : 'reflectasm', version: '1.11.9' + implementation group: 'com.github.davidmoten', name : 'rtree-multi', version: '0.1' protobuf files('proto/') diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java index 9d15fc61b..ed7d3ff2a 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -8,10 +8,9 @@ import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; -import ch.ethz.globis.phtree.PhTree; -import ch.ethz.globis.phtree.v16.PhTree16; import com.google.gson.Gson; import emu.grasscutter.data.custom.*; +import emu.grasscutter.scripts.SceneIndexManager; import emu.grasscutter.utils.Utils; import lombok.SneakyThrows; import org.reflections.Reflections; @@ -428,15 +427,12 @@ public class ResourceLoader { continue; } - PhTree index = new PhTree16<>(3); - var data = Grasscutter.getGsonFactory().fromJson(Files.readString(file), SceneNpcBornData.class); if(data.getBornPosList() == null || data.getBornPosList().size() == 0){ continue; } - data.getBornPosList().forEach(item -> index.put(item.getPos().toLongArray(), item)); - data.setIndex(index); + data.setIndex(SceneIndexManager.buildIndex(3, data.getBornPosList(), item -> item.getPos().toPoint())); GameData.getSceneNpcBornData().put(data.getSceneId(), data); } diff --git a/src/main/java/emu/grasscutter/data/custom/SceneNpcBornData.java b/src/main/java/emu/grasscutter/data/custom/SceneNpcBornData.java index 7aab358f2..968ef9a05 100644 --- a/src/main/java/emu/grasscutter/data/custom/SceneNpcBornData.java +++ b/src/main/java/emu/grasscutter/data/custom/SceneNpcBornData.java @@ -1,6 +1,7 @@ package emu.grasscutter.data.custom; -import ch.ethz.globis.phtree.PhTree; +import com.github.davidmoten.rtreemulti.RTree; +import com.github.davidmoten.rtreemulti.geometry.Geometry; import emu.grasscutter.scripts.data.SceneGroup; import lombok.AccessLevel; import lombok.Data; @@ -19,7 +20,7 @@ public class SceneNpcBornData { /** * Spatial Index For NPC */ - transient PhTree index; + transient RTree index; /** * npc groups diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index 44c10b10c..2fa9b3cb0 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -498,11 +498,11 @@ public class Scene { this.broadcastPacket(new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_REMOVE)); } } - public Set getPlayerActiveBlocks(Player player){ - // TODO consider the borders of blocks - return getScriptManager().getBlocks().values().stream() - .filter(block -> block.contains(player.getPos())) - .collect(Collectors.toSet()); + + public List getPlayerActiveBlocks(Player player){ + // consider the borders' entities of blocks, so we check if contains by index + return SceneIndexManager.queryNeighbors(getScriptManager().getBlocksIndex(), + player.getPos().toXZDoubleArray(), Grasscutter.getConfig().server.game.loadEntitiesForPlayerRange); } public void checkBlocks() { Set visible = new HashSet<>(); @@ -542,9 +542,8 @@ public class Scene { } public List playerMeetGroups(Player player, SceneBlock block){ - int RANGE = 100; - - List sceneGroups = SceneIndexManager.queryNeighbors(block.sceneGroupIndex, player.getPos(), RANGE); + List sceneGroups = SceneIndexManager.queryNeighbors(block.sceneGroupIndex, player.getPos().toDoubleArray(), + Grasscutter.getConfig().server.game.loadEntitiesForPlayerRange); List groups = sceneGroups.stream() .filter(group -> !scriptManager.getLoadedGroupSetPerBlock().get(block.id).contains(group)) @@ -732,14 +731,14 @@ public class Scene { return List.of(); } - int RANGE = 100; var pos = player.getPos(); var data = GameData.getSceneNpcBornData().get(getId()); if(data == null){ return List.of(); } - var npcs = SceneIndexManager.queryNeighbors(data.getIndex(), pos.toLongArray(), RANGE); + var npcs = SceneIndexManager.queryNeighbors(data.getIndex(), pos.toDoubleArray(), + Grasscutter.getConfig().server.game.loadEntitiesForPlayerRange); var entityNPCS = npcs.stream().map(item -> { var group = data.getGroups().get(item.getGroupId()); if(group == null){ diff --git a/src/main/java/emu/grasscutter/scripts/SceneIndexManager.java b/src/main/java/emu/grasscutter/scripts/SceneIndexManager.java index 504884427..597661226 100644 --- a/src/main/java/emu/grasscutter/scripts/SceneIndexManager.java +++ b/src/main/java/emu/grasscutter/scripts/SceneIndexManager.java @@ -1,7 +1,9 @@ package emu.grasscutter.scripts; -import ch.ethz.globis.phtree.PhTree; -import emu.grasscutter.utils.Position; +import com.github.davidmoten.rtreemulti.Entry; +import com.github.davidmoten.rtreemulti.RTree; +import com.github.davidmoten.rtreemulti.geometry.Geometry; +import com.github.davidmoten.rtreemulti.geometry.Rectangle; import java.util.ArrayList; import java.util.Collection; @@ -10,31 +12,20 @@ import java.util.function.Function; public class SceneIndexManager { - public static void buildIndex(PhTree tree, Collection elements, Function extractor){ - elements.forEach(e -> tree.put(extractor.apply(e), e)); + public static RTree buildIndex(int dimensions, Collection elements, Function extractor){ + RTree rtree = RTree.dimensions(dimensions).create(); + return rtree.add(elements.stream().map(e -> Entry.entry(e, extractor.apply(e))).toList()); } - public static List queryNeighbors(PhTree tree, Position position, int range){ + public static List queryNeighbors(RTree tree, double[] position, int range){ var result = new ArrayList(); - var arrPos = position.toLongArray(); - var query = tree.query(calRange(arrPos, -range), calRange(arrPos, range)); - while(query.hasNext()){ - var element = query.next(); - result.add(element); - } + Rectangle rectangle = Rectangle.create(calRange(position, -range), calRange(position, range)); + var queryResult = tree.search(rectangle); + queryResult.forEach(q -> result.add(q.value())); return result; } - public static List queryNeighbors(PhTree tree, long[] position, int range){ - var result = new ArrayList(); - var query = tree.query(calRange(position, -range), calRange(position, range)); - while(query.hasNext()){ - var element = query.next(); - result.add(element); - } - return result; - } - private static long[] calRange(long[] position, int range){ + private static double[] calRange(double[] position, int range){ var newPos = position.clone(); - for(int i=0;i getBlocksIndex() { + public RTree getBlocksIndex() { return meta.sceneBlockIndex; } public void removeMonstersInGroup(SceneGroup group, SceneSuite suite) { diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java b/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java index b42852d8f..3584f3fd5 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java @@ -1,7 +1,8 @@ package emu.grasscutter.scripts.data; -import ch.ethz.globis.phtree.PhTree; -import ch.ethz.globis.phtree.v16.PhTree16; +import com.github.davidmoten.rtreemulti.RTree; +import com.github.davidmoten.rtreemulti.geometry.Geometry; +import com.github.davidmoten.rtreemulti.geometry.Rectangle; import emu.grasscutter.Grasscutter; import emu.grasscutter.scripts.SceneIndexManager; import emu.grasscutter.scripts.ScriptLoader; @@ -12,7 +13,6 @@ import lombok.ToString; import javax.script.Bindings; import javax.script.CompiledScript; import javax.script.ScriptException; -import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -27,7 +27,7 @@ public class SceneBlock { public int sceneId; public Map groups; - public PhTree sceneGroupIndex = new PhTree16<>(3); + public RTree sceneGroupIndex; private transient boolean loaded; // Not an actual variable in the scripts either @@ -67,11 +67,15 @@ public class SceneBlock { .collect(Collectors.toMap(x -> x.id, y -> y)); groups.values().forEach(g -> g.block_id = id); - SceneIndexManager.buildIndex(this.sceneGroupIndex, groups.values(), g -> g.pos.toLongArray()); + this.sceneGroupIndex = SceneIndexManager.buildIndex(3, groups.values(), g -> g.pos.toPoint()); } catch (ScriptException e) { Grasscutter.getLogger().error("Error loading block " + id + " in scene " + sceneId, e); } Grasscutter.getLogger().info("scene {} block {} is loaded successfully.", sceneId, id); return this; } + + public Rectangle toRectangle() { + return Rectangle.create(min.toXZDoubleArray(), max.toXZDoubleArray()); + } } \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneMeta.java b/src/main/java/emu/grasscutter/scripts/data/SceneMeta.java index 4014b2e1e..212c2c85d 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneMeta.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneMeta.java @@ -1,11 +1,10 @@ package emu.grasscutter.scripts.data; -import ch.ethz.globis.phtree.PhTree; -import ch.ethz.globis.phtree.v16.PhTree16; +import com.github.davidmoten.rtreemulti.RTree; +import com.github.davidmoten.rtreemulti.geometry.Geometry; import emu.grasscutter.Grasscutter; import emu.grasscutter.scripts.SceneIndexManager; import emu.grasscutter.scripts.ScriptLoader; -import lombok.Data; import lombok.Setter; import lombok.ToString; @@ -27,7 +26,7 @@ public class SceneMeta { public Bindings context; - public PhTree sceneBlockIndex = new PhTree16<>(2); + public RTree sceneBlockIndex; public static SceneMeta of(int sceneId) { return new SceneMeta().load(sceneId); @@ -64,8 +63,8 @@ public class SceneMeta { } this.blocks = blocks.stream().collect(Collectors.toMap(b -> b.id, b -> b)); - SceneIndexManager.buildIndex(this.sceneBlockIndex, blocks, g -> g.min.toXZLongArray()); - SceneIndexManager.buildIndex(this.sceneBlockIndex, blocks, g -> g.max.toXZLongArray()); + this.sceneBlockIndex = SceneIndexManager.buildIndex(2, blocks, SceneBlock::toRectangle); + } catch (ScriptException e) { Grasscutter.getLogger().error("Error running script", e); return null; diff --git a/src/main/java/emu/grasscutter/utils/ConfigContainer.java b/src/main/java/emu/grasscutter/utils/ConfigContainer.java index 9fd5eebae..9961f1323 100644 --- a/src/main/java/emu/grasscutter/utils/ConfigContainer.java +++ b/src/main/java/emu/grasscutter/utils/ConfigContainer.java @@ -135,7 +135,8 @@ public class ConfigContainer { public int bindPort = 22102; /* This is the port used in the default region. */ public int accessPort = 0; - + /* Entities within a certain range will be loaded for the player */ + public int loadEntitiesForPlayerRange = 100; public boolean enableScriptInBigWorld = false; public boolean enableConsole = true; public GameOptions gameOptions = new GameOptions(); diff --git a/src/main/java/emu/grasscutter/utils/Position.java b/src/main/java/emu/grasscutter/utils/Position.java index 4442e8f98..a323fe919 100644 --- a/src/main/java/emu/grasscutter/utils/Position.java +++ b/src/main/java/emu/grasscutter/utils/Position.java @@ -2,6 +2,7 @@ package emu.grasscutter.utils; import java.io.Serializable; +import com.github.davidmoten.rtreemulti.geometry.Point; import dev.morphia.annotations.Entity; import emu.grasscutter.net.proto.VectorOuterClass.Vector; @@ -155,10 +156,20 @@ public class Position implements Serializable { .setZ(this.getZ()) .build(); } - public long[] toLongArray(){ - return new long[]{(long) x, (long) y, (long) z}; + public Point toPoint(){ + return Point.create(x,y,z); } - public long[] toXZLongArray(){ - return new long[]{(long) x, (long) z}; + + /** + * To XYZ array for Spatial Index + */ + public double[] toDoubleArray(){ + return new double[]{ x, y, z}; + } + /** + * To XZ array for Spatial Index (Blocks) + */ + public double[] toXZDoubleArray(){ + return new double[]{x, z}; } }