mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-12-17 09:25:06 +01:00
Implement support for multiple scenes in a world
This commit is contained in:
@@ -33,24 +33,21 @@ import emu.grasscutter.server.packet.send.PacketSyncScenePlayTeamEntityNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketSyncTeamEntityNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketWorldPlayerInfoNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketWorldPlayerRTTNotify;
|
||||
import emu.grasscutter.utils.Position;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
|
||||
public class World implements Iterable<GenshinPlayer> {
|
||||
private final GenshinPlayer owner;
|
||||
private final List<GenshinPlayer> players;
|
||||
private final Int2ObjectMap<GenshinScene> scenes;
|
||||
|
||||
private int levelEntityId;
|
||||
private int nextEntityId = 0;
|
||||
private int nextPeerId = 0;
|
||||
private final Int2ObjectMap<GenshinEntity> entities;
|
||||
|
||||
private int worldLevel;
|
||||
private int sceneId;
|
||||
private int time;
|
||||
private ClimateType climate;
|
||||
|
||||
private boolean isMultiplayer;
|
||||
private boolean isDungeon;
|
||||
|
||||
public World(GenshinPlayer player) {
|
||||
this(player, false);
|
||||
@@ -59,11 +56,9 @@ public class World implements Iterable<GenshinPlayer> {
|
||||
public World(GenshinPlayer player, boolean isMultiplayer) {
|
||||
this.owner = player;
|
||||
this.players = Collections.synchronizedList(new ArrayList<>());
|
||||
this.entities = new Int2ObjectOpenHashMap<>();
|
||||
this.scenes = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
this.levelEntityId = getNextEntityId(EntityIdType.MPLEVEL);
|
||||
this.sceneId = player.getSceneId();
|
||||
this.time = 8 * 60;
|
||||
this.climate = ClimateType.CLIMATE_SUNNY;
|
||||
this.worldLevel = player.getWorldLevel();
|
||||
this.isMultiplayer = isMultiplayer;
|
||||
}
|
||||
@@ -87,22 +82,6 @@ public class World implements Iterable<GenshinPlayer> {
|
||||
return ++this.nextPeerId;
|
||||
}
|
||||
|
||||
public int getSceneId() {
|
||||
return sceneId;
|
||||
}
|
||||
|
||||
public void setSceneId(int sceneId) {
|
||||
this.sceneId = sceneId;
|
||||
}
|
||||
|
||||
public int getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void changeTime(int time) {
|
||||
this.time = time % 1440;
|
||||
}
|
||||
|
||||
public int getWorldLevel() {
|
||||
return worldLevel;
|
||||
}
|
||||
@@ -111,46 +90,30 @@ public class World implements Iterable<GenshinPlayer> {
|
||||
this.worldLevel = worldLevel;
|
||||
}
|
||||
|
||||
public ClimateType getClimate() {
|
||||
return climate;
|
||||
}
|
||||
|
||||
public void setClimate(ClimateType climate) {
|
||||
this.climate = climate;
|
||||
}
|
||||
|
||||
public List<GenshinPlayer> getPlayers() {
|
||||
return players;
|
||||
}
|
||||
|
||||
public Int2ObjectMap<GenshinScene> getScenes() {
|
||||
return this.scenes;
|
||||
}
|
||||
|
||||
public GenshinScene getSceneById(int sceneId) {
|
||||
return getScenes().computeIfAbsent(sceneId, id -> new GenshinScene(this, id));
|
||||
}
|
||||
|
||||
public int getPlayerCount() {
|
||||
return getPlayers().size();
|
||||
}
|
||||
|
||||
public Int2ObjectMap<GenshinEntity> getEntities() {
|
||||
return this.entities;
|
||||
}
|
||||
|
||||
public boolean isInWorld(GenshinEntity entity) {
|
||||
return this.entities.containsKey(entity.getId());
|
||||
}
|
||||
|
||||
public boolean isMultiplayer() {
|
||||
return isMultiplayer;
|
||||
}
|
||||
|
||||
public boolean isDungeon() {
|
||||
return isDungeon;
|
||||
}
|
||||
|
||||
public int getNextEntityId(EntityIdType idType) {
|
||||
return (idType.getId() << 24) + ++this.nextEntityId;
|
||||
}
|
||||
|
||||
public GenshinEntity getEntityById(int id) {
|
||||
return this.entities.get(id);
|
||||
}
|
||||
|
||||
public synchronized void addPlayer(GenshinPlayer player) {
|
||||
// Check if player already in
|
||||
if (getPlayers().contains(player)) {
|
||||
@@ -165,13 +128,20 @@ public class World implements Iterable<GenshinPlayer> {
|
||||
// Register
|
||||
player.setWorld(this);
|
||||
getPlayers().add(player);
|
||||
|
||||
|
||||
// Set player variables
|
||||
player.setPeerId(this.getNextPeerId());
|
||||
player.getTeamManager().setEntityId(getNextEntityId(EntityIdType.TEAM));
|
||||
|
||||
// Setup team avatars
|
||||
this.setupPlayerAvatars(player);
|
||||
// Copy main team to mp team
|
||||
if (this.isMultiplayer()) {
|
||||
player.getTeamManager().getMpTeam().copyFrom(player.getTeamManager().getCurrentSinglePlayerTeamInfo(), player.getTeamManager().getMaxTeamSize());
|
||||
}
|
||||
|
||||
// Add to scene
|
||||
GenshinScene scene = this.getSceneById(player.getSceneId());
|
||||
scene.addPlayer(player);
|
||||
|
||||
// Info packet for other players
|
||||
if (this.getPlayers().size() > 1) {
|
||||
this.updatePlayerInfos(player);
|
||||
@@ -191,18 +161,15 @@ public class World implements Iterable<GenshinPlayer> {
|
||||
getPlayers().remove(player);
|
||||
player.setWorld(null);
|
||||
|
||||
this.removePlayerAvatars(player);
|
||||
|
||||
// Remove from scene
|
||||
GenshinScene scene = this.getSceneById(player.getSceneId());
|
||||
scene.removePlayer(player);
|
||||
|
||||
// Info packet for other players
|
||||
if (this.getPlayers().size() > 0) {
|
||||
this.updatePlayerInfos(player);
|
||||
}
|
||||
|
||||
// Remove player gadgets
|
||||
for (EntityGadget gadget : player.getTeamManager().getGadgets()) {
|
||||
this.removeEntity(gadget);
|
||||
}
|
||||
|
||||
|
||||
// Disband world if host leaves
|
||||
if (getHost() == player) {
|
||||
List<GenshinPlayer> kicked = new ArrayList<>(this.getPlayers());
|
||||
@@ -210,11 +177,24 @@ public class World implements Iterable<GenshinPlayer> {
|
||||
World world = new World(victim);
|
||||
world.addPlayer(victim);
|
||||
|
||||
victim.sendPacket(new PacketPlayerEnterSceneNotify(victim, EnterType.EnterSelf, EnterReason.TeamKick, victim.getWorld().getSceneId(), victim.getPos()));
|
||||
victim.sendPacket(new PacketPlayerEnterSceneNotify(victim, EnterType.EnterSelf, EnterReason.TeamKick, victim.getSceneId(), victim.getPos()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void transferPlayerToScene(GenshinPlayer player, int sceneId, Position pos) {
|
||||
if (player.getScene() != null) {
|
||||
player.getScene().removePlayer(player);
|
||||
}
|
||||
|
||||
GenshinScene scene = this.getSceneById(sceneId);
|
||||
scene.addPlayer(player);
|
||||
|
||||
player.getPos().set(pos);
|
||||
|
||||
player.sendPacket(new PacketPlayerEnterSceneNotify(player, EnterType.EnterSelf, EnterReason.TransPoint, sceneId, pos));
|
||||
}
|
||||
|
||||
private void updatePlayerInfos(GenshinPlayer paramPlayer) {
|
||||
for (GenshinPlayer player : getPlayers()) {
|
||||
// Dont send packets if player is loading in and filter out joining player
|
||||
@@ -239,185 +219,6 @@ public class World implements Iterable<GenshinPlayer> {
|
||||
}
|
||||
}
|
||||
|
||||
private void addEntityDirectly(GenshinEntity entity) {
|
||||
getEntities().put(entity.getId(), entity);
|
||||
}
|
||||
|
||||
public synchronized void addEntity(GenshinEntity entity) {
|
||||
this.addEntityDirectly(entity);
|
||||
this.broadcastPacket(new PacketSceneEntityAppearNotify(entity));
|
||||
}
|
||||
|
||||
public synchronized void addEntities(Collection<GenshinEntity> entities) {
|
||||
for (GenshinEntity entity : entities) {
|
||||
this.addEntityDirectly(entity);
|
||||
}
|
||||
|
||||
this.broadcastPacket(new PacketSceneEntityAppearNotify(entities, VisionType.VisionBorn));
|
||||
}
|
||||
|
||||
private GenshinEntity removeEntityDirectly(GenshinEntity entity) {
|
||||
return getEntities().remove(entity.getId());
|
||||
}
|
||||
|
||||
public void removeEntity(GenshinEntity entity) {
|
||||
this.removeEntity(entity, VisionType.VisionDie);
|
||||
}
|
||||
|
||||
public synchronized void removeEntity(GenshinEntity entity, VisionType visionType) {
|
||||
GenshinEntity removed = this.removeEntityDirectly(entity);
|
||||
if (removed != null) {
|
||||
this.broadcastPacket(new PacketSceneEntityDisappearNotify(removed, visionType));
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void replaceEntity(EntityAvatar oldEntity, EntityAvatar newEntity) {
|
||||
this.removeEntityDirectly(oldEntity);
|
||||
this.addEntityDirectly(newEntity);
|
||||
this.broadcastPacket(new PacketSceneEntityDisappearNotify(oldEntity, VisionType.VisionReplace));
|
||||
this.broadcastPacket(new PacketSceneEntityAppearNotify(newEntity, VisionType.VisionReplace, oldEntity.getId()));
|
||||
}
|
||||
|
||||
private void setupPlayerAvatars(GenshinPlayer player) {
|
||||
// Copy main team to mp team
|
||||
if (this.isMultiplayer()) {
|
||||
player.getTeamManager().getMpTeam().copyFrom(player.getTeamManager().getCurrentSinglePlayerTeamInfo(), player.getTeamManager().getMaxTeamSize());
|
||||
}
|
||||
|
||||
// Clear entities from old team
|
||||
player.getTeamManager().getActiveTeam().clear();
|
||||
|
||||
// Add new entities for player
|
||||
TeamInfo teamInfo = player.getTeamManager().getCurrentTeamInfo();
|
||||
for (int avatarId : teamInfo.getAvatars()) {
|
||||
EntityAvatar entity = new EntityAvatar(this, player.getAvatars().getAvatarById(avatarId));
|
||||
player.getTeamManager().getActiveTeam().add(entity);
|
||||
}
|
||||
|
||||
// Limit character index in case its out of bounds
|
||||
if (player.getTeamManager().getCurrentCharacterIndex() >= player.getTeamManager().getActiveTeam().size() || player.getTeamManager().getCurrentCharacterIndex() < 0) {
|
||||
player.getTeamManager().setCurrentCharacterIndex(player.getTeamManager().getCurrentCharacterIndex() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void removePlayerAvatars(GenshinPlayer player) {
|
||||
Iterator<EntityAvatar> it = player.getTeamManager().getActiveTeam().iterator();
|
||||
while (it.hasNext()) {
|
||||
this.removeEntity(it.next(), VisionType.VisionRemove);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
public void spawnPlayer(GenshinPlayer player) {
|
||||
if (isInWorld(player.getTeamManager().getCurrentAvatarEntity())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.getTeamManager().getCurrentAvatarEntity().getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) <= 0f) {
|
||||
player.getTeamManager().getCurrentAvatarEntity().setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 1f);
|
||||
}
|
||||
|
||||
this.addEntity(player.getTeamManager().getCurrentAvatarEntity());
|
||||
}
|
||||
|
||||
public void showOtherEntities(GenshinPlayer player) {
|
||||
List<GenshinEntity> entities = new LinkedList<>();
|
||||
GenshinEntity currentEntity = player.getTeamManager().getCurrentAvatarEntity();
|
||||
|
||||
for (GenshinEntity entity : this.getEntities().values()) {
|
||||
if (entity == currentEntity) {
|
||||
continue;
|
||||
}
|
||||
entities.add(entity);
|
||||
}
|
||||
|
||||
player.sendPacket(new PacketSceneEntityAppearNotify(entities, VisionType.VisionMeet));
|
||||
}
|
||||
|
||||
public void handleAttack(AttackResult result) {
|
||||
//GenshinEntity attacker = getEntityById(result.getAttackerId());
|
||||
GenshinEntity target = getEntityById(result.getDefenseId());
|
||||
|
||||
if (target == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Godmode check
|
||||
if (target instanceof EntityAvatar) {
|
||||
if (((EntityAvatar) target).getPlayer().hasGodmode()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Lose hp
|
||||
target.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, -result.getDamage());
|
||||
|
||||
// Check if dead
|
||||
boolean isDead = false;
|
||||
if (target.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) <= 0f) {
|
||||
target.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0f);
|
||||
isDead = true;
|
||||
}
|
||||
|
||||
// Packets
|
||||
this.broadcastPacket(new PacketEntityFightPropUpdateNotify(target, FightProperty.FIGHT_PROP_CUR_HP));
|
||||
|
||||
// Check if dead
|
||||
if (isDead) {
|
||||
this.killEntity(target, result.getAttackerId());
|
||||
}
|
||||
}
|
||||
|
||||
public void killEntity(GenshinEntity target, int attackerId) {
|
||||
// Packet
|
||||
this.broadcastPacket(new PacketLifeStateChangeNotify(attackerId, target, LifeState.LIFE_DEAD));
|
||||
this.removeEntity(target);
|
||||
|
||||
// Death event
|
||||
target.onDeath(attackerId);
|
||||
}
|
||||
|
||||
// Gadgets
|
||||
|
||||
public void onPlayerCreateGadget(EntityClientGadget gadget) {
|
||||
// Directly add
|
||||
this.addEntityDirectly(gadget);
|
||||
|
||||
// Add to owner's gadget list
|
||||
gadget.getOwner().getTeamManager().getGadgets().add(gadget);
|
||||
|
||||
// Optimization
|
||||
if (this.getPlayerCount() == 1 && this.getPlayers().get(0) == gadget.getOwner()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.broadcastPacketToOthers(gadget.getOwner(), new PacketSceneEntityAppearNotify(gadget));
|
||||
}
|
||||
|
||||
public void onPlayerDestroyGadget(int entityId) {
|
||||
GenshinEntity entity = getEntities().get(entityId);
|
||||
|
||||
if (entity == null || !(entity instanceof EntityClientGadget)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get and remove entity
|
||||
EntityClientGadget gadget = (EntityClientGadget) entity;
|
||||
this.removeEntityDirectly(gadget);
|
||||
|
||||
// Remove from owner's gadget list
|
||||
gadget.getOwner().getTeamManager().getGadgets().remove(gadget);
|
||||
|
||||
// Optimization
|
||||
if (this.getPlayerCount() == 1 && this.getPlayers().get(0) == gadget.getOwner()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.broadcastPacketToOthers(gadget.getOwner(), new PacketSceneEntityDisappearNotify(gadget, VisionType.VisionDie));
|
||||
}
|
||||
|
||||
// Broadcasting
|
||||
|
||||
public void broadcastPacket(GenshinPacket packet) {
|
||||
// Send to all players - might have to check if player has been sent data packets
|
||||
for (GenshinPlayer player : this.getPlayers()) {
|
||||
@@ -425,21 +226,6 @@ public class World implements Iterable<GenshinPlayer> {
|
||||
}
|
||||
}
|
||||
|
||||
public void broadcastPacketToOthers(GenshinPlayer excludedPlayer, GenshinPacket packet) {
|
||||
// Optimization
|
||||
if (this.getPlayerCount() == 1 && this.getPlayers().get(0) == excludedPlayer) {
|
||||
return;
|
||||
}
|
||||
// Send to all players - might have to check if player has been sent data packets
|
||||
for (GenshinPlayer player : this.getPlayers()) {
|
||||
if (player == excludedPlayer) {
|
||||
continue;
|
||||
}
|
||||
// Send
|
||||
player.getSession().send(packet);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user