Enable script in big world (#884)

* add docs for tower

* fix: LEAK: ByteBuf.release() was not called

* enableScriptInBigWorld

* not print log when loaded scripts from cache

* revert the change of server tick

* revert the change of server tick

* fix

* optimize the performance: lazy load & cache

* fix the refresh group

* fix NPE

Co-authored-by: Melledy <52122272+Melledy@users.noreply.github.com>
This commit is contained in:
Akka
2022-05-15 19:19:24 +08:00
committed by GitHub
parent eb64b25f12
commit 6dc30e4def
21 changed files with 643 additions and 321 deletions

View File

@@ -1,5 +1,6 @@
package emu.grasscutter.game.world;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.GameDepot;
import emu.grasscutter.data.def.DungeonData;
@@ -19,12 +20,12 @@ import emu.grasscutter.game.world.SpawnDataEntry.SpawnGroupEntry;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult;
import emu.grasscutter.net.proto.VisionTypeOuterClass.VisionType;
import emu.grasscutter.scripts.SceneIndexManager;
import emu.grasscutter.scripts.SceneScriptManager;
import emu.grasscutter.scripts.data.SceneBlock;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.server.packet.send.PacketAvatarSkillInfoNotify;
import emu.grasscutter.server.packet.send.PacketDungeonChallengeFinishNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify;
import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify;
import emu.grasscutter.server.packet.send.PacketSceneEntityDisappearNotify;
@@ -34,6 +35,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.danilopianini.util.SpatialIndex;
import java.util.*;
import java.util.stream.Collectors;
public class Scene {
private final World world;
@@ -57,7 +59,6 @@ public class Scene {
private DungeonData dungeonData;
private int prevScene; // Id of the previous scene
private int prevScenePoint;
public Scene(World world, SceneData sceneData) {
this.world = world;
this.sceneData = sceneData;
@@ -323,14 +324,21 @@ public class Scene {
public synchronized void addEntityToSingleClient(Player player, GameEntity entity) {
this.addEntityDirectly(entity);
player.sendPacket(new PacketSceneEntityAppearNotify(entity));
}
public void addEntities(Collection<? extends GameEntity> entities){
addEntities(entities, VisionType.VISION_BORN);
}
public synchronized void addEntities(Collection<GameEntity> entities) {
public synchronized void addEntities(Collection<? extends GameEntity> entities, VisionType visionType) {
if(entities == null || entities.isEmpty()){
return;
}
for (GameEntity entity : entities) {
this.addEntityDirectly(entity);
}
this.broadcastPacket(new PacketSceneEntityAppearNotify(entities, VisionType.VISION_BORN));
this.broadcastPacket(new PacketSceneEntityAppearNotify(entities, visionType));
}
private GameEntity removeEntityDirectly(GameEntity entity) {
@@ -354,7 +362,7 @@ public class Scene {
this.broadcastPacket(new PacketSceneEntityDisappearNotify(oldEntity, VisionType.VISION_REPLACE));
this.broadcastPacket(new PacketSceneEntityAppearNotify(newEntity, VisionType.VISION_REPLACE, oldEntity.getId()));
}
public void showOtherEntities(Player player) {
List<GameEntity> entities = new LinkedList<>();
GameEntity currentEntity = player.getTeamManager().getCurrentAvatarEntity();
@@ -402,7 +410,7 @@ public class Scene {
// Death event
target.onDeath(attackerId);
}
public void onTick() {
if (this.getScriptManager().isInit()) {
this.checkBlocks();
@@ -410,9 +418,8 @@ public class Scene {
// TEMPORARY
this.checkSpawns();
}
// Triggers
this.getScriptManager().onTick();
this.scriptManager.checkRegions();
}
// TODO - Test
@@ -484,54 +491,75 @@ public class Scene {
this.broadcastPacket(new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_REMOVE));
}
}
public Set<SceneBlock> getPlayerActiveBlocks(Player player){
// TODO consider the borders of blocks
return getScriptManager().getBlocks().values().stream()
.filter(block -> block.contains(player.getPos()))
.collect(Collectors.toSet());
}
public void checkBlocks() {
Set<SceneBlock> visible = new HashSet<>();
for (Player player : this.getPlayers()) {
for (SceneBlock block : getScriptManager().getBlocks()) {
if (!block.contains(player.getPos())) {
continue;
}
visible.add(block);
}
var blocks = getPlayerActiveBlocks(player);
visible.addAll(blocks);
}
Iterator<SceneBlock> it = this.getLoadedBlocks().iterator();
while (it.hasNext()) {
SceneBlock block = it.next();
if (!visible.contains(block)) {
it.remove();
onUnloadBlock(block);
}
}
for (SceneBlock block : visible) {
for(var block : visible){
if (!this.getLoadedBlocks().contains(block)) {
this.onLoadBlock(block);
this.onLoadBlock(block, this.getPlayers());
this.getLoadedBlocks().add(block);
}
this.getPlayers().stream()
.filter(p -> block.contains(p.getPos()))
.forEach(p -> playerMeetGroups(p, block));
}
}
// TODO optimize
public void onLoadBlock(SceneBlock block) {
for (SceneGroup group : block.groups) {
public List<SceneGroup> playerMeetGroups(Player player, SceneBlock block){
int RANGE = 100;
var sceneGroups = SceneIndexManager.queryNeighbors(block.sceneGroupIndex, player.getPos(), RANGE);
var groups = new ArrayList<>(sceneGroups.stream()
.filter(group -> !scriptManager.getLoadedGroupSetPerBlock().get(block.id).contains(group))
.peek(group -> scriptManager.getLoadedGroupSetPerBlock().get(block.id).add(group)).toList());
if(groups.size() == 0){
return List.of();
}
Grasscutter.getLogger().info("Scene {} Block {} loaded {} group(s)", this.getId(), block.id, groups.size());
return groups;
}
public void onLoadBlock(SceneBlock block, List<Player> players) {
this.getScriptManager().loadBlockFromScript(block);
scriptManager.getLoadedGroupSetPerBlock().put(block.id , new HashSet<>());
// the groups form here is not added in current scene
var groups = players.stream()
.map(p -> playerMeetGroups(p, block))
.flatMap(Collection::stream)
.toList();
for (SceneGroup group : groups) {
// We load the script files for the groups here
if (!group.isLoaded()) {
this.getScriptManager().loadGroupFromScript(group);
}
group.triggers.forEach(getScriptManager()::registerTrigger);
group.regions.forEach(getScriptManager()::registerRegion);
this.getScriptManager().loadGroupFromScript(group);
}
// Spawn gadgets AFTER triggers are added
// TODO
for (SceneGroup group : block.groups) {
for (SceneGroup group : groups) {
if (group.init_config == null) {
continue;
}
@@ -541,26 +569,36 @@ public class Scene {
if (suite == 0) {
continue;
}
do {
this.getScriptManager().spawnGadgetsInGroup(group, suite);
var suiteData = group.getSuiteByIndex(suite);
getScriptManager().spawnGadgetsInGroup(group,suiteData);
getScriptManager().spawnMonstersInGroup(group, suiteData);
suite++;
} while (suite < group.init_config.end_suite);
}
Grasscutter.getLogger().info("Scene {} Block {} loaded.", this.getId(), block.id);
}
public void onUnloadBlock(SceneBlock block) {
List<GameEntity> toRemove = this.getEntities().values().stream().filter(e -> e.getBlockId() == block.id).toList();
List<GameEntity> toRemove = this.getEntities().values().stream()
.filter(e -> e.getBlockId() == block.id).toList();
if (toRemove.size() > 0) {
toRemove.stream().forEach(this::removeEntityDirectly);
toRemove.forEach(this::removeEntityDirectly);
this.broadcastPacket(new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_REMOVE));
}
for (SceneGroup group : block.groups) {
group.triggers.forEach(getScriptManager()::deregisterTrigger);
group.regions.forEach(getScriptManager()::deregisterRegion);
if(group.triggers != null){
group.triggers.forEach(getScriptManager()::deregisterTrigger);
}
if(group.regions != null){
group.regions.forEach(getScriptManager()::deregisterRegion);
}
}
scriptManager.getLoadedGroupSetPerBlock().remove(block.id);
Grasscutter.getLogger().info("Scene {} Block {} is unloaded.", this.getId(), block.id);
}
// Gadgets