mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-12-23 04:15:49 +01:00
Copy some files from Grasscutter-Quests
NOT completely finished, nor is it completely done. Protocol issues remain! (including lack of packet IDs)
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
package emu.grasscutter.game.world;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class GroupReplacementData {
|
||||
int id;
|
||||
List<Integer> replace_groups;
|
||||
}
|
||||
package emu.grasscutter.game.world;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class GroupReplacementData {
|
||||
int id;
|
||||
List<Integer> replace_groups;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,16 @@ import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.data.GameDepot;
|
||||
import emu.grasscutter.data.binout.SceneNpcBornEntry;
|
||||
import emu.grasscutter.data.binout.routes.Route;
|
||||
import emu.grasscutter.data.excels.*;
|
||||
import emu.grasscutter.data.excels.codex.CodexAnimalData;
|
||||
import emu.grasscutter.data.excels.monster.MonsterData;
|
||||
import emu.grasscutter.data.excels.world.WorldLevelData;
|
||||
import emu.grasscutter.game.avatar.Avatar;
|
||||
import emu.grasscutter.game.dungeons.DungeonManager;
|
||||
import emu.grasscutter.game.dungeons.DungeonSettleListener;
|
||||
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
|
||||
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
|
||||
import emu.grasscutter.game.entity.*;
|
||||
import emu.grasscutter.game.entity.gadget.GadgetWorktop;
|
||||
import emu.grasscutter.game.managers.blossom.BlossomManager;
|
||||
@@ -28,6 +34,7 @@ import emu.grasscutter.scripts.data.SceneGadget;
|
||||
import emu.grasscutter.scripts.data.SceneGroup;
|
||||
import emu.grasscutter.server.packet.send.*;
|
||||
import emu.grasscutter.utils.Position;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
@@ -43,21 +50,25 @@ public class Scene {
|
||||
@Getter private final Set<SpawnDataEntry> spawnedEntities;
|
||||
@Getter private final Set<SpawnDataEntry> deadSpawnedEntities;
|
||||
@Getter private final Set<SceneBlock> loadedBlocks;
|
||||
@Getter private final Set<SceneGroup> loadedGroups;
|
||||
@Getter private final BlossomManager blossomManager;
|
||||
private final HashSet<Integer> unlockedForces;
|
||||
private final List<Runnable> afterLoadedCallbacks = new ArrayList<>();
|
||||
private final long startWorldTime;
|
||||
@Getter @Setter DungeonManager dungeonManager;
|
||||
@Getter Int2ObjectMap<Route> sceneRoutes;
|
||||
private Set<SpawnDataEntry.GridBlockId> loadedGridBlocks;
|
||||
@Getter @Setter private boolean dontDestroyWhenEmpty;
|
||||
|
||||
@Getter @Setter private int autoCloseTime;
|
||||
@Getter @Setter private int time;
|
||||
private final long startTime;
|
||||
|
||||
@Getter private final SceneScriptManager scriptManager;
|
||||
@Getter @Setter private WorldChallenge challenge;
|
||||
@Getter private List<DungeonSettleListener> dungeonSettleListeners;
|
||||
@Getter private DungeonData dungeonData;
|
||||
@Getter @Setter private int prevScene; // Id of the previous scene
|
||||
@Getter @Setter private int prevScenePoint;
|
||||
@Getter @Setter private int killedMonsterCount;
|
||||
private Set<SceneNpcBornEntry> npcBornEntrySet;
|
||||
@Getter private boolean finishedLoading = false;
|
||||
@Getter private int tickCount = 0;
|
||||
@Getter private boolean isPaused = false;
|
||||
|
||||
public Scene(World world, SceneData sceneData) {
|
||||
this.world = world;
|
||||
@@ -65,17 +76,20 @@ public class Scene {
|
||||
this.players = new CopyOnWriteArrayList<>();
|
||||
this.entities = new ConcurrentHashMap<>();
|
||||
|
||||
this.time = 8 * 60;
|
||||
this.startTime = System.currentTimeMillis();
|
||||
this.prevScene = 3;
|
||||
this.sceneRoutes = GameData.getSceneRoutes(getId());
|
||||
|
||||
this.startWorldTime = world.getWorldTime();
|
||||
|
||||
this.spawnedEntities = ConcurrentHashMap.newKeySet();
|
||||
this.deadSpawnedEntities = ConcurrentHashMap.newKeySet();
|
||||
this.loadedBlocks = ConcurrentHashMap.newKeySet();
|
||||
this.loadedGroups = ConcurrentHashMap.newKeySet();
|
||||
this.loadedGridBlocks = new HashSet<>();
|
||||
this.npcBornEntrySet = ConcurrentHashMap.newKeySet();
|
||||
this.scriptManager = new SceneScriptManager(this);
|
||||
this.blossomManager = new BlossomManager(this);
|
||||
this.unlockedForces = new HashSet<>();
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
@@ -101,31 +115,55 @@ public class Scene {
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public void changeTime(int time) {
|
||||
this.time = time % 1440;
|
||||
}
|
||||
|
||||
public int getSceneTime() {
|
||||
return (int) (System.currentTimeMillis() - this.startTime);
|
||||
}
|
||||
|
||||
public void setDungeonData(DungeonData dungeonData) {
|
||||
if (dungeonData == null
|
||||
|| this.dungeonData != null
|
||||
|| this.getSceneType() != SceneType.SCENE_DUNGEON
|
||||
|| dungeonData.getSceneId() != this.getId()) {
|
||||
return;
|
||||
/**
|
||||
* Sets the scene's pause state. Sends the current scene's time to all players.
|
||||
*
|
||||
* @param paused The new pause state.
|
||||
*/
|
||||
public void setPaused(boolean paused) {
|
||||
if (this.isPaused != paused) {
|
||||
this.isPaused = paused;
|
||||
this.broadcastPacket(new PacketSceneTimeNotify(this));
|
||||
}
|
||||
this.dungeonData = dungeonData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the time in seconds since the scene started.
|
||||
*
|
||||
* @return The time in seconds since the scene started.
|
||||
*/
|
||||
public int getSceneTime() {
|
||||
return (int) (this.getWorld().getWorldTime() - this.startWorldTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets {@link Scene#getSceneTime()} in seconds.
|
||||
*
|
||||
* @return The time in seconds since the scene started.
|
||||
*/
|
||||
public int getSceneTimeSeconds() {
|
||||
return this.getSceneTime() / 1000;
|
||||
}
|
||||
|
||||
public void addDungeonSettleObserver(DungeonSettleListener dungeonSettleListener) {
|
||||
if (dungeonSettleListeners == null) {
|
||||
dungeonSettleListeners = new ArrayList<>();
|
||||
}
|
||||
|
||||
dungeonSettleListeners.add(dungeonSettleListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers an event in the dungeon manager.
|
||||
*
|
||||
* @param conditionType The condition type to trigger.
|
||||
* @param params The parameters to pass to the event.
|
||||
*/
|
||||
public void triggerDungeonEvent(DungeonPassConditionType conditionType, int... params) {
|
||||
if (this.dungeonManager == null) return;
|
||||
this.dungeonManager.triggerEvent(conditionType, params);
|
||||
}
|
||||
|
||||
public boolean isInScene(GameEntity entity) {
|
||||
return this.entities.containsKey(entity.getId());
|
||||
}
|
||||
|
||||
@@ -1,86 +1,85 @@
|
||||
package emu.grasscutter.game.world;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.bson.types.ObjectId;
|
||||
|
||||
import dev.morphia.annotations.Entity;
|
||||
import dev.morphia.annotations.Id;
|
||||
import dev.morphia.annotations.Indexed;
|
||||
import emu.grasscutter.database.DatabaseHelper;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.scripts.data.SceneGadget;
|
||||
import emu.grasscutter.scripts.data.SceneGroup;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity(value = "group_instances", useDiscriminator = false)
|
||||
public class SceneGroupInstance {
|
||||
@Id private ObjectId id;
|
||||
|
||||
@Indexed private int ownerUid; //This group is owned by the host player
|
||||
@Getter private int groupId;
|
||||
|
||||
@Getter private transient SceneGroup luaGroup;
|
||||
@Getter @Setter private int targetSuiteId;
|
||||
@Getter @Setter private int activeSuiteId;
|
||||
@Getter private Set<Integer> deadEntities; //Config_ids
|
||||
private boolean isCached;
|
||||
|
||||
@Getter private Map<Integer, Integer> cachedGadgetStates;
|
||||
@Getter private Map<String, Integer> cachedVariables;
|
||||
|
||||
@Getter @Setter private int lastTimeRefreshed;
|
||||
|
||||
public SceneGroupInstance(SceneGroup group, Player owner) {
|
||||
this.luaGroup = group;
|
||||
this.groupId = group.id;
|
||||
this.targetSuiteId = 0;
|
||||
this.activeSuiteId = 0;
|
||||
this.lastTimeRefreshed = 0;
|
||||
this.ownerUid = owner.getUid();
|
||||
this.deadEntities = new HashSet<>();
|
||||
this.cachedGadgetStates = new ConcurrentHashMap<>();
|
||||
this.cachedVariables = new ConcurrentHashMap<>();
|
||||
|
||||
this.isCached = false; //This is true when the group is not loaded on scene but caches suite data
|
||||
}
|
||||
|
||||
@Deprecated // Morphia only!
|
||||
SceneGroupInstance(){
|
||||
this.cachedVariables = new ConcurrentHashMap<>();
|
||||
this.deadEntities = new HashSet<>();
|
||||
this.cachedGadgetStates = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
public void setLuaGroup(SceneGroup group) {
|
||||
this.luaGroup = group;
|
||||
this.groupId = group.id;
|
||||
}
|
||||
|
||||
public boolean isCached() {
|
||||
return this.isCached;
|
||||
}
|
||||
|
||||
public void setCached(boolean value) {
|
||||
this.isCached = value;
|
||||
save(); //Save each time a group is registered or unregistered
|
||||
}
|
||||
|
||||
public void cacheGadgetState(SceneGadget g, int state) {
|
||||
if(g.persistent) //Only cache when is persistent
|
||||
cachedGadgetStates.put(g.config_id, state);
|
||||
}
|
||||
|
||||
public int getCachedGadgetState(SceneGadget g) {
|
||||
Integer state = cachedGadgetStates.getOrDefault(g.config_id, null);
|
||||
return (state == null) ? g.state : state;
|
||||
}
|
||||
|
||||
public void save() {
|
||||
DatabaseHelper.saveGroupInstance(this);
|
||||
}
|
||||
}
|
||||
package emu.grasscutter.game.world;
|
||||
|
||||
import dev.morphia.annotations.Entity;
|
||||
import dev.morphia.annotations.Id;
|
||||
import dev.morphia.annotations.Indexed;
|
||||
import emu.grasscutter.database.DatabaseHelper;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.scripts.data.SceneGadget;
|
||||
import emu.grasscutter.scripts.data.SceneGroup;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.bson.types.ObjectId;
|
||||
|
||||
@Entity(value = "group_instances", useDiscriminator = false)
|
||||
public class SceneGroupInstance {
|
||||
@Id private ObjectId id;
|
||||
|
||||
@Indexed private int ownerUid; // This group is owned by the host player
|
||||
@Getter private int groupId;
|
||||
|
||||
@Getter private transient SceneGroup luaGroup;
|
||||
@Getter @Setter private int targetSuiteId;
|
||||
@Getter @Setter private int activeSuiteId;
|
||||
@Getter private Set<Integer> deadEntities; // Config_ids
|
||||
private boolean isCached;
|
||||
|
||||
@Getter private Map<Integer, Integer> cachedGadgetStates;
|
||||
@Getter private Map<String, Integer> cachedVariables;
|
||||
|
||||
@Getter @Setter private int lastTimeRefreshed;
|
||||
|
||||
public SceneGroupInstance(SceneGroup group, Player owner) {
|
||||
this.luaGroup = group;
|
||||
this.groupId = group.id;
|
||||
this.targetSuiteId = 0;
|
||||
this.activeSuiteId = 0;
|
||||
this.lastTimeRefreshed = 0;
|
||||
this.ownerUid = owner.getUid();
|
||||
this.deadEntities = new HashSet<>();
|
||||
this.cachedGadgetStates = new ConcurrentHashMap<>();
|
||||
this.cachedVariables = new ConcurrentHashMap<>();
|
||||
|
||||
this.isCached =
|
||||
false; // This is true when the group is not loaded on scene but caches suite data
|
||||
}
|
||||
|
||||
@Deprecated // Morphia only!
|
||||
SceneGroupInstance() {
|
||||
this.cachedVariables = new ConcurrentHashMap<>();
|
||||
this.deadEntities = new HashSet<>();
|
||||
this.cachedGadgetStates = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
public void setLuaGroup(SceneGroup group) {
|
||||
this.luaGroup = group;
|
||||
this.groupId = group.id;
|
||||
}
|
||||
|
||||
public boolean isCached() {
|
||||
return this.isCached;
|
||||
}
|
||||
|
||||
public void setCached(boolean value) {
|
||||
this.isCached = value;
|
||||
save(); // Save each time a group is registered or unregistered
|
||||
}
|
||||
|
||||
public void cacheGadgetState(SceneGadget g, int state) {
|
||||
if (g.persistent) // Only cache when is persistent
|
||||
cachedGadgetStates.put(g.config_id, state);
|
||||
}
|
||||
|
||||
public int getCachedGadgetState(SceneGadget g) {
|
||||
Integer state = cachedGadgetStates.getOrDefault(g.config_id, null);
|
||||
return (state == null) ? g.state : state;
|
||||
}
|
||||
|
||||
public void save() {
|
||||
DatabaseHelper.saveGroupInstance(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
package emu.grasscutter.game.world;
|
||||
|
||||
import static emu.grasscutter.server.event.player.PlayerTeleportEvent.TeleportType.SCRIPT;
|
||||
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.data.excels.DungeonData;
|
||||
import emu.grasscutter.data.excels.SceneData;
|
||||
import emu.grasscutter.data.excels.dungeon.DungeonData;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.game.player.Player.SceneLoadState;
|
||||
import emu.grasscutter.game.props.EnterReason;
|
||||
import emu.grasscutter.game.props.EntityIdType;
|
||||
import emu.grasscutter.game.props.SceneType;
|
||||
import emu.grasscutter.game.quest.enums.QuestContent;
|
||||
import emu.grasscutter.game.world.data.TeleportProperties;
|
||||
import emu.grasscutter.net.packet.BasePacket;
|
||||
import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType;
|
||||
import emu.grasscutter.scripts.data.SceneConfig;
|
||||
@@ -15,6 +18,7 @@ import emu.grasscutter.server.event.player.PlayerTeleportEvent;
|
||||
import emu.grasscutter.server.event.player.PlayerTeleportEvent.TeleportType;
|
||||
import emu.grasscutter.server.game.GameServer;
|
||||
import emu.grasscutter.server.packet.send.*;
|
||||
import emu.grasscutter.utils.ConversionUtils;
|
||||
import emu.grasscutter.utils.Position;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
|
||||
@@ -24,26 +28,33 @@ import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.Getter;
|
||||
import lombok.val;
|
||||
|
||||
public class World implements Iterable<Player> {
|
||||
private final GameServer server;
|
||||
private final Player owner;
|
||||
private final List<Player> players;
|
||||
private final Int2ObjectMap<Scene> scenes;
|
||||
@Getter private final GameServer server;
|
||||
@Getter private final Player host;
|
||||
@Getter private final List<Player> players;
|
||||
@Getter private final Int2ObjectMap<Scene> scenes;
|
||||
|
||||
private final int levelEntityId;
|
||||
@Getter private final int levelEntityId;
|
||||
private int nextEntityId = 0;
|
||||
private int nextPeerId = 0;
|
||||
private int worldLevel;
|
||||
|
||||
private final boolean isMultiplayer;
|
||||
|
||||
private long lastUpdateTime;
|
||||
@Getter private int tickCount = 0;
|
||||
@Getter private boolean isPaused = false;
|
||||
@Getter private long currentWorldTime = 0;
|
||||
|
||||
public World(Player player) {
|
||||
this(player, false);
|
||||
}
|
||||
|
||||
public World(Player player, boolean isMultiplayer) {
|
||||
this.owner = player;
|
||||
this.host = player;
|
||||
this.server = player.getServer();
|
||||
this.players = Collections.synchronizedList(new ArrayList<>());
|
||||
this.scenes = Int2ObjectMaps.synchronize(new Int2ObjectOpenHashMap<>());
|
||||
@@ -52,26 +63,19 @@ public class World implements Iterable<Player> {
|
||||
this.worldLevel = player.getWorldLevel();
|
||||
this.isMultiplayer = isMultiplayer;
|
||||
|
||||
this.owner.getServer().registerWorld(this);
|
||||
}
|
||||
|
||||
public Player getHost() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public GameServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public int getLevelEntityId() {
|
||||
return levelEntityId;
|
||||
this.lastUpdateTime = System.currentTimeMillis();
|
||||
this.currentWorldTime = host.getPlayerGameTime();
|
||||
|
||||
this.host.getServer().registerWorld(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the peer ID of the world's host.
|
||||
*
|
||||
* @return The peer ID of the world's host. 0 if the host is null.
|
||||
*/
|
||||
public int getHostPeerId() {
|
||||
if (this.getHost() == null) {
|
||||
return 0;
|
||||
}
|
||||
return this.getHost().getPeerId();
|
||||
return this.getHost() == null ? 0 : this.getHost().getPeerId();
|
||||
}
|
||||
|
||||
public int getNextPeerId() {
|
||||
@@ -86,23 +90,21 @@ public class World implements Iterable<Player> {
|
||||
this.worldLevel = worldLevel;
|
||||
}
|
||||
|
||||
public List<Player> getPlayers() {
|
||||
return players;
|
||||
}
|
||||
|
||||
public Int2ObjectMap<Scene> getScenes() {
|
||||
return this.scenes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an associated scene by ID. Creates a new instance of the scene if it doesn't exist.
|
||||
*
|
||||
* @param sceneId The scene ID.
|
||||
* @return The scene.
|
||||
*/
|
||||
public Scene getSceneById(int sceneId) {
|
||||
// Get scene normally
|
||||
Scene scene = this.getScenes().get(sceneId);
|
||||
var scene = this.getScenes().get(sceneId);
|
||||
if (scene != null) {
|
||||
return scene;
|
||||
}
|
||||
|
||||
// Create scene from scene data if it doesnt exist
|
||||
SceneData sceneData = GameData.getSceneDataMap().get(sceneId);
|
||||
// Create scene from scene data if it doesn't exist
|
||||
var sceneData = GameData.getSceneDataMap().get(sceneId);
|
||||
if (sceneData != null) {
|
||||
scene = new Scene(this, sceneData);
|
||||
this.registerScene(scene);
|
||||
@@ -120,6 +122,12 @@ public class World implements Iterable<Player> {
|
||||
return isMultiplayer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next entity ID for the specified entity type.
|
||||
*
|
||||
* @param idType The entity type.
|
||||
* @return The next entity ID.
|
||||
*/
|
||||
public int getNextEntityId(EntityIdType idType) {
|
||||
return (idType.getId() << 24) + ++this.nextEntityId;
|
||||
}
|
||||
@@ -231,80 +239,112 @@ public class World implements Iterable<Player> {
|
||||
TeleportType teleportType,
|
||||
DungeonData dungeonData,
|
||||
Position teleportTo) {
|
||||
EnterReason enterReason =
|
||||
switch (teleportType) {
|
||||
// shouldn't affect the teleportation, but its clearer when inspecting the packets
|
||||
// TODO add more conditions for different reason.
|
||||
case INTERNAL -> EnterReason.TransPoint;
|
||||
case WAYPOINT -> EnterReason.TransPoint;
|
||||
case MAP -> EnterReason.TransPoint;
|
||||
case COMMAND -> EnterReason.Gm;
|
||||
case SCRIPT -> EnterReason.Lua;
|
||||
case CLIENT -> EnterReason.ClientTransmit;
|
||||
case DUNGEON -> EnterReason.DungeonEnter;
|
||||
default -> EnterReason.None;
|
||||
};
|
||||
return transferPlayerToScene(
|
||||
player, sceneId, teleportType, enterReason, dungeonData, teleportTo);
|
||||
}
|
||||
|
||||
public boolean transferPlayerToScene(
|
||||
Player player,
|
||||
int sceneId,
|
||||
TeleportType teleportType,
|
||||
EnterReason enterReason,
|
||||
DungeonData dungeonData,
|
||||
Position teleportTo) {
|
||||
// Get enter types
|
||||
val teleportProps =
|
||||
TeleportProperties.builder()
|
||||
.sceneId(sceneId)
|
||||
.teleportType(teleportType)
|
||||
.enterReason(enterReason)
|
||||
.teleportTo(teleportTo)
|
||||
.enterType(EnterType.ENTER_TYPE_JUMP);
|
||||
|
||||
val sceneData = GameData.getSceneDataMap().get(sceneId);
|
||||
if (dungeonData != null) {
|
||||
teleportProps.enterType(EnterType.ENTER_TYPE_DUNGEON).enterReason(EnterReason.DungeonEnter);
|
||||
} else if (player.getSceneId() == sceneId) {
|
||||
teleportProps.enterType(EnterType.ENTER_TYPE_GOTO);
|
||||
} else if (sceneData != null && sceneData.getSceneType() == SceneType.SCENE_HOME_WORLD) {
|
||||
// Home
|
||||
teleportProps.enterType(EnterType.ENTER_TYPE_SELF_HOME).enterReason(EnterReason.EnterHome);
|
||||
}
|
||||
return transferPlayerToScene(player, teleportProps.build());
|
||||
}
|
||||
|
||||
public boolean transferPlayerToScene(Player player, TeleportProperties teleportProperties) {
|
||||
// Call player teleport event.
|
||||
PlayerTeleportEvent event =
|
||||
new PlayerTeleportEvent(player, teleportType, player.getPosition(), teleportTo);
|
||||
new PlayerTeleportEvent(player, teleportProperties, player.getPosition());
|
||||
// Call event & check if it was canceled.
|
||||
event.call();
|
||||
if (event.isCanceled()) {
|
||||
return false; // Teleport was canceled.
|
||||
}
|
||||
|
||||
// Set the destination.
|
||||
teleportTo = event.getDestination();
|
||||
|
||||
if (GameData.getSceneDataMap().get(sceneId) == null) {
|
||||
if (GameData.getSceneDataMap().get(teleportProperties.getSceneId()) == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Scene oldScene = null;
|
||||
|
||||
if (player.getScene() != null) {
|
||||
oldScene = player.getScene();
|
||||
|
||||
// Don't deregister scenes if the player is going to tp back into them
|
||||
if (oldScene.getId() == sceneId) {
|
||||
if (oldScene.getId() == teleportProperties.getSceneId()) {
|
||||
oldScene.setDontDestroyWhenEmpty(true);
|
||||
}
|
||||
|
||||
oldScene.removePlayer(player);
|
||||
}
|
||||
|
||||
Scene newScene = this.getSceneById(sceneId);
|
||||
newScene.setDungeonData(dungeonData);
|
||||
var newScene = this.getSceneById(teleportProperties.getSceneId());
|
||||
newScene.addPlayer(player);
|
||||
player.getTeamManager().applyAbilities(newScene);
|
||||
|
||||
// Dungeon
|
||||
SceneConfig config = newScene.getScriptManager().getConfig();
|
||||
if (teleportTo == null && config != null) {
|
||||
if (teleportProperties.getTeleportTo() == null && config != null) {
|
||||
if (config.born_pos != null) {
|
||||
teleportTo = newScene.getScriptManager().getConfig().born_pos;
|
||||
teleportProperties.setTeleportTo(newScene.getScriptManager().getConfig().born_pos);
|
||||
}
|
||||
if (config.born_rot != null) {
|
||||
player.getRotation().set(config.born_rot);
|
||||
teleportProperties.setTeleportRot(config.born_rot);
|
||||
}
|
||||
}
|
||||
|
||||
// Set player position
|
||||
if (teleportTo == null) {
|
||||
teleportTo = player.getPosition();
|
||||
// Set player position and rotation
|
||||
if (teleportProperties.getTeleportTo() != null) {
|
||||
player.getPosition().set(teleportProperties.getTeleportTo());
|
||||
}
|
||||
if (teleportProperties.getTeleportRot() != null) {
|
||||
player.getRotation().set(teleportProperties.getTeleportRot());
|
||||
}
|
||||
|
||||
player.getPosition().set(teleportTo);
|
||||
|
||||
if (oldScene != null) {
|
||||
if (oldScene != null && newScene != oldScene) {
|
||||
newScene.setPrevScene(oldScene.getId());
|
||||
oldScene.setDontDestroyWhenEmpty(false);
|
||||
}
|
||||
|
||||
// Get enter types
|
||||
EnterType enterType = EnterType.ENTER_TYPE_JUMP;
|
||||
EnterReason enterReason = EnterReason.TransPoint;
|
||||
// Teleport packet
|
||||
player.sendPacket(new PacketPlayerEnterSceneNotify(player, teleportProperties));
|
||||
|
||||
if (dungeonData != null) {
|
||||
enterType = EnterType.ENTER_TYPE_DUNGEON;
|
||||
enterReason = EnterReason.DungeonEnter;
|
||||
} else if (oldScene == newScene) {
|
||||
enterType = EnterType.ENTER_TYPE_GOTO;
|
||||
} else if (newScene.getSceneType() == SceneType.SCENE_HOME_WORLD) {
|
||||
// Home
|
||||
enterReason = EnterReason.EnterHome;
|
||||
enterType = EnterType.ENTER_TYPE_SELF_HOME;
|
||||
if (teleportProperties.getTeleportType() != TeleportType.INTERNAL
|
||||
&& teleportProperties.getTeleportType() != SCRIPT) {
|
||||
player.getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_ANY_MANUAL_TRANSPORT);
|
||||
}
|
||||
|
||||
// Teleport packet
|
||||
player.sendPacket(
|
||||
new PacketPlayerEnterSceneNotify(player, enterType, enterReason, sceneId, teleportTo));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -347,15 +387,114 @@ public class World implements Iterable<Player> {
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if the world should be deleted
|
||||
/**
|
||||
* Invoked every game tick.
|
||||
*
|
||||
* @return True if the world should be removed.
|
||||
*/
|
||||
public boolean onTick() {
|
||||
// Check if there are players in this world.
|
||||
if (this.getPlayerCount() == 0) return true;
|
||||
this.scenes.forEach((k, scene) -> scene.onTick());
|
||||
// Tick all associated scenes.
|
||||
this.getScenes().forEach((k, scene) -> scene.onTick());
|
||||
|
||||
// sync time every 10 seconds
|
||||
if (this.tickCount % 10 == 0) {
|
||||
this.getPlayers().forEach(p -> p.sendPacket(new PacketPlayerGameTimeNotify(p)));
|
||||
}
|
||||
|
||||
// store updated world time every 60 seconds. (in-game hour)
|
||||
if (this.tickCount % 60 == 0) {
|
||||
this.getHost().updatePlayerGameTime(currentWorldTime);
|
||||
}
|
||||
|
||||
this.tickCount++;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void close() {}
|
||||
|
||||
/** Returns the in-game world time in real milliseconds. */
|
||||
public long getWorldTime() {
|
||||
if (!this.isPaused) {
|
||||
var newUpdateTime = System.currentTimeMillis();
|
||||
this.currentWorldTime += (newUpdateTime - lastUpdateTime);
|
||||
this.lastUpdateTime = newUpdateTime;
|
||||
}
|
||||
|
||||
return currentWorldTime;
|
||||
}
|
||||
|
||||
/** Returns the current in game days world time in ingame minutes (0-1439) */
|
||||
public int getGameTime() {
|
||||
return (int) (getTotalGameTimeMinutes() % 1440);
|
||||
}
|
||||
|
||||
/** Returns the current in game days world time in ingame hours (0-23) */
|
||||
public int getGameTimeHours() {
|
||||
return this.getGameTime() / 60;
|
||||
}
|
||||
|
||||
/** Returns the total number of in game days that got completed since the beginning of the game */
|
||||
public long getTotalGameTimeDays() {
|
||||
return ConversionUtils.gameTimeToDays(getTotalGameTimeMinutes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of in game hours that got completed since the beginning of the game
|
||||
*/
|
||||
public long getTotalGameTimeHours() {
|
||||
return ConversionUtils.gameTimeToHours(getTotalGameTimeMinutes());
|
||||
}
|
||||
|
||||
/** Returns the elapsed in-game minutes since the creation of the world. */
|
||||
public long getTotalGameTimeMinutes() {
|
||||
return this.getWorldTime() / 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the world's pause status. Updates players and scenes accordingly.
|
||||
*
|
||||
* @param paused True if the world should be paused.
|
||||
*/
|
||||
public void setPaused(boolean paused) {
|
||||
this.getWorldTime(); // Update the world time.
|
||||
|
||||
// If the world is being un-paused, update the last update time.
|
||||
if (this.isPaused != paused && !paused) {
|
||||
this.lastUpdateTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
this.isPaused = paused;
|
||||
this.getPlayers().forEach(player -> player.setPaused(paused));
|
||||
this.getScenes().forEach((key, scene) -> scene.setPaused(paused));
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the time of the world.
|
||||
*
|
||||
* @param time The new time in minutes.
|
||||
* @param days The number of days to add.
|
||||
*/
|
||||
public void changeTime(int time, int days) {
|
||||
// Calculate time differences.
|
||||
var currentTime = this.getGameTime();
|
||||
var diff = time - currentTime;
|
||||
if (diff < 0) diff = 1440 + diff;
|
||||
|
||||
// Update the world time.
|
||||
this.currentWorldTime += days * 1440 * 1000L + diff * 1000L;
|
||||
|
||||
// Update all players.
|
||||
this.host.updatePlayerGameTime(currentWorldTime);
|
||||
this.players.forEach(
|
||||
player ->
|
||||
player
|
||||
.getQuestManager()
|
||||
.queueEvent(
|
||||
QuestContent.QUEST_CONTENT_GAME_TIME_TICK, this.getGameTimeHours(), days));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Player> iterator() {
|
||||
return this.getPlayers().iterator();
|
||||
|
||||
@@ -5,7 +5,7 @@ import emu.grasscutter.data.DataLoader;
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.data.excels.InvestigationMonsterData;
|
||||
import emu.grasscutter.data.excels.RewardPreviewData;
|
||||
import emu.grasscutter.data.excels.WorldLevelData;
|
||||
import emu.grasscutter.data.excels.world.WorldLevelData;
|
||||
import emu.grasscutter.game.entity.gadget.chest.BossChestInteractHandler;
|
||||
import emu.grasscutter.game.entity.gadget.chest.ChestInteractHandler;
|
||||
import emu.grasscutter.game.entity.gadget.chest.NormalChestInteractHandler;
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
package emu.grasscutter.game.world.data;
|
||||
|
||||
import emu.grasscutter.game.props.EnterReason;
|
||||
import emu.grasscutter.net.proto.EnterTypeOuterClass;
|
||||
import emu.grasscutter.server.event.player.PlayerTeleportEvent;
|
||||
import emu.grasscutter.utils.Position;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class TeleportProperties {
|
||||
private final int sceneId;
|
||||
private final PlayerTeleportEvent.TeleportType teleportType;
|
||||
private final EnterReason enterReason;
|
||||
private Position teleportTo;
|
||||
private Position teleportRot;
|
||||
private EnterTypeOuterClass.EnterType enterType;
|
||||
}
|
||||
package emu.grasscutter.game.world.data;
|
||||
|
||||
import emu.grasscutter.game.props.EnterReason;
|
||||
import emu.grasscutter.net.proto.EnterTypeOuterClass;
|
||||
import emu.grasscutter.server.event.player.PlayerTeleportEvent;
|
||||
import emu.grasscutter.utils.Position;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class TeleportProperties {
|
||||
private final int sceneId;
|
||||
private final PlayerTeleportEvent.TeleportType teleportType;
|
||||
private final EnterReason enterReason;
|
||||
private Position teleportTo;
|
||||
private Position teleportRot;
|
||||
private EnterTypeOuterClass.EnterType enterType;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user