Skip to content
Snippets Groups Projects
Commit 711e6eab authored by Melledy's avatar Melledy
Browse files

Natural spawn test

(without luas)
parent c4bdcc38
No related merge requests found
Showing with 300 additions and 6 deletions
......@@ -37,6 +37,7 @@ dependencies {
implementation group: 'dev.morphia.morphia', name: 'morphia-core', version: '2.2.6'
implementation group: 'org.greenrobot', name: 'eventbus-java', version: '3.3.1'
implementation group: 'org.danilopianini', name: 'java-quadtree', version: '0.1.9'
}
application {
......
This diff is collapsed.
......@@ -3,9 +3,14 @@ package emu.grasscutter.data;
import java.util.ArrayList;
import java.util.List;
import org.danilopianini.util.FlexibleQuadTree;
import org.danilopianini.util.SpatialIndex;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.def.ReliquaryAffixData;
import emu.grasscutter.data.def.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;
......@@ -14,6 +19,8 @@ public class GenshinDepot {
private static Int2ObjectMap<WeightedList<ReliquaryMainPropData>> relicMainPropDepot = new Int2ObjectOpenHashMap<>();
private static Int2ObjectMap<List<ReliquaryAffixData>> relicAffixDepot = new Int2ObjectOpenHashMap<>();
private static Int2ObjectMap<SpatialIndex<SpawnGroupEntry>> spawnLists = new Int2ObjectOpenHashMap<>();
public static void load() {
for (ReliquaryMainPropData data : GenshinData.getReliquaryMainPropDataMap().values()) {
if (data.getWeight() <= 0 || data.getPropDepotId() <= 0) {
......@@ -46,4 +53,12 @@ public class GenshinDepot {
public static List<ReliquaryAffixData> getRandomRelicAffixList(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<>());
}
}
......@@ -19,6 +19,8 @@ import emu.grasscutter.data.common.ScenePointConfig;
import emu.grasscutter.data.custom.AbilityEmbryoEntry;
import emu.grasscutter.data.custom.OpenConfigEntry;
import emu.grasscutter.data.custom.ScenePointEntry;
import emu.grasscutter.game.world.SpawnDataEntry;
import emu.grasscutter.game.world.SpawnDataEntry.SpawnGroupEntry;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
public class ResourceLoader {
......@@ -49,6 +51,8 @@ public class ResourceLoader {
loadScenePoints();
// Process into depots
GenshinDepot.load();
// Load spawn data
loadSpawnData();
// Custom - TODO move this somewhere else
try {
GenshinData.getAvatarSkillDepotDataMap().get(504).setAbilities(
......@@ -233,6 +237,33 @@ public class ResourceLoader {
}
}
private static void loadSpawnData() {
// Read from cached file if exists
File spawnDataEntries = new File(Grasscutter.getConfig().DATA_FOLDER + "Spawns.json");
List<SpawnGroupEntry> spawnEntryList = null;
if (spawnDataEntries.exists()) {
// Load from cache
try (FileReader fileReader = new FileReader(spawnDataEntries)) {
spawnEntryList = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, SpawnGroupEntry.class).getType());
} catch (Exception e) {
e.printStackTrace();
}
}
if (spawnEntryList == null || spawnEntryList.isEmpty()) {
Grasscutter.getLogger().error("No spawn data loaded!");
return;
}
for (SpawnGroupEntry entry : spawnEntryList) {
entry.getSpawns().stream().forEach(s -> {
s.setGroup(entry);
});
GenshinDepot.getSpawnListById(entry.getSceneId()).insert(entry, entry.getPos().getX(), entry.getPos().getZ());
}
}
private static void loadOpenConfig() {
// Read from cached file if exists
File openConfigCache = new File(Grasscutter.getConfig().DATA_FOLDER + "OpenConfig.json");
......
......@@ -195,11 +195,11 @@ public class GenshinPlayer {
this.world = world;
}
public GenshinScene getScene() {
public synchronized GenshinScene getScene() {
return scene;
}
public void setScene(GenshinScene scene) {
public synchronized void setScene(GenshinScene scene) {
this.scene = scene;
}
......@@ -695,6 +695,10 @@ public class GenshinPlayer {
it.remove();
}
}
//
if (this.getScene() != null && this.getSceneLoadState() == SceneLoadState.LOADED) {
this.getScene().checkSpawns(this);
}
// Ping
if (this.getWorld() != null) {
// RTT notify - very important to send this often
......
......@@ -3,19 +3,31 @@ package emu.grasscutter.game;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.danilopianini.util.SpatialIndex;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GenshinData;
import emu.grasscutter.data.GenshinDepot;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.def.MonsterData;
import emu.grasscutter.data.def.SceneData;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.entity.EntityClientGadget;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.entity.GenshinEntity;
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.world.SpawnDataEntry;
import emu.grasscutter.game.world.SpawnDataEntry.SpawnGroupEntry;
import emu.grasscutter.net.packet.GenshinPacket;
import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult;
import emu.grasscutter.net.proto.VisionTypeOuterClass.VisionType;
......@@ -23,6 +35,7 @@ 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;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
......@@ -32,6 +45,9 @@ public class GenshinScene {
private final List<GenshinPlayer> players;
private final Int2ObjectMap<GenshinEntity> entities;
private final Set<SpawnDataEntry> spawnedEntities;
private final Set<SpawnDataEntry> deadSpawnedEntities;
private int time;
private ClimateType climate;
private int weather;
......@@ -44,6 +60,9 @@ public class GenshinScene {
this.time = 8 * 60;
this.climate = ClimateType.CLIMATE_SUNNY;
this.spawnedEntities = new HashSet<>();
this.deadSpawnedEntities = new HashSet<>();
}
public int getId() {
......@@ -102,6 +121,14 @@ public class GenshinScene {
this.weather = weather;
}
public Set<SpawnDataEntry> getSpawnedEntities() {
return spawnedEntities;
}
public Set<SpawnDataEntry> getDeadSpawnedEntities() {
return deadSpawnedEntities;
}
public boolean isInScene(GenshinEntity entity) {
return this.entities.containsKey(entity.getId());
}
......@@ -277,6 +304,73 @@ public class GenshinScene {
target.onDeath(attackerId);
}
// TODO Do not use yet
public synchronized void onTick() {
for (GenshinPlayer player : this.getPlayers()) {
this.checkSpawns(player);
}
}
// TODO - Test
public void checkSpawns(GenshinPlayer player) {
SpatialIndex<SpawnGroupEntry> list = GenshinDepot.getSpawnListById(this.getId());
Set<SpawnDataEntry> visible = new HashSet<>();
int RANGE = 100;
Collection<SpawnGroupEntry> entries = list.query(
new double[] {player.getPos().getX() - RANGE, player.getPos().getZ() - RANGE},
new double[] {player.getPos().getX() + RANGE, player.getPos().getZ() + RANGE}
);
for (SpawnGroupEntry entry : entries) {
for (SpawnDataEntry spawnData : entry.getSpawns()) {
visible.add(spawnData);
}
}
// Todo
List<GenshinEntity> toAdd = new LinkedList<>();
List<GenshinEntity> toRemove = new LinkedList<>();
for (SpawnDataEntry entry : visible) {
if (!this.getSpawnedEntities().contains(entry) && !this.getDeadSpawnedEntities().contains(entry)) {
// Spawn entity
MonsterData data = GenshinData.getMonsterDataMap().get(entry.getMonsterId());
if (data == null) {
continue;
}
EntityMonster entity = new EntityMonster(this, data, entry.getPos(), entry.getLevel());
entity.getRotation().set(entry.getRot());
entity.setGroupId(entry.getGroup().getGroupId());
entity.setPoseId(entry.getPoseId());
entity.setConfigId(entry.getConfigId());
entity.setSpawnEntry(entry);
toAdd.add(entity);
this.addEntityDirectly(entity);
// Add to spawned list
this.getSpawnedEntities().add(entry);
}
}
for (GenshinEntity entity : this.getEntities().values()) {
if (entity.getSpawnEntry() != null && !visible.contains(entity.getSpawnEntry())) {
toRemove.add(entity);
this.removeEntityDirectly(entity);
}
}
if (toAdd.size() > 0) {
this.broadcastPacket(new PacketSceneEntityAppearNotify(toAdd, VisionType.VisionBorn));
}
if (toRemove.size() > 0) {
this.broadcastPacket(new PacketSceneEntityDisappearNotify(toRemove, VisionType.VisionRemove));
}
}
// Gadgets
public void onPlayerCreateGadget(EntityClientGadget gadget) {
......
......@@ -37,6 +37,10 @@ public class EntityMonster extends GenshinEntity {
private final int level;
private int weaponEntityId;
private int groupId;
private int configId;
private int poseId;
public EntityMonster(GenshinScene scene, MonsterData monsterData, Position pos, int level) {
super(scene);
this.id = getWorld().getNextEntityId(EntityIdType.MONSTER);
......@@ -100,9 +104,35 @@ public class EntityMonster extends GenshinEntity {
return this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) > 0f;
}
public int getGroupId() {
return groupId;
}
public void setGroupId(int groupId) {
this.groupId = groupId;
}
public int getConfigId() {
return configId;
}
public void setConfigId(int configId) {
this.configId = configId;
}
public int getPoseId() {
return poseId;
}
public void setPoseId(int poseId) {
this.poseId = poseId;
}
@Override
public void onDeath(int killerId) {
if (this.getSpawnEntry() != null) {
this.getScene().getDeadSpawnedEntities().add(getSpawnEntry());
}
}
public void recalcStats() {
......@@ -190,11 +220,11 @@ public class EntityMonster extends GenshinEntity {
SceneMonsterInfo.Builder monsterInfo = SceneMonsterInfo.newBuilder()
.setMonsterId(getMonsterId())
.setGroupId(133003095)
.setConfigId(95001)
.setGroupId(this.getGroupId())
.setConfigId(this.getConfigId())
.addAllAffixList(getMonsterData().getAffix())
.setAuthorityPeerId(getWorld().getHostPeerId())
.setPoseId(0)
.setPoseId(this.getPoseId())
.setBlockId(3001)
.setBornType(MonsterBornType.MonsterBornDefault)
.setSpecialNameId(40);
......
......@@ -4,6 +4,7 @@ import emu.grasscutter.game.GenshinScene;
import emu.grasscutter.game.World;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.LifeState;
import emu.grasscutter.game.world.SpawnDataEntry;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
......@@ -14,6 +15,7 @@ import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
public abstract class GenshinEntity {
protected int id;
private final GenshinScene scene;
private SpawnDataEntry spawnEntry;
private MotionState moveState;
private int lastMoveSceneTimeMs;
......@@ -104,4 +106,12 @@ public abstract class GenshinEntity {
return proto;
}
public SpawnDataEntry getSpawnEntry() {
return spawnEntry;
}
public void setSpawnEntry(SpawnDataEntry spawnEntry) {
this.spawnEntry = spawnEntry;
}
}
package emu.grasscutter.game.world;
import java.util.ArrayList;
import java.util.List;
import emu.grasscutter.utils.Position;
public class SpawnDataEntry {
private transient SpawnGroupEntry group;
private int monsterId;
private int configId;
private int level;
private int poseId;
private Position pos;
private Position rot;
public SpawnGroupEntry getGroup() {
return group;
}
public void setGroup(SpawnGroupEntry group) {
this.group = group;
}
public int getMonsterId() {
return monsterId;
}
public int getConfigId() {
return configId;
}
public int getLevel() {
return level;
}
public int getPoseId() {
return poseId;
}
public Position getPos() {
return pos;
}
public Position getRot() {
return rot;
}
public static class SpawnGroupEntry {
private int sceneId;
private int groupId;
private int blockId;
private Position pos;
private List<SpawnDataEntry> spawns;
public int getSceneId() {
return sceneId;
}
public int getGroupId() {
return groupId;
}
public int getBlockId() {
return blockId;
}
public void setBlockId(int blockId) {
this.blockId = blockId;
}
public Position getPos() {
return pos;
}
public List<SpawnDataEntry> getSpawns() {
return spawns;
}
}
}
package emu.grasscutter.server.packet.send;
import java.util.Collection;
import java.util.List;
import emu.grasscutter.game.entity.GenshinEntity;
import emu.grasscutter.net.packet.GenshinPacket;
import emu.grasscutter.net.packet.PacketOpcodes;
......@@ -18,4 +21,15 @@ public class PacketSceneEntityDisappearNotify extends GenshinPacket {
this.setData(proto);
}
public PacketSceneEntityDisappearNotify(Collection<GenshinEntity> entities, VisionType disappearType) {
super(PacketOpcodes.SceneEntityDisappearNotify);
SceneEntityDisappearNotify.Builder proto = SceneEntityDisappearNotify.newBuilder()
.setDisappearType(disappearType);
entities.forEach(e -> proto.addEntityList(e.getId()));
this.setData(proto);
}
}
......@@ -10,6 +10,7 @@ import emu.grasscutter.Grasscutter;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import org.slf4j.Logger;
@SuppressWarnings({"UnusedReturnValue", "BooleanMethodIsAlwaysInverted"})
......@@ -24,6 +25,19 @@ public final class Utils {
return random.nextFloat() * (max - min) + min;
}
public static double getDist(Position pos1, Position pos2) {
double xs = pos1.getX() - pos2.getX();
xs = xs * xs;
double ys = pos1.getY() - pos2.getY();
ys = ys * ys;
double zs = pos1.getZ() - pos2.getZ();
zs = zs * zs;
return Math.sqrt(xs + zs + ys);
}
public static int getCurrentSeconds() {
return (int) (System.currentTimeMillis() / 1000.0);
}
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment