Merge unstable into development (#2173)

* Remove more scene synchronized

* Fix worktop options not appearing

* Format code [skip actions]

* Fix delay with server tasks

* Format code [skip actions]

* Fully fix fairy clock (#2146)

* Fix scene transition

* fully fix fairy clock

* Re-add call to `Player#updatePlayerGameTime`

* Format code [skip actions]

* Initialize the script loader in `ResourceLoader#loadAll`

* Fix region removal checking

* Format code [skip actions]

* Use Lombok's `EqualsAndHashCode` for comparing scene regions

* Format code [skip actions]

* Move 'invalid gather object' to `trace`

* Add more information to the 'unknown condition handler' message

* Move invalid ability action to trace

* Make `KcpTunnel` public

* Validate the NPC being talked to

* Format code [skip actions]

* NPCs are not spawned server side; change logic to handle it

* Format code [skip actions]

* unload scene when there are no players (#2147)

* unload scene when there are no players

* Update src/main/java/emu/grasscutter/game/world/Scene.java

Co-authored-by: Magix <27646710+KingRainbow44@users.noreply.github.com>

---------

Co-authored-by: Magix <27646710+KingRainbow44@users.noreply.github.com>

* Check if a command should be copied or HTTP should be used

* Lint Code [skip actions]

* Fix character names rendering incorrectly

* Add basic troubleshooting command

* Implement handbook teleporting

also a few formatting changes and sort data by logical sense

* Fix listener `ConcurrentModificationException` issue

* Add color change to `Join the Community!`

* Lint Code [skip actions]

* Make clickable buttons appear clickable

* Remove 'Mechanicus' entities from the list of entities

* Format code [skip actions]

* Fix going back returning a blank screen

* Implement entity spawning

* Add setting level to entity card

* Add support for 'plain text' mode

* Make descriptions of objects scrollable

* Lint Code [skip actions]

* Format code [skip actions]

* Change the way existing hooks work

* Format code [skip actions]

* Upgrade Javalin to 5.5.0 & Fix project warnings

* Upgrade logging libraries

* Fix gacha mappings static file issue

* Add temporary backwards compatability for `ServerHelper`

* Format code [skip actions]

* Remove artifact signatures from VCS

* Fix forge queue data protocol definition

* Run `spotlessApply`

* Format code [skip actions]

* Download data required for building artifacts

* Add call for Facebook logins

* Add the wiki page as a submodule

* Format code [skip actions]

* Update translation (#2150)

* Update translation

* Update translation

* Separate the dispatch and game servers (pt. 1)

gacha is still broken, handbook still needs to be done

* Format code [skip actions]

* Separate the dispatch and game servers (pt. 2)

this commit fixes the gacha page

* Add description for '/troubleshoot'

* Set default avatar talent level to 10

* Separate the dispatch and game servers (pt. 3)

implement handbook across servers!

* Format code [skip actions]

* Update GitHub Actions to use 'download-file' over 'wget'

* Gm handbook lmao (#2149)

* Fix font issue

* Fix avatars

* Fix text overflow in commands

* Fix virtualized lists and items page 😭😭

* magix why 💀

* use hover style in all minicards

* button

* remove console.log

* lint

* Add icons

* magix asked

* Fix overflow padding issue

* Fix achievement text overflow

* remove icons from repo

* Change command icon

* Add the wiki page as a submodule

* total magix moment

* fix text overflow in commands

* Fix discord button

* Make text scale on Minicard

* import icons and font from another source

* Add hover effects to siebar buttons

* move font and readme to submodule repo

* Make data folder a submodule

* import icons and font from data submodule

* Update README.md

* total magix moment

* magix moment v2

* submodule change

* Import `.webp` files

* Resize `HomeButton`

* Fix 'Copy Command' reappearing after changing pages

---------

Co-authored-by: KingRainbow44 <kobedo11@gmail.com>

* Lint Code [skip actions]

* Download data for the build, not for the lint

* format imports

this is really just to see if build handbook works kek

* Implement proper handbook authentication (pt. 1)

* Implement proper handbook authentication (pt. 2)

* Format code [skip actions]

* Add quest data dumping for the handbook

* Change colors to fit _something suitable_

* Format code [skip actions]

* Fix force pushing to branches after linting

* Fix logic of `SetPlayerPropReq`

* Move more group loading to `trace`

* Add handbook IP authentication in hybrid mode

* Fix player level up not displaying on the client properly

* Format code [skip actions]

* Fix game time locking

* Format code [skip actions]

* Update player properties

* Format code [skip actions]

* Move `warn`s for groups to `debug`

* Fix player pausing

* Move more logs to `trace`

* Use `removeItemById` for deleting items via quests

* Clean up logger more

* Pause in-game time when the world is paused

* Format code [skip actions]

* More player property documentation

* Multi-threaded resource loading

* Format code [skip actions]

* Add quest widgets

* Add quests page (basic impl.)

* Add/fix colors

also fix tailwind

* Remove banned packets

client modifications already perform the job of blocking malicious packets from being executed, no point in having this if self-windy is wanted

* Re-add `BeginCameraSceneLookNotify`

* Fix being unable to attack (#2157)

* Add `PlayerOpenChestEvent`

* Add methods to get players from the server

* Add static methods to register an event handler

* Add `PlayerEnterDungeonEvent`

* Remove legacy documentation from `PlayerMoveEvent`

* Add `PlayerChatEvent`

* Add defaults to `Position`

* Clean up `.utils`

* Revert `Multi-threaded resource loading`

* Fix changing target UID when talking to the server

* Lint Code [skip actions]

* Format code [skip actions]

* fix NPC talk triggering main quest in 46101 (#2158)

Make it so that only talks where the param matches the talkId are checked.

* Format code [skip actions]

* Partially fix Chasing Shadows (#2159)

* Partially fix Chasing Shadows

* Go ahead and move it before the return before Magix tells me to.

* Format code [skip actions]

* Bring back period lol (#2160)

* Disable SNI for the HTTPS server

* Add `EntityCreationEvent`

* Add initial startup message

this is so the server appears like its preparing to start

* Format code [skip actions]

* Enable debug mode for plugin loggers if enabled for the primary logger

* Add documentation about `WorldAreaConfigData`

* Make more fields in excels accessible

* Remove deprecated fields from `GetShopRsp`

* Run `spotlessApply` on definitions

* Add `PlayerEnterAreaEvent`

* Optimize event calls

* Fix event invokes

* Format code [skip actions]

* Remove manual autofinish for main quests. (#2162)

* Add world areas to the textmap cache

* Format code [skip actions]

* Don't overdefine variables in extended classes (#2163)

* Add dumper for world areas

* Format code [skip actions]

* instantiate personalLineList (#2165)

* Fix protocol definitions

thank you Nazrin! (+ hiro for raw definitions)

* Fix the background color leaking from the character widget

* Change HTML spacing to 2 spaces

* Implement hiding widgets

* Change scrollbar to a vibrant color

* Add _some_ scaling to the home buttons and its text

* Build the handbook with Gradle

* Fix the 'finer details' with the handbook UI

* Lint Code [skip actions]

* Fix target destination for the Gradle-built handbook

* Implement fetching a player across servers & Add a chainable JsonObject

useful for plugins! might be used in grasscutter eventually

* Fix GitHub actions

* Fix event calling & canceling

* Run `spotlessApply`

* Rename fields (might be wrong)

* Add/update all/more protocol definitions

* Add/update all/more protocol definitions

* Remove outdated packet

* Fix protocol definitions

* Format code [skip actions]

* Implement some lua variables for less console spam (#2172)

* Implement some lua variables for less console spam

* Add GetHostQuestState

This fixes some chapter 3 stuff.

* Format code [skip actions]

* Fix merge import

* Format code [skip actions]

* Fully fix fairy clock for real this time (#2167)

* Fully fix fairy clock For real this time

* Make it so relogging keeps the time lock state.

* Refactor out questLockTime

* Per Hartie, the client packet needs to be changed too

* Update src/main/java/emu/grasscutter/game/world/World.java

Co-authored-by: Magix <27646710+KingRainbow44@users.noreply.github.com>

* Update src/main/java/emu/grasscutter/server/packet/recv/HandlerClientLockGameTimeNotify.java

* Remove all code not needed to get clock working

---------

Co-authored-by: Magix <27646710+KingRainbow44@users.noreply.github.com>

* Implement a proper ability system (#2166)

* Apply fix `21dec2fe`

* Apply fix `89d01d5f`

* Apply fix `d900f154`

this one was already implemented; updated to use call from previous commit

* Ability changing commit

TODO: change info to debug

* Remove use of deprecated methods/fields

* Temp commit v2
(Adding LoseHP and some fixes)

* Oopsie

* Probably fix monster battle

* Fix issue with reflecting into fields

* Fix some things

* Fix ability names for 3.6 resources

* Improve logging

---------

Co-authored-by: StartForKiller <jesussanz2003@gmail.com>

* Format code [skip actions]

* Add system for sending messages between servers

* Format some code

* Remove protocol definitions from Spotless

* Default debug to false; enable with `-debug`

* Implement completely useless global value copying

* HACK: Return the avatar which holds the weapon when the weapon is referred to by ID

* Add properties to `AbilityModifier`

* Change the way HTML is served after authentication

* Use thread executors to speed up the database loading process

* Format code [skip actions]

* Add system for setting handbook address and port

* Lint Code [skip actions]

* Format code [skip actions]

* Fix game-related data not saving

* Format code [skip actions]

* Fix handbook server details

* Lint Code [skip actions]

* Format code [skip actions]

* Use the headers provided by a context to get the IP address

should acknowledge #1975

* Format code [skip actions]

* Move more logs to `trace`

* Format code [skip actions]

* more trace

* Fix something and implement weapon entities

* Format code [skip actions]

* Fix `EntityWeapon`

* Remove deprecated API & Fix resource checking

* Fix unnecessary warning for first-time setup

* Implement handbook request limiting

* Format code [skip actions]

* Fix new avatar weapons being null

* Format code [skip actions]

* Fix issue with 35303 being un-completable & Try to fix fulfilled quest conditions being met

* Load activity config on server startup

* Require plugins to specify an API version and match with the server

* Add default open state ignore list

* Format code [skip actions]

* Quick fix for questing, needs more investigation
This would make the questing work again

* Remove existing hack for 35303

* Fix ignored open states from being set

* Format code [skip actions]

* fix the stupidest bug ive ever seen

* Optimize player kicking on server close

* Format code [skip actions]

* Re-add hack to fix 35303

* Update GitHub actions

* Format code [skip actions]

* Potentially fix issues with regions

* Download additional handbook data

* Revert "Potentially fix issues with regions"

This reverts commit 84e3823695.

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: scooterboo <lewasite@yahoo.com>
Co-authored-by: Tesutarin <105267106+Tesutarin@users.noreply.github.com>
Co-authored-by: Scald <104459145+Arikatsu@users.noreply.github.com>
Co-authored-by: StartForKiller <jesussanz2003@gmail.com>
This commit is contained in:
Magix
2023-05-31 20:48:16 -07:00
committed by GitHub
parent f46fd372d2
commit 9e5b57a043
3839 changed files with 1841548 additions and 37533 deletions

View File

@@ -1,12 +1,11 @@
package emu.grasscutter.game.world;
import emu.grasscutter.game.inventory.ItemDef;
import java.util.List;
import lombok.AccessLevel;
import lombok.Data;
import lombok.experimental.FieldDefaults;
import java.util.List;
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
public class ChestReward {
@@ -18,5 +17,4 @@ public class ChestReward {
List<ItemDef> content;
int randomCount;
List<ItemDef> randomContent;
}

View File

@@ -0,0 +1,124 @@
package emu.grasscutter.game.world;
import com.github.davidmoten.rtreemulti.geometry.Point;
import dev.morphia.annotations.Entity;
import java.io.IOException;
import java.io.Serializable;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
@Entity
public final class GridPosition implements Serializable {
private static final long serialVersionUID = -2001232300615923575L;
@Getter @Setter private int x;
@Getter @Setter private int z;
@Getter @Setter private int width;
public GridPosition() {}
public GridPosition(int x, int y, int width) {
set(x, y, width);
}
public GridPosition(GridPosition pos) {
this.set(pos);
}
public GridPosition(Position pos, int width) {
this.set((int) (pos.getX() / width), (int) (pos.getZ() / width), width);
}
public GridPosition(List<Integer> xzwidth) {
this.width = xzwidth.get(2);
this.z = xzwidth.get(1);
this.x = xzwidth.get(0);
}
@SneakyThrows
public GridPosition(String str) {
var listOfParams = str.replace(" ", "").replace("(", "").replace(")", "").split(",");
if (listOfParams.length != 3)
throw new IOException("invalid size on GridPosition definition - ");
try {
this.x = Integer.parseInt(listOfParams[0]);
this.z = Integer.parseInt(listOfParams[1]);
this.width = Integer.parseInt(listOfParams[2]);
} catch (NumberFormatException ignored) {
throw new IOException("invalid number on GridPosition definition - ");
}
}
public GridPosition set(int x, int z) {
this.x = x;
this.z = z;
return this;
}
public GridPosition set(int x, int z, int width) {
this.x = x;
this.z = z;
this.width = width;
return this;
}
// Deep copy
public GridPosition set(GridPosition pos) {
return this.set(pos.getX(), pos.getZ(), pos.getWidth());
}
public GridPosition addClone(int x, int z) {
GridPosition pos = clone();
pos.x += x;
pos.z += z;
return pos;
}
@Override
public GridPosition clone() {
return new GridPosition(x, z, width);
}
@Override
public String toString() {
return "(" + this.getX() + ", " + this.getZ() + ", " + this.getWidth() + ")";
}
public int[] toIntArray() {
return new int[] {x, z, width};
}
public double[] toDoubleArray() {
return new double[] {x, z};
}
public int[] toXZIntArray() {
return new int[] {x, z};
}
public Point toPoint() {
return Point.create(x, z);
}
@Override
public int hashCode() {
int result = x ^ (x >>> 32);
result = 31 * result + (z ^ (z >>> 32));
result = 31 * result + (width ^ (width >>> 32));
return result;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
if (getClass() != o.getClass()) return false;
GridPosition pos = (GridPosition) o;
// field comparison
return pos.x == x && pos.z == z && pos.width == width;
}
}

View File

@@ -0,0 +1,10 @@
package emu.grasscutter.game.world;
import java.util.List;
import lombok.Data;
@Data
public class GroupReplacementData {
int id;
List<Integer> replace_groups;
}

View File

@@ -0,0 +1,39 @@
package emu.grasscutter.game.world;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient;
import lombok.Getter;
import lombok.Setter;
@Entity
public class Location extends Position {
@Transient @Getter @Setter private Scene scene;
public Location(Scene scene, Position position) {
this.set(position);
this.scene = scene;
}
public Location(Scene scene, float x, float y) {
this.set(x, y);
this.scene = scene;
}
public Location(Scene scene, float x, float y, float z) {
this.set(x, y, z);
this.scene = scene;
}
@Override
public Location clone() {
return new Location(this.scene, super.clone());
}
@Override
public String toString() {
return String.format("%s:%s,%s,%s", this.scene.getId(), this.getX(), this.getY(), this.getZ());
}
}

View File

@@ -0,0 +1,209 @@
package emu.grasscutter.game.world;
import com.github.davidmoten.rtreemulti.geometry.Point;
import com.google.gson.annotations.SerializedName;
import dev.morphia.annotations.Entity;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.utils.Utils;
import java.io.Serializable;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
@Entity
@Accessors(chain = true)
public class Position implements Serializable {
private static final long serialVersionUID = -2001232313615923575L;
public static final Position ZERO = new Position(0, 0, 0);
public static final Position IDENTITY = new Position(0, 0);
@SerializedName(
value = "x",
alternate = {"_x", "X"})
@Getter
@Setter
private float x;
@SerializedName(
value = "y",
alternate = {"_y", "Y"})
@Getter
@Setter
private float y;
@SerializedName(
value = "z",
alternate = {"_z", "Z"})
@Getter
@Setter
private float z;
public Position() {}
public Position(float x, float y) {
set(x, y);
}
public Position(float x, float y, float z) {
set(x, y, z);
}
public Position(List<Float> xyz) {
switch (xyz.size()) {
default: // Might want to error on excess elements, but maybe we want to extend to 3+3
// representation later.
case 3:
this.z = xyz.get(2); // Fall-through
case 2:
this.y = xyz.get(1); // Fall-through
case 1:
this.y = xyz.get(0); // pointless fall-through
case 0:
break;
}
}
public Position(String p) {
String[] split = p.split(",");
if (split.length >= 2) {
this.x = Float.parseFloat(split[0]);
this.y = Float.parseFloat(split[1]);
}
if (split.length >= 3) {
this.z = Float.parseFloat(split[2]);
}
}
public Position(Vector vector) {
this.set(vector);
}
public Position(Position pos) {
this.set(pos);
}
public Position set(float x, float y) {
this.x = x;
this.y = y;
return this;
}
// Deep copy
public Position set(Position pos) {
return this.set(pos.getX(), pos.getY(), pos.getZ());
}
public Position set(Vector pos) {
return this.set(pos.getX(), pos.getY(), pos.getZ());
}
public Position set(float x, float y, float z) {
this.x = x;
this.y = y;
this.z = z;
return this;
}
public Position multiply(float value) {
this.x *= value;
this.y *= value;
this.z *= value;
return this;
}
public Position add(Position add) {
this.x += add.getX();
this.y += add.getY();
this.z += add.getZ();
return this;
}
public Position addX(float d) {
this.x += d;
return this;
}
public Position addY(float d) {
this.y += d;
return this;
}
public Position addZ(float d) {
this.z += d;
return this;
}
public Position subtract(Position sub) {
this.x -= sub.getX();
this.y -= sub.getY();
this.z -= sub.getZ();
return this;
}
/** In radians */
public Position translate(float dist, float angle) {
this.x += dist * Math.sin(angle);
this.y += dist * Math.cos(angle);
return this;
}
public boolean equal2d(Position other) {
// Y is height
return getX() == other.getX() && getZ() == other.getZ();
}
public boolean equal3d(Position other) {
return getX() == other.getX() && getY() == other.getY() && getZ() == other.getZ();
}
public double computeDistance(Position b) {
double detX = getX() - b.getX();
double detY = getY() - b.getY();
double detZ = getZ() - b.getZ();
return Math.sqrt(detX * detX + detY * detY + detZ * detZ);
}
public Position nearby2d(float range) {
Position position = clone();
position.z += Utils.randomFloatRange(-range, range);
position.x += Utils.randomFloatRange(-range, range);
return position;
}
public Position translateWithDegrees(float dist, float angle) {
angle = (float) Math.toRadians(angle);
this.x += dist * Math.sin(angle);
this.y += -dist * Math.cos(angle);
return this;
}
@Override
public Position clone() {
return new Position(x, y, z);
}
@Override
public String toString() {
return "(" + this.getX() + ", " + this.getY() + ", " + this.getZ() + ")";
}
public Vector toProto() {
return Vector.newBuilder().setX(this.getX()).setY(this.getY()).setZ(this.getZ()).build();
}
public Point toPoint() {
return Point.create(x, y, 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};
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,85 @@
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 final 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);
}
}

View File

@@ -1,10 +1,8 @@
package emu.grasscutter.game.world;
import emu.grasscutter.data.GameDepot;
import java.util.List;
import java.util.Objects;
import emu.grasscutter.data.GameDepot;
import emu.grasscutter.utils.Position;
import lombok.Getter;
import lombok.Setter;
@@ -22,10 +20,11 @@ public class SpawnDataEntry {
public GridBlockId getBlockId() {
int scale = GridBlockId.getScale(gadgetId);
return new GridBlockId(group.sceneId,scale,
(int)(pos.getX() / GameDepot.BLOCK_SIZE[scale]),
(int)(pos.getZ() / GameDepot.BLOCK_SIZE[scale])
);
return new GridBlockId(
group.sceneId,
scale,
(int) (pos.getX() / GameDepot.BLOCK_SIZE[scale]),
(int) (pos.getZ() / GameDepot.BLOCK_SIZE[scale]));
}
public static class SpawnGroupEntry {
@@ -48,14 +47,37 @@ public class SpawnDataEntry {
this.z = z;
}
public static GridBlockId[] getAdjacentGridBlockIds(int sceneId, Position pos) {
GridBlockId[] results = new GridBlockId[5 * 5 * GameDepot.BLOCK_SIZE.length];
int t = 0;
for (int scale = 0; scale < GameDepot.BLOCK_SIZE.length; scale++) {
int x = ((int) (pos.getX() / GameDepot.BLOCK_SIZE[scale]));
int z = ((int) (pos.getZ() / GameDepot.BLOCK_SIZE[scale]));
for (int i = x - 2; i < x + 3; i++) {
for (int j = z - 2; j < z + 3; j++) {
results[t++] = new GridBlockId(sceneId, scale, i, j);
}
}
}
return results;
}
public static int getScale(int gadgetId) {
return 0; // you should implement here,this is index of GameDepot.BLOCK_SIZE
}
@Override
public String toString() {
return "SpawnDataEntryScaledPoint{" +
"sceneId=" + sceneId +
", scale=" + scale +
", x=" + x +
", z=" + z +
'}';
return "SpawnDataEntryScaledPoint{"
+ "sceneId="
+ sceneId
+ ", scale="
+ scale
+ ", x="
+ x
+ ", z="
+ z
+ '}';
}
@Override
@@ -70,24 +92,5 @@ public class SpawnDataEntry {
public int hashCode() {
return Objects.hash(sceneId, scale, x, z);
}
public static GridBlockId[] getAdjacentGridBlockIds(int sceneId, Position pos) {
GridBlockId[] results = new GridBlockId[5*5*GameDepot.BLOCK_SIZE.length];
int t=0;
for (int scale = 0; scale < GameDepot.BLOCK_SIZE.length; scale++) {
int x = ((int)(pos.getX()/GameDepot.BLOCK_SIZE[scale]));
int z = ((int)(pos.getZ()/GameDepot.BLOCK_SIZE[scale]));
for (int i=x-2; i<x+3; i++) {
for (int j=z-2; j<z+3; j++) {
results[t++] = new GridBlockId(sceneId, scale, i, j);
}
}
}
return results;
}
public static int getScale(int gadgetId) {
return 0;//you should implement here,this is index of GameDepot.BLOCK_SIZE
}
}
}

View File

@@ -1,84 +1,88 @@
package emu.grasscutter.game.world;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import static emu.grasscutter.server.event.player.PlayerTeleportEvent.TeleportType.SCRIPT;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.dungeon.DungeonData;
import emu.grasscutter.game.entity.EntityTeam;
import emu.grasscutter.game.entity.EntityWorld;
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.PlayerProperty;
import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.DungeonData;
import emu.grasscutter.data.excels.SceneData;
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;
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.PacketDelTeamEntityNotify;
import emu.grasscutter.server.packet.send.PacketPlayerEnterSceneNotify;
import emu.grasscutter.server.packet.send.PacketScenePlayerInfoNotify;
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 emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.ConversionUtils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import lombok.Getter;
import lombok.val;
import org.jetbrains.annotations.NotNull;
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 int levelEntityId;
@Getter private EntityWorld entity;
private int nextEntityId = 0;
private int nextPeerId = 0;
private int worldLevel;
private boolean isMultiplayer;
@Getter private boolean isMultiplayer, timeLocked = false;
private long lastUpdateTime;
@Getter private int tickCount = 0;
@Getter private boolean isPaused = false;
@Getter private long currentWorldTime;
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<>());
this.levelEntityId = this.getNextEntityId(EntityIdType.MPLEVEL);
// this.levelEntityId = this.getNextEntityId(EntityIdType.MPLEVEL);
this.entity = new EntityWorld(this);
this.worldLevel = player.getWorldLevel();
this.isMultiplayer = isMultiplayer;
this.owner.getServer().registerWorld(this);
}
this.lastUpdateTime = System.currentTimeMillis();
this.currentWorldTime = host.getPlayerGameTime();
public Player getHost() {
return owner;
}
public GameServer getServer() {
return server;
this.host.getServer().registerWorld(this);
}
public int getLevelEntityId() {
return levelEntityId;
return entity.getId();
}
/**
* 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() {
@@ -93,23 +97,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,13 +122,15 @@ public class World implements Iterable<Player> {
}
public int getPlayerCount() {
return this.getPlayers().size();
}
public boolean isMultiplayer() {
return isMultiplayer;
return this.players.size();
}
/**
* 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;
}
@@ -148,11 +152,17 @@ public class World implements Iterable<Player> {
// Set player variables
player.setPeerId(this.getNextPeerId());
player.getTeamManager().setEntityId(this.getNextEntityId(EntityIdType.TEAM));
player.getTeamManager().setEntity(new EntityTeam(player));
// player.getTeamManager().setEntityId(this.getNextEntityId(EntityIdType.TEAM));
// Copy main team to multiplayer team
if (this.isMultiplayer()) {
player.getTeamManager().getMpTeam().copyFrom(player.getTeamManager().getCurrentSinglePlayerTeamInfo(), player.getTeamManager().getMaxTeamSize());
player
.getTeamManager()
.getMpTeam()
.copyFrom(
player.getTeamManager().getCurrentSinglePlayerTeamInfo(),
player.getTeamManager().getMaxTeamSize());
player.getTeamManager().setCurrentCharacterIndex(0);
}
@@ -171,9 +181,13 @@ public class World implements Iterable<Player> {
player.sendPacket(
new PacketDelTeamEntityNotify(
player.getSceneId(),
this.getPlayers().stream().map(p -> p.getTeamManager().getEntityId()).collect(Collectors.toList())
)
);
this.getPlayers().stream()
.map(
p ->
p.getTeamManager().getEntity() == null
? 0
: p.getTeamManager().getEntity().getId())
.toList()));
// Deregister
this.getPlayers().remove(player);
@@ -195,7 +209,13 @@ public class World implements Iterable<Player> {
World world = new World(victim);
world.addPlayer(victim);
victim.sendPacket(new PacketPlayerEnterSceneNotify(victim, EnterType.ENTER_TYPE_SELF, EnterReason.TeamKick, victim.getSceneId(), victim.getPosition()));
victim.sendPacket(
new PacketPlayerEnterSceneNotify(
victim,
EnterType.ENTER_TYPE_SELF,
EnterReason.TeamKick,
victim.getSceneId(),
victim.getPosition()));
}
}
}
@@ -205,14 +225,20 @@ public class World implements Iterable<Player> {
}
public void deregisterScene(Scene scene) {
scene.saveGroups();
this.getScenes().remove(scene.getId());
}
public void save() {
this.getScenes().values().forEach(Scene::saveGroups);
}
public boolean transferPlayerToScene(Player player, int sceneId, Position pos) {
return this.transferPlayerToScene(player, sceneId, TeleportType.INTERNAL, null, pos);
}
public boolean transferPlayerToScene(Player player, int sceneId, TeleportType teleportType, Position pos) {
public boolean transferPlayerToScene(
Player player, int sceneId, TeleportType teleportType, Position pos) {
return this.transferPlayerToScene(player, sceneId, teleportType, null, pos);
}
@@ -220,79 +246,135 @@ public class World implements Iterable<Player> {
return this.transferPlayerToScene(player, sceneId, TeleportType.DUNGEON, data, null);
}
public boolean transferPlayerToScene(Player player, int sceneId, TeleportType teleportType, DungeonData dungeonData, Position teleportTo) {
public boolean transferPlayerToScene(
Player player,
int sceneId,
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
.teleportTo(dungeonData.getStartPosition())
.teleportRot(dungeonData.getStartRotation());
teleportProps.enterType(EnterType.ENTER_TYPE_DUNGEON).enterReason(EnterReason.DungeonEnter);
teleportProps.dungeonId(dungeonData.getId());
} 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) {
// Check if the teleport properties are valid.
if (teleportProperties.getTeleportTo() == null)
teleportProperties.setTeleportTo(player.getPosition());
// Call player teleport event.
PlayerTeleportEvent event = new PlayerTeleportEvent(player, teleportType, player.getPosition(), teleportTo);
PlayerTeleportEvent event =
new PlayerTeleportEvent(player, teleportProperties, player.getPosition());
// Call event & check if it was canceled.
event.call(); if (event.isCanceled()) {
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
// Dungeon system is handling this already
// if(dungeonData!=null){
// var dungeonManager = new DungeonManager(newScene, dungeonData);
// dungeonManager.startDungeon();
// }
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(config.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;
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;
// Teleport packet
player.sendPacket(new PacketPlayerEnterSceneNotify(player, teleportProperties));
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;
}
@@ -303,14 +385,19 @@ public class World implements Iterable<Player> {
continue;
}
// Update team of all players since max players has been changed - Probably not the best way to do it
// Update team of all players since max players has been changed - Probably not the best way
// to do it
if (this.isMultiplayer()) {
player.getTeamManager().getMpTeam().copyFrom(player.getTeamManager().getMpTeam(), player.getTeamManager().getMaxTeamSize());
player
.getTeamManager()
.getMpTeam()
.copyFrom(
player.getTeamManager().getMpTeam(), player.getTeamManager().getMaxTeamSize());
player.getTeamManager().updateTeamEntities(null);
}
// Dont send packets if player is loading into the scene
if (player.getSceneLoadState().getValue() < SceneLoadState.INIT.getValue() ) {
if (player.getSceneLoadState().getValue() < SceneLoadState.INIT.getValue()) {
// World player info packets
player.getSession().send(new PacketWorldPlayerInfoNotify(this));
player.getSession().send(new PacketScenePlayerInfoNotify(this));
@@ -330,18 +417,152 @@ 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.timeLocked) {
this.getHost().updatePlayerGameTime(this.currentWorldTime);
}
this.tickCount++;
return false;
}
public void close() {
public void close() {}
/** Returns the in-game world time in real milliseconds. */
public long getWorldTime() {
if (!this.isPaused && !this.timeLocked) {
var newUpdateTime = System.currentTimeMillis();
this.currentWorldTime += (newUpdateTime - lastUpdateTime);
this.lastUpdateTime = newUpdateTime;
}
return this.currentWorldTime;
}
@Override
/** Returns the current in game days world time in in-game 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) {
// Check if this world is a multiplayer world.
if (this.isMultiplayer) return;
// Update the world time.
this.getWorldTime();
this.updateTime();
// 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 game time of the world.
*
* @param gameTime The time in game minutes.
*/
public void changeTime(long gameTime) {
this.currentWorldTime = gameTime;
// Trigger script events.
this.players.forEach(
player -> player.getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_GAME_TIME_TICK));
}
/**
* 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) {
// Check if the time is locked.
if (this.timeLocked) return;
// 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));
}
/** Notifies all players of the current world time. */
public void updateTime() {
this.getPlayers().forEach(p -> p.sendPacket(new PacketPlayerGameTimeNotify(p)));
this.getPlayers().forEach(p -> p.sendPacket(new PacketSceneTimeNotify(p)));
}
/**
* Locks the world time.
*
* @param locked True if the world time should be locked.
*/
public void lockTime(boolean locked) {
this.timeLocked = locked;
// Notify players of the locking.
this.updateTime();
this.getPlayers()
.forEach(player -> player.setProperty(PlayerProperty.PROP_IS_GAME_TIME_LOCKED, locked));
}
@NotNull @Override
public Iterator<Player> iterator() {
return this.getPlayers().iterator();
}

View File

@@ -1,26 +1,17 @@
package emu.grasscutter.game.world;
import emu.grasscutter.Grasscutter;
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.game.entity.gadget.chest.BossChestInteractHandler;
import emu.grasscutter.game.entity.gadget.chest.ChestInteractHandler;
import emu.grasscutter.game.entity.gadget.chest.NormalChestInteractHandler;
import emu.grasscutter.data.*;
import emu.grasscutter.data.excels.*;
import emu.grasscutter.data.excels.world.WorldLevelData;
import emu.grasscutter.game.entity.gadget.chest.*;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.InvestigationMonsterOuterClass;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.scripts.data.SceneMonster;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import emu.grasscutter.scripts.data.*;
import emu.grasscutter.server.game.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import org.luaj.vm2.LuaError;
public class WorldDataSystem extends BaseGameSystem {
private final Map<String, ChestInteractHandler> chestInteractHandlerMap; // chestType-Handler
@@ -40,9 +31,14 @@ public class WorldDataSystem extends BaseGameSystem {
try {
DataLoader.loadList("ChestReward.json", ChestReward.class)
.forEach(reward ->
reward.getObjNames().forEach(name ->
chestInteractHandlerMap.computeIfAbsent(name, x -> new NormalChestInteractHandler(reward))));
.forEach(
reward ->
reward
.getObjNames()
.forEach(
name ->
chestInteractHandlerMap.computeIfAbsent(
name, x -> new NormalChestInteractHandler(reward))));
} catch (Exception e) {
Grasscutter.getLogger().error("Unable to load chest reward config.", e);
}
@@ -53,23 +49,29 @@ public class WorldDataSystem extends BaseGameSystem {
}
public RewardPreviewData getRewardByBossId(int monsterId) {
var investigationMonsterData = GameData.getInvestigationMonsterDataMap().values().parallelStream()
.filter(imd -> imd.getMonsterIdList() != null && !imd.getMonsterIdList().isEmpty())
.filter(imd -> imd.getMonsterIdList().get(0) == monsterId)
.findFirst();
var investigationMonsterData =
GameData.getInvestigationMonsterDataMap().values().parallelStream()
.filter(imd -> imd.getMonsterIdList() != null && !imd.getMonsterIdList().isEmpty())
.filter(imd -> imd.getMonsterIdList().get(0) == monsterId)
.findFirst();
if (investigationMonsterData.isEmpty()) {
return null;
}
return GameData.getRewardPreviewDataMap().get(investigationMonsterData.get().getRewardPreviewId());
return investigationMonsterData
.map(
monsterData -> GameData.getRewardPreviewDataMap().get(monsterData.getRewardPreviewId()))
.orElse(null);
}
private SceneGroup getInvestigationGroup(int sceneId, int groupId) {
var key = sceneId + "_" + groupId;
if (!sceneInvestigationGroupMap.containsKey(key)) {
var group = SceneGroup.of(groupId).load(sceneId);
sceneInvestigationGroupMap.putIfAbsent(key, group);
return group;
try {
var group = SceneGroup.of(groupId).load(sceneId);
sceneInvestigationGroupMap.putIfAbsent(key, group);
return group;
} catch (LuaError luaError) {
Grasscutter.getLogger()
.error("failed to get investigationGroup {} in scene{}:", groupId, sceneId, luaError);
}
}
return sceneInvestigationGroupMap.get(key);
}
@@ -80,11 +82,13 @@ public class WorldDataSystem extends BaseGameSystem {
WorldLevelData worldLevelData = GameData.getWorldLevelDataMap().get(world.getWorldLevel());
if (worldLevelData != null) {
level = worldLevelData.getMonsterLevel();
level = Math.max(level, worldLevelData.getMonsterLevel());
}
return level;
}
private InvestigationMonsterOuterClass.InvestigationMonster getInvestigationMonster(Player player, InvestigationMonsterData imd) {
private InvestigationMonsterOuterClass.InvestigationMonster getInvestigationMonster(
Player player, InvestigationMonsterData imd) {
if (imd.getGroupIdList().isEmpty() || imd.getMonsterIdList().isEmpty()) {
return null;
}
@@ -98,16 +102,16 @@ public class WorldDataSystem extends BaseGameSystem {
return null;
}
var monster = group.monsters.values().stream()
.filter(x -> x.monster_id == monsterId)
.findFirst();
var monster =
group.monsters.values().stream().filter(x -> x.monster_id == monsterId).findFirst();
if (monster.isEmpty()) {
return null;
}
var builder = InvestigationMonsterOuterClass.InvestigationMonster.newBuilder();
builder.setId(imd.getId())
builder
.setId(imd.getId())
.setCityId(imd.getCityId())
.setSceneId(imd.getCityData().getSceneId())
.setGroupId(groupId)
@@ -128,19 +132,18 @@ public class WorldDataSystem extends BaseGameSystem {
return builder.build();
}
public List<InvestigationMonsterOuterClass.InvestigationMonster> getInvestigationMonstersByCityId(Player player, int cityId) {
public List<InvestigationMonsterOuterClass.InvestigationMonster> getInvestigationMonstersByCityId(
Player player, int cityId) {
var cityData = GameData.getCityDataMap().get(cityId);
if (cityData == null) {
Grasscutter.getLogger().warn("City not exist {}", cityId);
return List.of();
}
return GameData.getInvestigationMonsterDataMap().values()
.parallelStream()
return GameData.getInvestigationMonsterDataMap().values().parallelStream()
.filter(imd -> imd.getCityId() == cityId)
.map(imd -> this.getInvestigationMonster(player, imd))
.filter(Objects::nonNull)
.toList();
}
}

View File

@@ -0,0 +1,20 @@
package emu.grasscutter.game.world.data;
import emu.grasscutter.game.props.EnterReason;
import emu.grasscutter.game.world.Position;
import emu.grasscutter.net.proto.EnterTypeOuterClass;
import emu.grasscutter.server.event.player.PlayerTeleportEvent;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public final class TeleportProperties {
private final int sceneId;
private final int dungeonId;
private final PlayerTeleportEvent.TeleportType teleportType;
private final EnterReason enterReason;
private Position teleportTo;
private Position teleportRot;
private EnterTypeOuterClass.EnterType enterType;
}