mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-06-26 18:14:50 +08:00
Compare commits
No commits in common. "5d0610b6f2c0aead04dc4a15508c545ba463ee10" and "862bfa061133a35e453fa042d7125644810082f2" have entirely different histories.
5d0610b6f2
...
862bfa0611
@ -86,8 +86,8 @@ dependencies {
|
|||||||
|
|
||||||
implementation group: 'org.luaj', name: 'luaj-jse', version: '3.0.1'
|
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.esotericsoftware', name : 'reflectasm', version: '1.11.9'
|
||||||
implementation group: 'com.github.davidmoten', name : 'rtree-multi', version: '0.1'
|
|
||||||
|
|
||||||
protobuf files('proto/')
|
protobuf files('proto/')
|
||||||
|
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
option java_package = "emu.grasscutter.net.proto";
|
|
||||||
|
|
||||||
message GroupSuiteNotify {
|
|
||||||
enum CmdId {
|
|
||||||
option allow_alias = true;
|
|
||||||
NONE = 0;
|
|
||||||
ENET_CHANNEL_ID = 0;
|
|
||||||
ENET_IS_RELIABLE = 1;
|
|
||||||
CMD_ID = 3098;
|
|
||||||
}
|
|
||||||
|
|
||||||
map<uint32, uint32> group_map = 1;
|
|
||||||
}
|
|
@ -7,8 +7,12 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import emu.grasscutter.Grasscutter;
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.data.custom.*;
|
|
||||||
import emu.grasscutter.utils.Utils;
|
import emu.grasscutter.utils.Utils;
|
||||||
|
import emu.grasscutter.data.custom.AbilityEmbryoEntry;
|
||||||
|
import emu.grasscutter.data.custom.AbilityModifierEntry;
|
||||||
|
import emu.grasscutter.data.custom.OpenConfigEntry;
|
||||||
|
import emu.grasscutter.data.custom.MainQuestData;
|
||||||
|
import emu.grasscutter.data.custom.ScenePointEntry;
|
||||||
import emu.grasscutter.data.def.*;
|
import emu.grasscutter.data.def.*;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
@ -24,7 +28,6 @@ public class GameData {
|
|||||||
private static final Map<String, OpenConfigEntry> openConfigEntries = new HashMap<>();
|
private static final Map<String, OpenConfigEntry> openConfigEntries = new HashMap<>();
|
||||||
private static final Map<String, ScenePointEntry> scenePointEntries = new HashMap<>();
|
private static final Map<String, ScenePointEntry> scenePointEntries = new HashMap<>();
|
||||||
private static final Int2ObjectMap<MainQuestData> mainQuestData = new Int2ObjectOpenHashMap<>();
|
private static final Int2ObjectMap<MainQuestData> mainQuestData = new Int2ObjectOpenHashMap<>();
|
||||||
private static final Int2ObjectMap<SceneNpcBornData> npcBornData = new Int2ObjectOpenHashMap<>();
|
|
||||||
|
|
||||||
// ExcelConfigs
|
// ExcelConfigs
|
||||||
private static final Int2ObjectMap<PlayerLevelData> playerLevelDataMap = new Int2ObjectOpenHashMap<>();
|
private static final Int2ObjectMap<PlayerLevelData> playerLevelDataMap = new Int2ObjectOpenHashMap<>();
|
||||||
@ -128,9 +131,7 @@ public class GameData {
|
|||||||
public static Int2ObjectMap<MainQuestData> getMainQuestDataMap() {
|
public static Int2ObjectMap<MainQuestData> getMainQuestDataMap() {
|
||||||
return mainQuestData;
|
return mainQuestData;
|
||||||
}
|
}
|
||||||
public static Int2ObjectMap<SceneNpcBornData> getSceneNpcBornData() {
|
|
||||||
return npcBornData;
|
|
||||||
}
|
|
||||||
public static Int2ObjectMap<AvatarData> getAvatarDataMap() {
|
public static Int2ObjectMap<AvatarData> getAvatarDataMap() {
|
||||||
return avatarDataMap;
|
return avatarDataMap;
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,13 @@
|
|||||||
package emu.grasscutter.data;
|
package emu.grasscutter.data;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import emu.grasscutter.data.custom.*;
|
|
||||||
import emu.grasscutter.scripts.SceneIndexManager;
|
|
||||||
import emu.grasscutter.utils.Utils;
|
import emu.grasscutter.utils.Utils;
|
||||||
import lombok.SneakyThrows;
|
|
||||||
import org.reflections.Reflections;
|
import org.reflections.Reflections;
|
||||||
|
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
@ -21,9 +16,15 @@ import com.google.gson.reflect.TypeToken;
|
|||||||
import emu.grasscutter.Grasscutter;
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.data.common.PointData;
|
import emu.grasscutter.data.common.PointData;
|
||||||
import emu.grasscutter.data.common.ScenePointConfig;
|
import emu.grasscutter.data.common.ScenePointConfig;
|
||||||
|
import emu.grasscutter.data.custom.AbilityEmbryoEntry;
|
||||||
|
import emu.grasscutter.data.custom.AbilityModifier;
|
||||||
import emu.grasscutter.data.custom.AbilityModifier.AbilityConfigData;
|
import emu.grasscutter.data.custom.AbilityModifier.AbilityConfigData;
|
||||||
import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierAction;
|
import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierAction;
|
||||||
import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierActionType;
|
import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierActionType;
|
||||||
|
import emu.grasscutter.data.custom.AbilityModifierEntry;
|
||||||
|
import emu.grasscutter.data.custom.OpenConfigEntry;
|
||||||
|
import emu.grasscutter.data.custom.MainQuestData;
|
||||||
|
import emu.grasscutter.data.custom.ScenePointEntry;
|
||||||
import emu.grasscutter.game.world.SpawnDataEntry.*;
|
import emu.grasscutter.game.world.SpawnDataEntry.*;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
|
|
||||||
@ -64,7 +65,6 @@ public class ResourceLoader {
|
|||||||
loadQuests();
|
loadQuests();
|
||||||
// Load scene points - must be done AFTER resources are loaded
|
// Load scene points - must be done AFTER resources are loaded
|
||||||
loadScenePoints();
|
loadScenePoints();
|
||||||
loadNpcBornData();
|
|
||||||
// Custom - TODO move this somewhere else
|
// Custom - TODO move this somewhere else
|
||||||
try {
|
try {
|
||||||
GameData.getAvatarSkillDepotDataMap().get(504).setAbilities(
|
GameData.getAvatarSkillDepotDataMap().get(504).setAbilities(
|
||||||
@ -418,26 +418,6 @@ public class ResourceLoader {
|
|||||||
Grasscutter.getLogger().info("Loaded " + GameData.getMainQuestDataMap().size() + " MainQuestDatas.");
|
Grasscutter.getLogger().info("Loaded " + GameData.getMainQuestDataMap().size() + " MainQuestDatas.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
private static void loadNpcBornData(){
|
|
||||||
var folder = Files.list(Path.of(RESOURCE("BinOutput/Scene/SceneNpcBorn"))).toList();
|
|
||||||
|
|
||||||
for(var file : folder){
|
|
||||||
if(file.toFile().isDirectory()){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var data = Grasscutter.getGsonFactory().fromJson(Files.readString(file), SceneNpcBornData.class);
|
|
||||||
if(data.getBornPosList() == null || data.getBornPosList().size() == 0){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
data.setIndex(SceneIndexManager.buildIndex(3, data.getBornPosList(), item -> item.getPos().toPoint()));
|
|
||||||
GameData.getSceneNpcBornData().put(data.getSceneId(), data);
|
|
||||||
}
|
|
||||||
|
|
||||||
Grasscutter.getLogger().info("Loaded " + GameData.getSceneNpcBornData().size() + " SceneNpcBornDatas.");
|
|
||||||
}
|
|
||||||
// BinOutput configs
|
// BinOutput configs
|
||||||
|
|
||||||
private static class AvatarConfig {
|
private static class AvatarConfig {
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
package emu.grasscutter.data.custom;
|
|
||||||
|
|
||||||
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;
|
|
||||||
import lombok.experimental.FieldDefaults;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@FieldDefaults(level = AccessLevel.PRIVATE)
|
|
||||||
public class SceneNpcBornData {
|
|
||||||
int sceneId;
|
|
||||||
List<SceneNpcBornEntry> bornPosList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Spatial Index For NPC
|
|
||||||
*/
|
|
||||||
transient RTree<SceneNpcBornEntry, Geometry> index;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* npc groups
|
|
||||||
*/
|
|
||||||
transient Map<Integer, SceneGroup> groups = new ConcurrentHashMap<>();
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package emu.grasscutter.data.custom;
|
|
||||||
|
|
||||||
import emu.grasscutter.utils.Position;
|
|
||||||
import lombok.AccessLevel;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.experimental.FieldDefaults;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@FieldDefaults(level = AccessLevel.PRIVATE)
|
|
||||||
public class SceneNpcBornEntry {
|
|
||||||
int id;
|
|
||||||
int configId;
|
|
||||||
Position pos;
|
|
||||||
Position rot;
|
|
||||||
int groupId;
|
|
||||||
List<Integer> suiteIdList;
|
|
||||||
}
|
|
@ -1,81 +0,0 @@
|
|||||||
package emu.grasscutter.game.entity;
|
|
||||||
|
|
||||||
import emu.grasscutter.game.props.EntityIdType;
|
|
||||||
import emu.grasscutter.game.world.Scene;
|
|
||||||
import emu.grasscutter.net.proto.*;
|
|
||||||
import emu.grasscutter.scripts.data.SceneNPC;
|
|
||||||
import emu.grasscutter.utils.Position;
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
|
|
||||||
|
|
||||||
public class EntityNPC extends GameEntity{
|
|
||||||
|
|
||||||
private final Position position;
|
|
||||||
private final Position rotation;
|
|
||||||
private final SceneNPC metaNpc;
|
|
||||||
private final int suiteId;
|
|
||||||
|
|
||||||
public EntityNPC(Scene scene, SceneNPC metaNPC, int blockId, int suiteId) {
|
|
||||||
super(scene);
|
|
||||||
this.id = getScene().getWorld().getNextEntityId(EntityIdType.NPC);
|
|
||||||
setConfigId(metaNPC.config_id);
|
|
||||||
setGroupId(metaNPC.group.id);
|
|
||||||
setBlockId(blockId);
|
|
||||||
this.suiteId = suiteId;
|
|
||||||
this.position = metaNPC.pos.clone();
|
|
||||||
this.rotation = metaNPC.rot.clone();
|
|
||||||
this.metaNpc = metaNPC;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Int2FloatOpenHashMap getFightProperties() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Position getPosition() {
|
|
||||||
return position;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Position getRotation() {
|
|
||||||
return rotation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSuiteId() {
|
|
||||||
return suiteId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SceneEntityInfoOuterClass.SceneEntityInfo toProto() {
|
|
||||||
|
|
||||||
EntityAuthorityInfoOuterClass.EntityAuthorityInfo authority = EntityAuthorityInfoOuterClass.EntityAuthorityInfo.newBuilder()
|
|
||||||
.setAbilityInfo(AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo.newBuilder())
|
|
||||||
.setRendererChangedInfo(EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo.newBuilder())
|
|
||||||
.setAiInfo(SceneEntityAiInfoOuterClass.SceneEntityAiInfo.newBuilder()
|
|
||||||
.setIsAiOpen(true)
|
|
||||||
.setBornPos(getPosition().toProto()))
|
|
||||||
.setBornPos(getPosition().toProto())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
SceneEntityInfoOuterClass.SceneEntityInfo.Builder entityInfo = SceneEntityInfoOuterClass.SceneEntityInfo.newBuilder()
|
|
||||||
.setEntityId(getId())
|
|
||||||
.setEntityType(ProtEntityTypeOuterClass.ProtEntityType.PROT_ENTITY_NPC)
|
|
||||||
.setMotionInfo(MotionInfoOuterClass.MotionInfo.newBuilder()
|
|
||||||
.setPos(getPosition().toProto())
|
|
||||||
.setRot(getRotation().toProto())
|
|
||||||
.setSpeed(VectorOuterClass.Vector.newBuilder()))
|
|
||||||
.addAnimatorParaList(AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair.newBuilder())
|
|
||||||
.setEntityClientData(EntityClientDataOuterClass.EntityClientData.newBuilder())
|
|
||||||
.setEntityAuthorityInfo(authority)
|
|
||||||
.setLifeState(1);
|
|
||||||
|
|
||||||
|
|
||||||
entityInfo.setNpc(SceneNpcInfoOuterClass.SceneNpcInfo.newBuilder()
|
|
||||||
.setNpcId(metaNpc.npc_id)
|
|
||||||
.setBlockId(getBlockId())
|
|
||||||
.build());
|
|
||||||
|
|
||||||
return entityInfo.build();
|
|
||||||
}
|
|
||||||
}
|
|
@ -498,11 +498,11 @@ public class Scene {
|
|||||||
this.broadcastPacket(new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_REMOVE));
|
this.broadcastPacket(new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_REMOVE));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public Set<SceneBlock> getPlayerActiveBlocks(Player player){
|
||||||
public List<SceneBlock> getPlayerActiveBlocks(Player player){
|
// TODO consider the borders of blocks
|
||||||
// consider the borders' entities of blocks, so we check if contains by index
|
return getScriptManager().getBlocks().values().stream()
|
||||||
return SceneIndexManager.queryNeighbors(getScriptManager().getBlocksIndex(),
|
.filter(block -> block.contains(player.getPos()))
|
||||||
player.getPos().toXZDoubleArray(), Grasscutter.getConfig().server.game.loadEntitiesForPlayerRange);
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
public void checkBlocks() {
|
public void checkBlocks() {
|
||||||
Set<SceneBlock> visible = new HashSet<>();
|
Set<SceneBlock> visible = new HashSet<>();
|
||||||
@ -535,15 +535,13 @@ public class Scene {
|
|||||||
.toList();
|
.toList();
|
||||||
onLoadGroup(toLoad);
|
onLoadGroup(toLoad);
|
||||||
}
|
}
|
||||||
for (Player player : this.getPlayers()) {
|
|
||||||
getScriptManager().meetEntities(loadNpcForPlayer(player, block));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
public List<SceneGroup> playerMeetGroups(Player player, SceneBlock block){
|
public List<SceneGroup> playerMeetGroups(Player player, SceneBlock block){
|
||||||
List<SceneGroup> sceneGroups = SceneIndexManager.queryNeighbors(block.sceneGroupIndex, player.getPos().toDoubleArray(),
|
int RANGE = 100;
|
||||||
Grasscutter.getConfig().server.game.loadEntitiesForPlayerRange);
|
|
||||||
|
List<SceneGroup> sceneGroups = SceneIndexManager.queryNeighbors(block.sceneGroupIndex, player.getPos(), RANGE);
|
||||||
|
|
||||||
List<SceneGroup> groups = sceneGroups.stream()
|
List<SceneGroup> groups = sceneGroups.stream()
|
||||||
.filter(group -> !scriptManager.getLoadedGroupSetPerBlock().get(block.id).contains(group))
|
.filter(group -> !scriptManager.getLoadedGroupSetPerBlock().get(block.id).contains(group))
|
||||||
@ -592,9 +590,7 @@ public class Scene {
|
|||||||
List<SceneGadget> garbageGadgets = group.getGarbageGadgets();
|
List<SceneGadget> garbageGadgets = group.getGarbageGadgets();
|
||||||
|
|
||||||
if (garbageGadgets != null) {
|
if (garbageGadgets != null) {
|
||||||
entities.addAll(garbageGadgets.stream().map(g -> scriptManager.createGadget(group.id, group.block_id, g))
|
garbageGadgets.forEach(g -> scriptManager.createGadget(group.id, group.block_id, g));
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load suites
|
// Load suites
|
||||||
@ -609,13 +605,9 @@ public class Scene {
|
|||||||
suiteData.sceneTriggers.forEach(getScriptManager()::registerTrigger);
|
suiteData.sceneTriggers.forEach(getScriptManager()::registerTrigger);
|
||||||
|
|
||||||
entities.addAll(suiteData.sceneGadgets.stream()
|
entities.addAll(suiteData.sceneGadgets.stream()
|
||||||
.map(g -> scriptManager.createGadget(group.id, group.block_id, g))
|
.map(g -> scriptManager.createGadget(group.id, group.block_id, g)).toList());
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.toList());
|
|
||||||
entities.addAll(suiteData.sceneMonsters.stream()
|
entities.addAll(suiteData.sceneMonsters.stream()
|
||||||
.map(mob -> scriptManager.createMonster(group.id, group.block_id, mob))
|
.map(mob -> scriptManager.createMonster(group.id, group.block_id, mob)).toList());
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.toList());
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -634,7 +626,7 @@ public class Scene {
|
|||||||
this.broadcastPacket(new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_REMOVE));
|
this.broadcastPacket(new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_REMOVE));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (SceneGroup group : block.groups.values()) {
|
for (SceneGroup group : block.groups) {
|
||||||
if(group.triggers != null){
|
if(group.triggers != null){
|
||||||
group.triggers.values().forEach(getScriptManager()::deregisterTrigger);
|
group.triggers.values().forEach(getScriptManager()::deregisterTrigger);
|
||||||
}
|
}
|
||||||
@ -726,47 +718,4 @@ public class Scene {
|
|||||||
addEntity(entity);
|
addEntity(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public List<EntityNPC> loadNpcForPlayer(Player player, SceneBlock block){
|
|
||||||
if(!block.contains(player.getPos())){
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
var pos = player.getPos();
|
|
||||||
var data = GameData.getSceneNpcBornData().get(getId());
|
|
||||||
if(data == null){
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
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){
|
|
||||||
group = SceneGroup.of(item.getGroupId());
|
|
||||||
data.getGroups().putIfAbsent(item.getGroupId(), group);
|
|
||||||
group.load(getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
if(group.npc == null){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var npc = group.npc.get(item.getConfigId());
|
|
||||||
if(npc == null){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return getScriptManager().createNPC(npc, block.id, item.getSuiteIdList().get(0));
|
|
||||||
})
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.filter(item -> getEntities().values().stream()
|
|
||||||
.filter(e -> e instanceof EntityNPC)
|
|
||||||
.noneMatch(e -> e.getConfigId() == item.getConfigId()))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
if(entityNPCS.size() > 0){
|
|
||||||
broadcastPacket(new PacketGroupSuiteNotify(entityNPCS));
|
|
||||||
}
|
|
||||||
|
|
||||||
return entityNPCS;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,39 @@
|
|||||||
package emu.grasscutter.scripts;
|
package emu.grasscutter.scripts;
|
||||||
|
|
||||||
import com.github.davidmoten.rtreemulti.Entry;
|
import ch.ethz.globis.phtree.PhTree;
|
||||||
import com.github.davidmoten.rtreemulti.RTree;
|
import emu.grasscutter.utils.Position;
|
||||||
import com.github.davidmoten.rtreemulti.geometry.Geometry;
|
|
||||||
import com.github.davidmoten.rtreemulti.geometry.Rectangle;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
public class SceneIndexManager {
|
public class SceneIndexManager {
|
||||||
|
|
||||||
public static <T> RTree<T, Geometry> buildIndex(int dimensions, Collection<T> elements, Function<T, Geometry> extractor){
|
public static <T> void buildIndex(PhTree<T> tree, List<T> elements, Function<T, long[]> extractor){
|
||||||
RTree<T, Geometry> rtree = RTree.dimensions(dimensions).create();
|
elements.forEach(e -> tree.put(extractor.apply(e), e));
|
||||||
return rtree.add(elements.stream().map(e -> Entry.entry(e, extractor.apply(e))).toList());
|
|
||||||
}
|
}
|
||||||
public static <T> List<T> queryNeighbors(RTree<T, Geometry> tree, double[] position, int range){
|
public static <T> List<T> queryNeighbors(PhTree<T> tree, Position position, int range){
|
||||||
var result = new ArrayList<T>();
|
var result = new ArrayList<T>();
|
||||||
Rectangle rectangle = Rectangle.create(calRange(position, -range), calRange(position, range));
|
var arrPos = position.toLongArray();
|
||||||
var queryResult = tree.search(rectangle);
|
var query = tree.query(calRange(arrPos, -range), calRange(arrPos, range));
|
||||||
queryResult.forEach(q -> result.add(q.value()));
|
while(query.hasNext()){
|
||||||
|
var element = query.next();
|
||||||
|
result.add(element);
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
private static double[] calRange(double[] position, int range){
|
public static <T> List<T> queryNeighbors(PhTree<T> tree, long[] position, int range){
|
||||||
|
var result = new ArrayList<T>();
|
||||||
|
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){
|
||||||
var newPos = position.clone();
|
var newPos = position.clone();
|
||||||
for(int i=0;i<newPos.length;i++){
|
for(int i=0;i<position.length;i++){
|
||||||
newPos[i] += range;
|
newPos[i] += range;
|
||||||
}
|
}
|
||||||
return newPos;
|
return newPos;
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
package emu.grasscutter.scripts;
|
package emu.grasscutter.scripts;
|
||||||
|
|
||||||
import com.github.davidmoten.rtreemulti.RTree;
|
import ch.ethz.globis.phtree.PhTree;
|
||||||
import com.github.davidmoten.rtreemulti.geometry.Geometry;
|
|
||||||
import emu.grasscutter.Grasscutter;
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.data.GameData;
|
import emu.grasscutter.data.GameData;
|
||||||
import emu.grasscutter.data.def.MonsterData;
|
import emu.grasscutter.data.def.MonsterData;
|
||||||
import emu.grasscutter.data.def.WorldLevelData;
|
import emu.grasscutter.data.def.WorldLevelData;
|
||||||
import emu.grasscutter.game.entity.EntityGadget;
|
import emu.grasscutter.game.entity.EntityGadget;
|
||||||
import emu.grasscutter.game.entity.EntityMonster;
|
import emu.grasscutter.game.entity.EntityMonster;
|
||||||
import emu.grasscutter.game.entity.EntityNPC;
|
|
||||||
import emu.grasscutter.game.entity.GameEntity;
|
import emu.grasscutter.game.entity.GameEntity;
|
||||||
import emu.grasscutter.game.world.Scene;
|
import emu.grasscutter.game.world.Scene;
|
||||||
import emu.grasscutter.net.proto.VisionTypeOuterClass;
|
import emu.grasscutter.net.proto.VisionTypeOuterClass;
|
||||||
@ -142,15 +140,14 @@ public class SceneScriptManager {
|
|||||||
// TODO optimize
|
// TODO optimize
|
||||||
public SceneGroup getGroupById(int groupId) {
|
public SceneGroup getGroupById(int groupId) {
|
||||||
for (SceneBlock block : this.getScene().getLoadedBlocks()) {
|
for (SceneBlock block : this.getScene().getLoadedBlocks()) {
|
||||||
var group = block.groups.get(groupId);
|
for (SceneGroup group : block.groups) {
|
||||||
if(group == null){
|
if (group.id == groupId) {
|
||||||
continue;
|
if(!group.isLoaded()){
|
||||||
|
getScene().onLoadGroup(List.of(group));
|
||||||
|
}
|
||||||
|
return group;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!group.isLoaded()){
|
|
||||||
getScene().onLoadGroup(List.of(group));
|
|
||||||
}
|
|
||||||
return group;
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -368,9 +365,7 @@ public class SceneScriptManager {
|
|||||||
|
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
public EntityNPC createNPC(SceneNPC npc, int blockId, int suiteId) {
|
|
||||||
return new EntityNPC(getScene(), npc, blockId, suiteId);
|
|
||||||
}
|
|
||||||
public EntityMonster createMonster(int groupId, int blockId, SceneMonster monster) {
|
public EntityMonster createMonster(int groupId, int blockId, SceneMonster monster) {
|
||||||
if(monster == null){
|
if(monster == null){
|
||||||
return null;
|
return null;
|
||||||
@ -421,7 +416,7 @@ public class SceneScriptManager {
|
|||||||
getScene().addEntities(gameEntity);
|
getScene().addEntities(gameEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RTree<SceneBlock, Geometry> getBlocksIndex() {
|
public PhTree<SceneBlock> getBlocksIndex() {
|
||||||
return meta.sceneBlockIndex;
|
return meta.sceneBlockIndex;
|
||||||
}
|
}
|
||||||
public void removeMonstersInGroup(SceneGroup group, SceneSuite suite) {
|
public void removeMonstersInGroup(SceneGroup group, SceneSuite suite) {
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
package emu.grasscutter.scripts.data;
|
package emu.grasscutter.scripts.data;
|
||||||
|
|
||||||
import com.github.davidmoten.rtreemulti.RTree;
|
import ch.ethz.globis.phtree.PhTree;
|
||||||
import com.github.davidmoten.rtreemulti.geometry.Geometry;
|
import ch.ethz.globis.phtree.v16.PhTree16;
|
||||||
import com.github.davidmoten.rtreemulti.geometry.Rectangle;
|
|
||||||
import emu.grasscutter.Grasscutter;
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.scripts.SceneIndexManager;
|
import emu.grasscutter.scripts.SceneIndexManager;
|
||||||
import emu.grasscutter.scripts.ScriptLoader;
|
import emu.grasscutter.scripts.ScriptLoader;
|
||||||
@ -13,8 +12,7 @@ import lombok.ToString;
|
|||||||
import javax.script.Bindings;
|
import javax.script.Bindings;
|
||||||
import javax.script.CompiledScript;
|
import javax.script.CompiledScript;
|
||||||
import javax.script.ScriptException;
|
import javax.script.ScriptException;
|
||||||
import java.util.Map;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static emu.grasscutter.Configuration.SCRIPT;
|
import static emu.grasscutter.Configuration.SCRIPT;
|
||||||
|
|
||||||
@ -26,8 +24,8 @@ public class SceneBlock {
|
|||||||
public Position min;
|
public Position min;
|
||||||
|
|
||||||
public int sceneId;
|
public int sceneId;
|
||||||
public Map<Integer,SceneGroup> groups;
|
public List<SceneGroup> groups;
|
||||||
public RTree<SceneGroup, Geometry> sceneGroupIndex;
|
public PhTree<SceneGroup> sceneGroupIndex = new PhTree16<>(3);
|
||||||
|
|
||||||
private transient boolean loaded; // Not an actual variable in the scripts either
|
private transient boolean loaded; // Not an actual variable in the scripts either
|
||||||
|
|
||||||
@ -63,19 +61,13 @@ public class SceneBlock {
|
|||||||
cs.eval(bindings);
|
cs.eval(bindings);
|
||||||
|
|
||||||
// Set groups
|
// Set groups
|
||||||
groups = ScriptLoader.getSerializer().toList(SceneGroup.class, bindings.get("groups")).stream()
|
groups = ScriptLoader.getSerializer().toList(SceneGroup.class, bindings.get("groups"));
|
||||||
.collect(Collectors.toMap(x -> x.id, y -> y));
|
groups.forEach(g -> g.block_id = id);
|
||||||
|
SceneIndexManager.buildIndex(this.sceneGroupIndex, groups, g -> g.pos.toLongArray());
|
||||||
groups.values().forEach(g -> g.block_id = id);
|
|
||||||
this.sceneGroupIndex = SceneIndexManager.buildIndex(3, groups.values(), g -> g.pos.toPoint());
|
|
||||||
} catch (ScriptException e) {
|
} catch (ScriptException e) {
|
||||||
Grasscutter.getLogger().error("Error loading block " + id + " in scene " + sceneId, e);
|
Grasscutter.getLogger().error("Error loading block " + id + " in scene " + sceneId, e);
|
||||||
}
|
}
|
||||||
Grasscutter.getLogger().info("scene {} block {} is loaded successfully.", sceneId, id);
|
Grasscutter.getLogger().info("scene {} block {} is loaded successfully.", sceneId, id);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Rectangle toRectangle() {
|
|
||||||
return Rectangle.create(min.toXZDoubleArray(), max.toXZDoubleArray());
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -32,7 +32,7 @@ public class SceneGroup {
|
|||||||
public Map<Integer,SceneMonster> monsters; // <ConfigId, Monster>
|
public Map<Integer,SceneMonster> monsters; // <ConfigId, Monster>
|
||||||
public Map<Integer, SceneGadget> gadgets; // <ConfigId, Gadgets>
|
public Map<Integer, SceneGadget> gadgets; // <ConfigId, Gadgets>
|
||||||
public Map<String, SceneTrigger> triggers;
|
public Map<String, SceneTrigger> triggers;
|
||||||
public Map<Integer, SceneNPC> npc; // <NpcId, NPC>
|
|
||||||
public List<SceneRegion> regions;
|
public List<SceneRegion> regions;
|
||||||
public List<SceneSuite> suites;
|
public List<SceneSuite> suites;
|
||||||
public List<SceneVar> variables;
|
public List<SceneVar> variables;
|
||||||
@ -44,11 +44,6 @@ public class SceneGroup {
|
|||||||
private transient boolean loaded; // Not an actual variable in the scripts either
|
private transient boolean loaded; // Not an actual variable in the scripts either
|
||||||
private transient CompiledScript script;
|
private transient CompiledScript script;
|
||||||
private transient Bindings bindings;
|
private transient Bindings bindings;
|
||||||
public static SceneGroup of(int groupId) {
|
|
||||||
var group = new SceneGroup();
|
|
||||||
group.id = groupId;
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isLoaded() {
|
public boolean isLoaded() {
|
||||||
return loaded;
|
return loaded;
|
||||||
@ -129,10 +124,6 @@ public class SceneGroup {
|
|||||||
|
|
||||||
// Add variables to suite
|
// Add variables to suite
|
||||||
variables = ScriptLoader.getSerializer().toList(SceneVar.class, bindings.get("variables"));
|
variables = ScriptLoader.getSerializer().toList(SceneVar.class, bindings.get("variables"));
|
||||||
// NPC in groups
|
|
||||||
npc = ScriptLoader.getSerializer().toList(SceneNPC.class, bindings.get("npcs")).stream()
|
|
||||||
.collect(Collectors.toMap(x -> x.npc_id, y -> y));
|
|
||||||
npc.values().forEach(n -> n.group = this);
|
|
||||||
|
|
||||||
// Add monsters and gadgets to suite
|
// Add monsters and gadgets to suite
|
||||||
for (SceneSuite suite : suites) {
|
for (SceneSuite suite : suites) {
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
package emu.grasscutter.scripts.data;
|
package emu.grasscutter.scripts.data;
|
||||||
|
|
||||||
import com.github.davidmoten.rtreemulti.RTree;
|
import ch.ethz.globis.phtree.PhTree;
|
||||||
import com.github.davidmoten.rtreemulti.geometry.Geometry;
|
import ch.ethz.globis.phtree.v16.PhTree16;
|
||||||
import emu.grasscutter.Grasscutter;
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.scripts.SceneIndexManager;
|
import emu.grasscutter.scripts.SceneIndexManager;
|
||||||
import emu.grasscutter.scripts.ScriptLoader;
|
import emu.grasscutter.scripts.ScriptLoader;
|
||||||
|
import lombok.Data;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ public class SceneMeta {
|
|||||||
|
|
||||||
public Bindings context;
|
public Bindings context;
|
||||||
|
|
||||||
public RTree<SceneBlock, Geometry> sceneBlockIndex;
|
public PhTree<SceneBlock> sceneBlockIndex = new PhTree16<>(2);
|
||||||
|
|
||||||
public static SceneMeta of(int sceneId) {
|
public static SceneMeta of(int sceneId) {
|
||||||
return new SceneMeta().load(sceneId);
|
return new SceneMeta().load(sceneId);
|
||||||
@ -63,8 +64,8 @@ public class SceneMeta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.blocks = blocks.stream().collect(Collectors.toMap(b -> b.id, b -> b));
|
this.blocks = blocks.stream().collect(Collectors.toMap(b -> b.id, b -> b));
|
||||||
this.sceneBlockIndex = SceneIndexManager.buildIndex(2, blocks, SceneBlock::toRectangle);
|
SceneIndexManager.buildIndex(this.sceneBlockIndex, blocks, g -> g.min.toXZLongArray());
|
||||||
|
SceneIndexManager.buildIndex(this.sceneBlockIndex, blocks, g -> g.max.toXZLongArray());
|
||||||
} catch (ScriptException e) {
|
} catch (ScriptException e) {
|
||||||
Grasscutter.getLogger().error("Error running script", e);
|
Grasscutter.getLogger().error("Error running script", e);
|
||||||
return null;
|
return null;
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
package emu.grasscutter.scripts.data;
|
|
||||||
|
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.ToString;
|
|
||||||
|
|
||||||
@ToString
|
|
||||||
@Setter
|
|
||||||
public class SceneNPC extends SceneObject{
|
|
||||||
public int npc_id;
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
package emu.grasscutter.server.packet.send;
|
|
||||||
|
|
||||||
import emu.grasscutter.game.entity.EntityNPC;
|
|
||||||
import emu.grasscutter.net.packet.BasePacket;
|
|
||||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
|
||||||
import emu.grasscutter.net.proto.GroupSuiteNotifyOuterClass;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class PacketGroupSuiteNotify extends BasePacket {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* control which npc suite is loaded
|
|
||||||
*/
|
|
||||||
public PacketGroupSuiteNotify(List<EntityNPC> list) {
|
|
||||||
super(PacketOpcodes.GroupSuiteNotify);
|
|
||||||
|
|
||||||
var proto = GroupSuiteNotifyOuterClass.GroupSuiteNotify.newBuilder();
|
|
||||||
|
|
||||||
list.forEach(item -> proto.putGroupMap(item.getGroupId(), item.getSuiteId()));
|
|
||||||
|
|
||||||
this.setData(proto);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -135,8 +135,7 @@ public class ConfigContainer {
|
|||||||
public int bindPort = 22102;
|
public int bindPort = 22102;
|
||||||
/* This is the port used in the default region. */
|
/* This is the port used in the default region. */
|
||||||
public int accessPort = 0;
|
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 enableScriptInBigWorld = false;
|
||||||
public boolean enableConsole = true;
|
public boolean enableConsole = true;
|
||||||
public GameOptions gameOptions = new GameOptions();
|
public GameOptions gameOptions = new GameOptions();
|
||||||
|
@ -2,7 +2,6 @@ package emu.grasscutter.utils;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
import com.github.davidmoten.rtreemulti.geometry.Point;
|
|
||||||
import dev.morphia.annotations.Entity;
|
import dev.morphia.annotations.Entity;
|
||||||
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
|
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
|
||||||
|
|
||||||
@ -156,20 +155,10 @@ public class Position implements Serializable {
|
|||||||
.setZ(this.getZ())
|
.setZ(this.getZ())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
public Point toPoint(){
|
public long[] toLongArray(){
|
||||||
return Point.create(x,y,z);
|
return new long[]{(long) x, (long) y, (long) 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};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user