Merge remote-tracking branch 'origin/dev-4.0' into dev-4.0

# Conflicts:
#	src/main/java/emu/grasscutter/game/player/Player.java
#	src/main/java/emu/grasscutter/game/player/TeamManager.java
#	src/main/java/emu/grasscutter/game/world/Scene.java
This commit is contained in:
KingRainbow44
2023-08-31 21:06:13 -04:00
82 changed files with 2394 additions and 1075 deletions

View File

@@ -157,6 +157,13 @@ public class AvatarStorage extends BasePlayerManager implements Iterable<Avatar>
// Add to avatar storage
this.avatars.put(avatar.getAvatarId(), avatar);
this.avatarsGuid.put(avatar.getGuid(), avatar);
// Set main character skill depot data, fixes loading with no element every login
if ((avatar.getAvatarId() == 10000007) || (avatar.getAvatarId() == 10000005)) {
avatar.setSkillDepot(skillDepot);
avatar.setSkillDepotData(skillDepot);
avatar.save();
}
}
this.setLoaded(true);

View File

@@ -14,19 +14,24 @@ import org.bson.types.ObjectId;
@Entity(value = "friendships", useDiscriminator = false)
public class Friendship {
@Id private ObjectId id;
@Id
private ObjectId id;
@Transient private Player owner;
@Transient
private Player owner;
@Indexed private int ownerId;
@Indexed private int friendId;
@Indexed
private int ownerId;
@Indexed
private int friendId;
private boolean isFriend;
private int askerId;
private PlayerProfile profile;
@Deprecated // Morphia use only
public Friendship() {}
public Friendship() {
}
public Friendship(Player owner, Player friend, Player asker) {
this.setOwner(owner);
@@ -90,27 +95,28 @@ public class Friendship {
}
public FriendBrief toProto() {
FriendBrief proto =
FriendBrief.newBuilder()
.setUid(getFriendProfile().getUid())
.setNickname(getFriendProfile().getName())
.setLevel(getFriendProfile().getPlayerLevel())
.setProfilePicture(
ProfilePicture.newBuilder().setAvatarId(getFriendProfile().getAvatarId()))
.setWorldLevel(getFriendProfile().getWorldLevel())
.setSignature(getFriendProfile().getSignature())
.setOnlineState(
getFriendProfile().isOnline()
? FriendOnlineState.FRIEND_ONLINE_STATE_ONLINE
: FriendOnlineState.FRIEND_ONLINE_STATE_DISCONNECT)
.setIsMpModeAvailable(true)
.setLastActiveTime(getFriendProfile().getLastActiveTime())
.setNameCardId(getFriendProfile().getNameCard())
.setParam(getFriendProfile().getDaysSinceLogin())
.setIsGameSource(true)
.setPlatformType(PlatformTypeOuterClass.PlatformType.PLATFORM_TYPE_PC)
.build();
var player = this.getFriendProfile().getPlayer(); // get latest player and sync.
return proto;
return FriendBrief.newBuilder()
.setUid(getFriendProfile().getUid())
.setNickname(getFriendProfile().getName())
.setLevel(getFriendProfile().getPlayerLevel())
.setProfilePicture(
ProfilePicture.newBuilder().setAvatarId(getFriendProfile().getAvatarId()))
.setWorldLevel(getFriendProfile().getWorldLevel())
.setSignature(getFriendProfile().getSignature())
.setOnlineState(
player != null && player.isOnline()
? FriendOnlineState.FRIEND_ONLINE_STATE_ONLINE
: FriendOnlineState.FRIEND_ONLINE_STATE_DISCONNECT)
.setIsMpModeAvailable(true)
.setLastActiveTime(getFriendProfile().getLastActiveTime())
.setNameCardId(getFriendProfile().getNameCard())
.setParam(getFriendProfile().getDaysSinceLogin())
.setIsGameSource(true)
.setPlatformType(PlatformTypeOuterClass.PlatformType.PLATFORM_TYPE_PC)
.setIsInDuel(getFriendProfile().isInDuel())
.setIsDuelObservable(getFriendProfile().isDuelObservable())
.build();
}
}

View File

@@ -2,14 +2,21 @@ package emu.grasscutter.game.friends;
import dev.morphia.annotations.AlsoLoad;
import dev.morphia.annotations.Entity;
import emu.grasscutter.Grasscutter;
import dev.morphia.annotations.Transient;
import emu.grasscutter.game.home.GameHome;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.FriendEnterHomeOptionOuterClass;
import emu.grasscutter.utils.Utils;
import lombok.Getter;
import org.jetbrains.annotations.Nullable;
@Entity
@Getter
public class PlayerProfile {
@Transient private Player player;
@AlsoLoad("id")
private int uid;
@@ -17,12 +24,18 @@ public class PlayerProfile {
private int avatarId;
private String name;
private String signature;
private int achievements;
private int playerLevel;
private int worldLevel;
private int lastActiveTime;
private boolean isInDuel = false; // TODO: Implement duels. (TCG)
private boolean isDuelObservable = false; // TODO: Implement duels. (TCG)
@Getter
private int enterHomeOption;
@Deprecated // Morphia only
public PlayerProfile() {}
@@ -31,50 +44,13 @@ public class PlayerProfile {
this.syncWithCharacter(player);
}
public int getUid() {
return uid;
}
@Nullable
public Player getPlayer() {
var player = Grasscutter.getGameServer().getPlayerByUid(this.getUid(), true);
this.syncWithCharacter(player);
return player;
}
public synchronized void setPlayer(Player player) {
this.player = player;
}
public String getName() {
return name;
}
public int getNameCard() {
return nameCard;
}
public int getAvatarId() {
return avatarId;
}
public String getSignature() {
return signature;
}
public int getAchievements() {
return achievements;
}
public int getPlayerLevel() {
return playerLevel;
}
public int getWorldLevel() {
return worldLevel;
}
public int getLastActiveTime() {
return lastActiveTime;
}
public void updateLastActiveTime() {
this.lastActiveTime = Utils.getCurrentSeconds();
}
@@ -83,10 +59,6 @@ public class PlayerProfile {
return (int) Math.floor((Utils.getCurrentSeconds() - getLastActiveTime()) / 86400.0);
}
public boolean isOnline() {
return this.getPlayer() != null;
}
public void syncWithCharacter(Player player) {
if (player == null) {
return;
@@ -99,7 +71,7 @@ public class PlayerProfile {
this.nameCard = player.getNameCardId();
this.playerLevel = player.getLevel();
this.worldLevel = player.getWorldLevel();
// this.achievements = 0;
this.enterHomeOption = player.tryGetHome().map(GameHome::getEnterHomeOption).orElse(FriendEnterHomeOptionOuterClass.FriendEnterHomeOption.FRIEND_ENTER_HOME_OPTION_REFUSE_VALUE);
this.updateLastActiveTime();
}
}

View File

@@ -0,0 +1,10 @@
package emu.grasscutter.game.home;
import emu.grasscutter.game.CoopRequest;
import emu.grasscutter.game.player.Player;
public class EnterHomeRequest extends CoopRequest {
public EnterHomeRequest(Player requester) {
super(requester);
}
}

View File

@@ -4,31 +4,40 @@ import dev.morphia.annotations.*;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.HomeWorldLevelData;
import emu.grasscutter.data.excels.scene.SceneData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.server.packet.send.*;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity(value = "homes", useDiscriminator = false)
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class GameHome {
@Id String id;
public static final Set<Integer> HOME_SCENE_IDS = GameData.getSceneDataMap().values().stream()
.filter(sceneData -> sceneData.getSceneType() == SceneType.SCENE_HOME_WORLD || sceneData.getSceneType() == SceneType.SCENE_HOME_ROOM)
.map(SceneData::getId).collect(Collectors.toUnmodifiableSet());
@Id
String id;
@Indexed(options = @IndexOptions(unique = true))
long ownerUid;
@Transient Player player;
@Transient
Player player;
int level;
int exp;
@@ -49,13 +58,17 @@ public class GameHome {
return home;
}
public static boolean doesHomeExist(int uid) {
return DatabaseHelper.getHomeByUid(uid) != null;
}
public static GameHome create(Integer uid) {
return GameHome.of()
.ownerUid(uid)
.level(1)
.sceneMap(new ConcurrentHashMap<>())
.unlockedHomeBgmList(new HashSet<>())
.build();
.ownerUid(uid)
.level(1)
.sceneMap(new ConcurrentHashMap<>())
.unlockedHomeBgmList(new HashSet<>())
.build();
}
public void save() {
@@ -64,19 +77,19 @@ public class GameHome {
public HomeSceneItem getHomeSceneItem(int sceneId) {
return sceneMap.computeIfAbsent(
sceneId,
e -> {
var defaultItem = GameData.getHomeworldDefaultSaveData().get(sceneId);
if (defaultItem != null) {
Grasscutter.getLogger()
.info("Set player {} home {} to initial setting", ownerUid, sceneId);
return HomeSceneItem.parseFrom(defaultItem, sceneId);
} else {
// Realm res missing bricks account, use default realm data to allow main house
defaultItem = GameData.getHomeworldDefaultSaveData().get(2001);
return HomeSceneItem.parseFrom(defaultItem, sceneId);
}
});
sceneId,
e -> {
var defaultItem = GameData.getHomeworldDefaultSaveData().get(sceneId);
if (defaultItem != null) {
Grasscutter.getLogger()
.info("Set player {} home {} to initial setting", ownerUid, sceneId);
return HomeSceneItem.parseFrom(defaultItem, sceneId);
} else {
// Realm res missing bricks account, use default realm data to allow main house
defaultItem = GameData.getHomeworldDefaultSaveData().get(2001);
return HomeSceneItem.parseFrom(defaultItem, sceneId);
}
});
}
public void onOwnerLogin(Player player) {
@@ -130,9 +143,9 @@ public class GameHome {
private Set<Integer> getDefaultUnlockedHomeBgmIds() {
return GameData.getHomeWorldBgmDataMap().int2ObjectEntrySet().stream()
.filter(e -> e.getValue().isDefaultUnlock())
.map(Int2ObjectMap.Entry::getIntKey)
.collect(Collectors.toUnmodifiableSet());
.filter(e -> e.getValue().isDefaultUnlock())
.map(Int2ObjectMap.Entry::getIntKey)
.collect(Collectors.toUnmodifiableSet());
}
// Same as Player.java addExpDirectly
@@ -171,7 +184,7 @@ public class GameHome {
// Ensure next update is at top of the hour
nextUpdateTime =
(int) ZonedDateTime.now().plusHours(1).truncatedTo(ChronoUnit.HOURS).toEpochSecond();
(int) ZonedDateTime.now().plusHours(1).truncatedTo(ChronoUnit.HOURS).toEpochSecond();
// Get resources
var hourlyResources = getComfortResources(player);
@@ -194,42 +207,42 @@ public class GameHome {
// Outdoors avatars
sceneMap
.get(player.getCurrentRealmId() + 2000)
.getBlockItems()
.forEach(
(i, e) -> {
e.getDeployNPCList()
.forEach(
id -> {
invitedAvatars.add(id.getAvatarId());
});
});
.get(player.getCurrentRealmId() + 2000)
.getBlockItems()
.forEach(
(i, e) -> {
e.getDeployNPCList()
.forEach(
id -> {
invitedAvatars.add(id.getAvatarId());
});
});
// Check as realm 5 inside is not in defaults and will be null
if (Objects.nonNull(sceneMap.get(player.getCurrentRealmId() + 2200))) {
// Indoors avatars
sceneMap
.get(player.getCurrentRealmId() + 2200)
.getBlockItems()
.forEach(
(i, e) -> {
e.getDeployNPCList()
.forEach(
id -> {
invitedAvatars.add(id.getAvatarId());
});
});
.get(player.getCurrentRealmId() + 2200)
.getBlockItems()
.forEach(
(i, e) -> {
e.getDeployNPCList()
.forEach(
id -> {
invitedAvatars.add(id.getAvatarId());
});
});
}
// Add exp to all avatars
invitedAvatars.forEach(
id -> {
var avatar = player.getAvatars().getAvatarById(id);
player
.getServer()
.getInventorySystem()
.upgradeAvatarFetterLevel(player, avatar, storedFetterExp);
});
id -> {
var avatar = player.getAvatars().getAvatarById(id);
player
.getServer()
.getInventorySystem()
.upgradeAvatarFetterLevel(player, avatar, storedFetterExp);
});
storedFetterExp = 0;
save();
@@ -253,7 +266,7 @@ public class GameHome {
storeResources(player, 0, 0);
lastUpdatedTime = clientTime;
nextUpdateTime =
(int) ZonedDateTime.now().plusHours(1).truncatedTo(ChronoUnit.HOURS).toEpochSecond();
(int) ZonedDateTime.now().plusHours(1).truncatedTo(ChronoUnit.HOURS).toEpochSecond();
save();
// Send packet

View File

@@ -4,12 +4,15 @@ import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import emu.grasscutter.data.binout.HomeworldDefaultSaveData;
import emu.grasscutter.net.proto.HomeBlockArrangementInfoOuterClass.HomeBlockArrangementInfo;
import java.util.List;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
import java.util.Collection;
import java.util.List;
import java.util.stream.Stream;
@Entity
@Data
@Builder(builderMethodName = "of")
@@ -77,6 +80,8 @@ public class HomeBlockItem {
.setIsUnlocked(unlocked)
.setComfortValue(calComfort());
this.reassignIfNull();
this.deployFurnitureList.forEach(f -> proto.addDeployFurniureList(f.toProto()));
this.persistentFurnitureList.forEach(f -> proto.addPersistentFurnitureList(f.toProto()));
this.deployAnimalList.forEach(f -> proto.addDeployAnimalList(f.toProto()));
@@ -84,4 +89,26 @@ public class HomeBlockItem {
return proto.build();
}
// TODO add more types (farm field and suite)
public List<? extends HomeMarkPointProtoFactory> getMarkPointProtoFactories() {
this.reassignIfNull();
return Stream.of(this.deployFurnitureList, this.persistentFurnitureList, this.deployNPCList).flatMap(Collection::stream).toList();
}
public void reassignIfNull() {
if (this.deployFurnitureList == null) {
this.deployFurnitureList = List.of();
}
if (this.persistentFurnitureList == null) {
this.persistentFurnitureList = List.of();
}
if (this.deployAnimalList == null) {
this.deployAnimalList = List.of();
}
if (this.deployNPCList == null) {
this.deployNPCList = List.of();
}
}
}

View File

@@ -11,12 +11,24 @@ import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
import java.util.stream.Collectors;
@Entity
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class HomeFurnitureItem {
public class HomeFurnitureItem implements HomeMarkPointProtoFactory {
public static final int PAIMON_FURNITURE_ID = 368134;
public static final int TELEPORT_FURNITURE_ID = 373501;
public static final Set<Integer> APARTMENT_FURNITURE_ID_SET = GameData.getItemDataMap().values()
.stream()
.filter(itemData -> itemData.getSpecialFurnitureType() == SpecialFurnitureType.Apartment)
.map(ItemData::getId)
.collect(Collectors.toUnmodifiableSet());
int furnitureId;
int guid;
int parentFurnitureIndex;
@@ -41,7 +53,7 @@ public class HomeFurnitureItem {
.furnitureId(homeFurniture.getId())
.parentFurnitureIndex(1)
.spawnPos(homeFurniture.getPos() == null ? new Position() : homeFurniture.getPos())
.spawnRot(homeFurniture.getRot() == null ? new Position() : homeFurniture.getRot())
.spawnRot(new Position())
.build();
}
@@ -56,17 +68,6 @@ public class HomeFurnitureItem {
.build();
}
public HomeMarkPointFurnitureDataOuterClass.HomeMarkPointFurnitureData toMarkPointProto(
int type) {
return HomeMarkPointFurnitureDataOuterClass.HomeMarkPointFurnitureData.newBuilder()
.setFurnitureId(furnitureId)
.setGuid(guid)
.setFurnitureType(type)
.setPos(spawnPos.toProto())
// TODO NPC and farm
.build();
}
public ItemData getAsItem() {
return GameData.getItemDataMap().get(this.furnitureId);
}
@@ -79,4 +80,29 @@ public class HomeFurnitureItem {
}
return item.getComfort();
}
@Nullable
@Override
public HomeMarkPointFurnitureDataOuterClass.HomeMarkPointFurnitureData toMarkPointProto() {
var type = this.adjustByFurnitureId();
if (type == SpecialFurnitureType.NOT_SPECIAL) {
return null;
}
return HomeMarkPointFurnitureDataOuterClass.HomeMarkPointFurnitureData.newBuilder()
.setFurnitureId(this.furnitureId)
.setFurnitureType(type.getValue())
.setPos(this.spawnPos.toProto())
.setGuid(this.guid)
.build();
}
@Override
public SpecialFurnitureType adjustByFurnitureId() {
return switch (this.furnitureId) {
case PAIMON_FURNITURE_ID -> SpecialFurnitureType.Paimon;
case TELEPORT_FURNITURE_ID -> SpecialFurnitureType.TeleportPoint;
default -> APARTMENT_FURNITURE_ID_SET.contains(this.furnitureId) ? SpecialFurnitureType.Apartment : SpecialFurnitureType.NOT_SPECIAL;
};
}
}

View File

@@ -0,0 +1,21 @@
package emu.grasscutter.game.home;
import emu.grasscutter.net.proto.HomeMarkPointFurnitureDataOuterClass;
import org.jetbrains.annotations.Nullable;
public interface HomeMarkPointProtoFactory {
@Nullable
HomeMarkPointFurnitureDataOuterClass.HomeMarkPointFurnitureData toMarkPointProto();
default SpecialFurnitureType adjustByFurnitureId() {
return this.getType();
}
default SpecialFurnitureType getType() {
return SpecialFurnitureType.NOT_SPECIAL;
}
default boolean isProtoConvertible() {
return this.adjustByFurnitureId() != SpecialFurnitureType.NOT_SPECIAL;
}
}

View File

@@ -1,18 +1,23 @@
package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.world.Position;
import emu.grasscutter.net.proto.HomeMarkPointFurnitureDataOuterClass;
import emu.grasscutter.net.proto.HomeMarkPointNPCDataOuterClass;
import emu.grasscutter.net.proto.HomeNpcDataOuterClass;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
import org.jetbrains.annotations.Nullable;
@Entity
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class HomeNPCItem {
public class HomeNPCItem implements HomeMarkPointProtoFactory {
transient int furnitureId;
int avatarId;
Position spawnPos;
Position spawnRot;
@@ -20,19 +25,52 @@ public class HomeNPCItem {
public static HomeNPCItem parseFrom(HomeNpcDataOuterClass.HomeNpcData homeNpcData) {
return HomeNPCItem.of()
.avatarId(homeNpcData.getAvatarId())
.spawnPos(new Position(homeNpcData.getSpawnPos()))
.spawnRot(new Position(homeNpcData.getSpawnRot()))
.costumeId(homeNpcData.getCostumeId())
.build();
.avatarId(homeNpcData.getAvatarId())
.spawnPos(new Position(homeNpcData.getSpawnPos()))
.spawnRot(new Position(homeNpcData.getSpawnRot()))
.costumeId(homeNpcData.getCostumeId())
.build();
}
public HomeNpcDataOuterClass.HomeNpcData toProto() {
return HomeNpcDataOuterClass.HomeNpcData.newBuilder()
.setAvatarId(avatarId)
.setSpawnPos(spawnPos.toProto())
.setSpawnRot(spawnRot.toProto())
.setCostumeId(costumeId)
.build();
.setAvatarId(avatarId)
.setSpawnPos(spawnPos.toProto())
.setSpawnRot(spawnRot.toProto())
.setCostumeId(costumeId)
.build();
}
public int getFurnitureId() {
if (this.furnitureId == 0) {
var data = GameData.getHomeWorldNPCDataMap().get(this.avatarId);
this.furnitureId = data == null ? -1 : data.getFurnitureID();
}
return this.furnitureId;
}
@Nullable
@Override
public HomeMarkPointFurnitureDataOuterClass.HomeMarkPointFurnitureData toMarkPointProto() {
return HomeMarkPointFurnitureDataOuterClass.HomeMarkPointFurnitureData.newBuilder()
.setFurnitureId(this.getFurnitureId())
.setFurnitureType(this.getType().getValue())
.setPos(this.spawnPos.toProto())
.setNpcData(HomeMarkPointNPCDataOuterClass.HomeMarkPointNPCData.newBuilder()
.setAvatarId(this.avatarId)
.setCostumeId(this.costumeId)
.build())
.build();
}
@Override
public SpecialFurnitureType getType() {
return SpecialFurnitureType.NPC;
}
@Override
public boolean isProtoConvertible() {
return this.getFurnitureId() > 0;
}
}

View File

@@ -6,13 +6,14 @@ import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.binout.HomeworldDefaultSaveData;
import emu.grasscutter.game.world.Position;
import emu.grasscutter.net.proto.HomeSceneArrangementInfoOuterClass.HomeSceneArrangementInfo;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
import java.util.Map;
import java.util.stream.Collectors;
@Entity
@Data
@Builder(builderMethodName = "of")
@@ -64,12 +65,16 @@ public class HomeSceneItem {
}
public int getRoomSceneId() {
if (mainHouse == null || mainHouse.getAsItem() == null) {
if (this.isRoom()) {
return 0;
}
return mainHouse.getAsItem().getRoomSceneId();
}
public boolean isRoom() {
return mainHouse == null || mainHouse.getAsItem() == null;
}
public int calComfort() {
return this.blockItems.values().stream().mapToInt(HomeBlockItem::calComfort).sum();
}

View File

@@ -0,0 +1,162 @@
package emu.grasscutter.game.home;
import emu.grasscutter.game.entity.EntityTeam;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.game.world.World;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.proto.ChatInfoOuterClass;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketDelTeamEntityNotify;
import emu.grasscutter.server.packet.send.PacketPlayerChatNotify;
import lombok.Getter;
import java.util.List;
public class HomeWorld extends World {
@Getter
private final GameHome home;
public HomeWorld(GameServer server, Player owner) {
super(server, owner);
this.home = owner.isOnline() ? owner.getHome() : GameHome.getByUid(owner.getUid());
server.registerHomeWorld(this);
}
@Override
public synchronized void addPlayer(Player player) {
// Check if player already in
if (this.getPlayers().contains(player)) {
return;
}
// Remove player from prev world
if (player.getWorld() != null) {
player.getWorld().removePlayer(player);
}
// Register
player.setWorld(this);
this.getPlayers().add(player);
// Set player variables
if (this.getHost().equals(player)) {
player.setPeerId(1);
this.getGuests().forEach(player1 -> player1.setPeerId(player1.getPeerId() + 1));
} else {
player.setPeerId(this.getNextPeerId());
}
player.getTeamManager().setEntity(new EntityTeam(player));
// Copy main team to multiplayer team
if (this.isMultiplayer()) {
player
.getTeamManager()
.getMpTeam()
.copyFrom(
player.getTeamManager().getCurrentSinglePlayerTeamInfo(),
player.getTeamManager().getMaxTeamSize());
player.getTeamManager().setCurrentCharacterIndex(0);
if (!player.equals(this.getHost())) {
this.broadcastPacket(
new PacketPlayerChatNotify(
player,
0,
ChatInfoOuterClass.ChatInfo.SystemHint.newBuilder()
.setType(ChatInfoOuterClass.ChatInfo.SystemHintType.SYSTEM_HINT_TYPE_CHAT_ENTER_WORLD.getNumber())
.build()));
}
}
// Add to scene
var scene = this.getSceneById(player.getSceneId());
scene.addPlayer(player);
// Info packet for other players
if (this.getPlayers().size() > 1) {
this.updatePlayerInfos(player);
}
}
@Override
public synchronized void removePlayer(Player player) {
// Remove team entities
this.broadcastPacket(
new PacketDelTeamEntityNotify(
player.getSceneId(),
this.getPlayers().stream()
.map(
p ->
p.getTeamManager().getEntity() == null
? 0
: p.getTeamManager().getEntity().getId())
.toList()));
// Deregister
this.getPlayers().remove(player);
player.setWorld(null);
// Remove from scene
Scene scene = this.getSceneById(player.getSceneId());
scene.removePlayer(player);
// Info packet for other players
if (this.getPlayers().size() > 0) {
this.updatePlayerInfos(player);
}
this.broadcastPacket(
new PacketPlayerChatNotify(
player,
0,
ChatInfoOuterClass.ChatInfo.SystemHint.newBuilder()
.setType(ChatInfoOuterClass.ChatInfo.SystemHintType.SYSTEM_HINT_TYPE_CHAT_LEAVE_WORLD.getNumber())
.build()));
}
@Override
public int getNextPeerId() {
return this.getPlayers().size() + 1;
}
@Override
public synchronized void setHost(Player host) {
super.setHost(host);
}
@Override
public final boolean isMultiplayer() {
return true;
}
@Override
public final boolean isPaused() {
return false;
}
@Override
public final boolean isTimeLocked() {
return false;
}
public int getOwnerUid() {
return this.getHost().getUid();
}
public List<Player> getGuests() {
return this.getPlayers().stream().filter(player -> !player.equals(this.getHost())).toList();
}
public boolean isInHome(Player player) {
return this.getPlayers().contains(player);
}
public void sendPacketToHostIfOnline(BasePacket basePacket) {
if (this.getHost().isOnline()) {
this.getHost().sendPacket(basePacket);
}
}
}

View File

@@ -0,0 +1,187 @@
package emu.grasscutter.game.home;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.EnterReason;
import emu.grasscutter.game.world.Position;
import emu.grasscutter.game.world.World;
import emu.grasscutter.game.world.data.TeleportProperties;
import emu.grasscutter.net.proto.EnterTypeOuterClass;
import emu.grasscutter.net.proto.OtherPlayerEnterHomeNotifyOuterClass;
import emu.grasscutter.net.proto.PlayerApplyEnterHomeResultNotifyOuterClass;
import emu.grasscutter.net.proto.RetcodeOuterClass;
import emu.grasscutter.server.event.player.PlayerEnterHomeEvent;
import emu.grasscutter.server.event.player.PlayerLeaveHomeEvent;
import emu.grasscutter.server.event.player.PlayerTeleportEvent;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.*;
public class HomeWorldMPSystem extends BaseGameSystem {
public HomeWorldMPSystem(GameServer server) {
super(server);
}
public void sendEnterHomeRequest(Player requester, int ownerUid) {
var owner = getServer().getPlayerByUid(ownerUid);
if (owner == null) {
requester.sendPacket(new PacketPlayerApplyEnterHomeResultNotify(ownerUid, "", false, PlayerApplyEnterHomeResultNotifyOuterClass.PlayerApplyEnterHomeResultNotify.Reason.OPEN_STATE_NOT_OPEN));
requester.sendPacket(new PacketTryEnterHomeRsp());
return;
}
if (owner.getRealmList() == null) {
requester.sendPacket(new PacketPlayerApplyEnterHomeResultNotify(ownerUid, "", false, PlayerApplyEnterHomeResultNotifyOuterClass.PlayerApplyEnterHomeResultNotify.Reason.OPEN_STATE_NOT_OPEN));
requester.sendPacket(new PacketTryEnterHomeRsp());
return;
}
var request = owner.getEnterHomeRequests().get(requester.getUid());
if (request != null && !request.isExpired()) {
return;
}
if (owner.isInEditMode()) {
requester.sendPacket(new PacketPlayerApplyEnterHomeResultNotify(ownerUid, owner.getNickname(), false, PlayerApplyEnterHomeResultNotifyOuterClass.PlayerApplyEnterHomeResultNotify.Reason.HOST_IN_EDIT_MODE));
requester.sendPacket(new PacketTryEnterHomeRsp());
return;
}
request = new EnterHomeRequest(requester);
owner.getEnterHomeRequests().put(requester.getUid(), request);
owner.sendPacket(new PacketPlayerApplyEnterHomeNotify(requester));
}
public void acceptEnterHomeRequest(Player owner, int requesterUid, boolean isAgreed) {
var request = owner.getEnterHomeRequests().get(requesterUid);
if (request == null || request.isExpired()) {
return;
}
var requester = request.getRequester();
owner.getEnterHomeRequests().remove(requesterUid);
if (requester.getWorld().isMultiplayer()) {
requester.sendPacket(new PacketPlayerApplyEnterHomeResultNotify(owner.getUid(), owner.getNickname(), false, PlayerApplyEnterHomeResultNotifyOuterClass.PlayerApplyEnterHomeResultNotify.Reason.SYSTEM_JUDGE));
requester.sendPacket(new PacketTryEnterHomeRsp());
return;
}
requester.sendPacket(new PacketPlayerApplyEnterHomeResultNotify(owner.getUid(), owner.getNickname(), isAgreed, PlayerApplyEnterHomeResultNotifyOuterClass.PlayerApplyEnterHomeResultNotify.Reason.PLAYER_JUDGE));
if (!isAgreed) {
requester.sendPacket(new PacketTryEnterHomeRsp());
return;
}
this.enterHome(requester, owner);
}
public void enterHome(Player requester, Player owner) {
if (requester.getWorld().isMultiplayer()) {
return;
}
if (owner.getRealmList() == null) {
// should never happen
requester.sendPacket(new PacketTryEnterHomeRsp(RetcodeOuterClass.Retcode.RET_HOME_NOT_FOUND_IN_MEM_VALUE, owner.getUid()));
return;
}
var world = this.server.getHomeWorldOrCreate(owner);
var targetHome = world.getHome();
var event = new PlayerEnterHomeEvent(requester, owner, targetHome);
event.call();
if (event.isCanceled()) {
requester.sendPacket(new PacketTryEnterHomeRsp(RetcodeOuterClass.Retcode.RET_HOME_OWNER_REFUSE_TO_ENTER_HOME_VALUE, owner.getUid()));
return;
}
if (owner.isInEditMode()) {
requester.sendPacket(new PacketTryEnterHomeRsp(RetcodeOuterClass.Retcode.RET_HOME_CANT_ENTER_BY_IN_EDIT_MODE_VALUE, owner.getUid()));
return;
}
int realmId = 2000 + owner.getCurrentRealmId();
targetHome.getHomeSceneItem(realmId);
targetHome.save();
var pos = world.getSceneById(realmId).getScriptManager().getConfig().born_pos;
requester.getPrevPosForHome().set(requester.getPosition());
requester.setCurHomeWorld(world);
requester.setPrevScene(requester.getSceneId());
world.addPlayer(requester, realmId);
requester.setSceneId(realmId);
requester.getPosition().set(pos);
requester.sendPacket(new PacketPlayerEnterSceneNotify(requester, owner.getUid(), TeleportProperties.builder().sceneId(realmId).enterReason(EnterReason.EnterHome).teleportTo(pos).teleportType(PlayerTeleportEvent.TeleportType.INTERNAL).build(), !requester.equals(owner)));
requester.sendPacket(new PacketTryEnterHomeRsp(owner.getUid()));
requester.setHasSentInitPacketInHome(false);
world.getPlayers().stream()
.filter(player -> !player.equals(requester))
.forEach(player -> player.sendPacket(new PacketPlayerPreEnterMpNotify(requester)));
}
public boolean leaveCoop(Player player, int prevScene) {
return this.leaveCoop(player, prevScene, player.getPrevPosForHome());
}
public boolean leaveCoop(Player player, int prevScene, Position pos) {
// Make sure everyone's scene is loaded
for (var p : player.getWorld().getPlayers()) {
if (p.getSceneLoadState() != Player.SceneLoadState.LOADED) {
return false;
}
}
// Event
var event = new PlayerLeaveHomeEvent(player, player.getCurHomeWorld().getHost(), player.getCurHomeWorld().getHome(), PlayerLeaveHomeEvent.Reason.PLAYER_LEAVE);
event.call();
player.getPosition().set(pos);
var world = new World(player);
world.addPlayer(player, prevScene);
player.getCurHomeWorld().sendPacketToHostIfOnline(new PacketOtherPlayerEnterOrLeaveHomeNotify(player, OtherPlayerEnterHomeNotifyOuterClass.OtherPlayerEnterHomeNotify.Reason.LEAVE));
player.setCurHomeWorld(this.server.getHomeWorldOrCreate(player));
player.sendPacket(new PacketPlayerEnterSceneNotify(player, EnterTypeOuterClass.EnterType.ENTER_TYPE_BACK, EnterReason.TeamBack, prevScene, pos));
return true;
}
public boolean kickPlayerFromHome(Player owner, int targetUid) {
// Make sure player's world is multiplayer and that player is owner
if (!owner.getCurHomeWorld().getHost().equals(owner)) {
return false;
}
// Get victim and sanity checks
var victim = owner.getServer().getPlayerByUid(targetUid);
if (victim == null || owner.equals(victim)) {
return false;
}
// Make sure victim's scene has loaded
if (victim.getSceneLoadState() != Player.SceneLoadState.LOADED) {
return false;
}
// Event
var event = new PlayerLeaveHomeEvent(victim, owner, victim.getCurHomeWorld().getHome(), PlayerLeaveHomeEvent.Reason.KICKED);
event.call();
// Kick
victim.getPosition().set(victim.getPrevPosForHome());
var world = new World(victim);
world.addPlayer(victim, 3);
victim.getCurHomeWorld().sendPacketToHostIfOnline(new PacketOtherPlayerEnterOrLeaveHomeNotify(victim, OtherPlayerEnterHomeNotifyOuterClass.OtherPlayerEnterHomeNotify.Reason.LEAVE));
victim.setCurHomeWorld(this.server.getHomeWorldOrCreate(victim));
victim.sendPacket(new PacketPlayerEnterSceneNotify(victim, EnterTypeOuterClass.EnterType.ENTER_TYPE_BACK, EnterReason.TeamKick, victim.getScene().getId(), victim.getPrevPosForHome()));
return true;
}
}

View File

@@ -0,0 +1,20 @@
package emu.grasscutter.game.home;
import lombok.Getter;
@Getter
public enum SpecialFurnitureType {
NOT_SPECIAL(-1),
FarmField(2),
TeleportPoint(3),
NPC(5),
Apartment(6),
FurnitureSuite(7),
Paimon(8);
private final int value;
SpecialFurnitureType(int value) {
this.value = value;
}
}

View File

@@ -17,15 +17,26 @@ import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.expedition.ExpeditionInfo;
import emu.grasscutter.game.friends.*;
import emu.grasscutter.game.gacha.PlayerGachaInfo;
import emu.grasscutter.game.home.EnterHomeRequest;
import emu.grasscutter.game.home.GameHome;
import emu.grasscutter.game.inventory.*;
import emu.grasscutter.game.mail.*;
import emu.grasscutter.game.managers.*;
import emu.grasscutter.game.managers.cooking.*;
import emu.grasscutter.game.home.HomeWorld;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.Inventory;
import emu.grasscutter.game.mail.Mail;
import emu.grasscutter.game.mail.MailHandler;
import emu.grasscutter.game.managers.FurnitureManager;
import emu.grasscutter.game.managers.ResinManager;
import emu.grasscutter.game.managers.SatiationManager;
import emu.grasscutter.game.managers.SotSManager;
import emu.grasscutter.game.managers.cooking.ActiveCookCompoundData;
import emu.grasscutter.game.managers.cooking.CookingCompoundManager;
import emu.grasscutter.game.managers.cooking.CookingManager;
import emu.grasscutter.game.managers.deforestation.DeforestationManager;
import emu.grasscutter.game.managers.energy.EnergyManager;
import emu.grasscutter.game.managers.forging.*;
import emu.grasscutter.game.managers.mapmark.*;
import emu.grasscutter.game.managers.forging.ActiveForgeData;
import emu.grasscutter.game.managers.forging.ForgingManager;
import emu.grasscutter.game.managers.mapmark.MapMark;
import emu.grasscutter.game.managers.mapmark.MapMarksManager;
import emu.grasscutter.game.managers.stamina.StaminaManager;
import emu.grasscutter.game.props.*;
import emu.grasscutter.game.quest.QuestManager;
@@ -47,12 +58,14 @@ import emu.grasscutter.net.proto.ProfilePictureOuterClass.ProfilePicture;
import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason;
import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail;
import emu.grasscutter.plugin.api.PlayerHook;
import emu.grasscutter.scripts.ScriptLoader;
import emu.grasscutter.scripts.data.SceneRegion;
import emu.grasscutter.server.event.player.*;
import emu.grasscutter.server.game.*;
import emu.grasscutter.server.game.GameSession.SessionState;
import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.*;
import emu.grasscutter.utils.DispatchUtils;
import emu.grasscutter.utils.Utils;
import emu.grasscutter.utils.helpers.DateHelper;
import emu.grasscutter.utils.objects.FieldFetch;
import it.unimi.dsi.fastutil.ints.*;
@@ -79,6 +92,8 @@ public class Player implements PlayerHook, FieldFetch {
@Getter private int nameCardId = 210001;
@Getter private Position position;
@Getter @Setter private Position prevPos;
@Getter @Setter private Position prevPosForHome;
@Getter @Setter private int prevScene;
@Getter private Position rotation;
@Getter private PlayerBirthday birthday;
@Getter private PlayerCodex codex;
@@ -87,6 +102,7 @@ public class Player implements PlayerHook, FieldFetch {
@Getter @Setter private List<Integer> showNameCardList;
@Getter private Map<Integer, Integer> properties;
@Getter @Setter private int currentRealmId;
@Getter @Setter private transient boolean isInEditMode;
@Getter @Setter private int widgetId;
@Getter @Setter private int sceneId;
@Getter @Setter private int regionId;
@@ -119,6 +135,8 @@ public class Player implements PlayerHook, FieldFetch {
@Transient private long nextGuid = 0;
@Transient @Getter @Setter private int peerId;
@Transient private World world; // Synchronized getter and setter
@Transient @Getter @Setter private HomeWorld curHomeWorld;
@Transient @Getter @Setter private boolean hasSentInitPacketInHome;
@Transient private Scene scene; // Synchronized getter and setter
@Transient @Getter private int weatherId = 0;
@Transient @Getter private ClimateType climate = ClimateType.CLIMATE_SUNNY;
@@ -173,6 +191,7 @@ public class Player implements PlayerHook, FieldFetch {
@Transient @Getter @Setter private SceneLoadState sceneLoadState = SceneLoadState.NONE;
@Transient private boolean hasSentLoginPackets;
@Transient private long nextSendPlayerLocTime = 0;
@Getter private transient final Int2ObjectMap<EnterHomeRequest> enterHomeRequests;
private transient final Int2ObjectMap<CoopRequest> coopRequests; // Synchronized getter
@Getter private transient final Queue<AttackResult> attackResults;
@@ -209,6 +228,7 @@ public class Player implements PlayerHook, FieldFetch {
this.buffManager = new PlayerBuffManager(this);
this.position = new Position(GameConstants.START_POSITION);
this.prevPos = new Position();
this.prevPosForHome = Position.ZERO;
this.rotation = new Position(0, 307, 0);
this.sceneId = 3;
this.regionId = 1;
@@ -245,6 +265,7 @@ public class Player implements PlayerHook, FieldFetch {
this.attackResults = new LinkedBlockingQueue<>();
this.coopRequests = new Int2ObjectOpenHashMap<>();
this.enterHomeRequests = new Int2ObjectOpenHashMap<>();
this.combatInvokeHandler = new InvokeHandler(PacketCombatInvocationsNotify.class);
this.abilityInvokeHandler = new InvokeHandler(PacketAbilityInvocationsNotify.class);
this.clientAbilityInitFinishHandler = new InvokeHandler(PacketClientAbilityInitFinishNotify.class);
@@ -465,6 +486,18 @@ public class Player implements PlayerHook, FieldFetch {
this.seenRealmList.add(seenId);
}
public Optional<GameHome> tryGetHome() {
if (this.isOnline()) {
return Optional.ofNullable(this.home);
}
if (GameHome.doesHomeExist(this.getUid())) {
this.home = GameHome.getByUid(this.getUid());
}
return Optional.ofNullable(this.home);
}
public int getExpeditionLimit() {
final int CONST_VALUE_EXPEDITION_INIT_LIMIT = 2; // TODO: pull from ConstValueExcelConfigData.json
int expeditionLimit = CONST_VALUE_EXPEDITION_INIT_LIMIT;
@@ -687,7 +720,7 @@ public class Player implements PlayerHook, FieldFetch {
this.getQuestManager().forEachActiveQuest(quest -> {
if (quest.getTriggerData() != null &&
quest.getTriggers().containsKey(enterRegionName) &&
region.getGroupId() == quest.getTriggerData().get(enterRegionName).getGroupId()) {
region.getGroupId() == quest.getTriggerData().get(enterRegionName).getGroupId()) {
// If trigger hasn't been fired yet
if (!Boolean.TRUE.equals(quest.getTriggers().put(enterRegionName, true))) {
this.getSession().send(new PacketServerCondMeetQuestListUpdateNotify());
@@ -1175,6 +1208,21 @@ public class Player implements PlayerHook, FieldFetch {
return true;
}
private boolean expireEnterHomeRequest(EnterHomeRequest req) {
return this.expireEnterHomeRequest(req, false);
}
private boolean expireEnterHomeRequest(EnterHomeRequest req, boolean force) {
if (!req.isExpired() && !force) return false;
req.getRequester().sendPacket(new PacketPlayerApplyEnterHomeResultNotify(
this.getUid(),
this.getNickname(),
false,
PlayerApplyEnterHomeResultNotifyOuterClass.PlayerApplyEnterHomeResultNotify.Reason.SYSTEM_JUDGE));
req.getRequester().sendPacket(new PacketTryEnterHomeRsp());
return true;
}
public synchronized void onTick() {
// Check ping
if (this.getLastPingTime() > System.currentTimeMillis() + 60000) {
@@ -1183,6 +1231,8 @@ public class Player implements PlayerHook, FieldFetch {
}
// Check co-op requests
this.getCoopRequests().values().removeIf(this::expireCoopRequest);
// Check enter-home requests
this.getEnterHomeRequests().values().removeIf(this::expireEnterHomeRequest);
// Handle buff
this.getBuffManager().onTick();
// Ping
@@ -1339,6 +1389,16 @@ public class Player implements PlayerHook, FieldFetch {
}
*/
if (GameHome.HOME_SCENE_IDS.contains(this.getSceneId())) {
this.setSceneId(this.prevScene <= 0 ? 3 : this.prevScene); // if the player in home, make the player go back.
var pos = this.getPrevPosForHome();
if (pos.equals(Position.ZERO)) {
pos = ScriptLoader.getSceneMeta(this.getSceneId()).config.born_pos;
}
this.position.set(pos);
}
// Create world
World world = new World(this);
world.addPlayer(this);
@@ -1387,7 +1447,10 @@ public class Player implements PlayerHook, FieldFetch {
this.furnitureManager.onLogin();
// Home
home = GameHome.getByUid(getUid());
var homeWorld = this.getServer().getHomeWorldOrCreate(this);
homeWorld.setHost(this); // synchronize player object if homeWorld already exists in the server.
this.home = homeWorld.getHome();
this.setCurHomeWorld(homeWorld);
home.onOwnerLogin(this);
// Activity
this.activityManager = new ActivityManager(this);
@@ -1411,7 +1474,6 @@ public class Player implements PlayerHook, FieldFetch {
// register
getServer().registerPlayer(this);
getProfile().setPlayer(this); // Set online
}
public void onLogout() {
@@ -1432,9 +1494,10 @@ public class Player implements PlayerHook, FieldFetch {
// Status stuff
this.getProfile().syncWithCharacter(this);
this.getProfile().setPlayer(null); // Set offline
this.getCoopRequests().clear();
this.getEnterHomeRequests().values().forEach(req -> this.expireEnterHomeRequest(req, true));
this.getEnterHomeRequests().clear();
// Save to db
this.save();

View File

@@ -1,31 +1,47 @@
package emu.grasscutter.game.player;
import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
import dev.morphia.annotations.*;
import emu.grasscutter.*;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient;
import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.*;
import emu.grasscutter.game.props.*;
import emu.grasscutter.game.world.*;
import emu.grasscutter.net.packet.*;
import emu.grasscutter.net.proto.*;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.entity.EntityBaseGadget;
import emu.grasscutter.game.entity.EntityTeam;
import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.game.props.EnterReason;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.world.Position;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.game.world.World;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.AbilityControlBlockOuterClass;
import emu.grasscutter.net.proto.AbilityEmbryoOuterClass;
import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType;
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.net.proto.TrialAvatarGrantRecordOuterClass.TrialAvatarGrantRecord.GrantReason;
import emu.grasscutter.net.proto.VisionTypeOuterClass;
import emu.grasscutter.server.event.entity.EntityCreationEvent;
import emu.grasscutter.server.event.player.*;
import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.*;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import lombok.Getter;
import lombok.Setter;
import lombok.val;
import java.util.*;
import java.util.stream.Stream;
import lombok.*;
import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
@Entity
public final class TeamManager extends BasePlayerDataManager {
@@ -350,8 +366,8 @@ public final class TeamManager extends BasePlayerDataManager {
/** Updates all properties of the active team. */
public void updateTeamProperties() {
this.updateTeamResonances(); // Update team resonances.
this.getPlayer()
.sendPacket(new PacketSceneTeamUpdateNotify(this.getPlayer())); // Notify the player.
this.getWorld()
.broadcastPacket(new PacketSceneTeamUpdateNotify(this.getPlayer())); // Notify the all players in the world.
// Skill charges packet - Yes, this is official server behavior as of 2.6.0
this.getActiveTeam().stream()

View File

@@ -10,7 +10,10 @@ import java.util.stream.Stream;
import lombok.Getter;
public enum ElementType implements IntValueEnum {
None(0, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY),
None(
0,
FightProperty.FIGHT_PROP_CUR_WIND_ENERGY,
FightProperty.FIGHT_PROP_MAX_WIND_ENERGY),
Fire(
1,
FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY,
@@ -90,7 +93,7 @@ public enum ElementType implements IntValueEnum {
@Getter private final int configHash;
ElementType(int value, FightProperty curEnergyProp, FightProperty maxEnergyProp) {
this(value, curEnergyProp, maxEnergyProp, 0, null, 1);
this(value, curEnergyProp, maxEnergyProp, 0, null, 0);
}
ElementType(

View File

@@ -14,6 +14,7 @@ public class ItemUseUnlockHomeModule extends ItemUseInt {
@Override
public boolean useItem(UseItemParams params) {
return false;
params.player.addRealmList(this.i);
return true;
}
}

View File

@@ -104,6 +104,11 @@ public class MultiplayerSystem extends BaseGameSystem {
}
public boolean leaveCoop(Player player) {
// Make sure player is not in home
if (player.getCurHomeWorld().isInHome(player)) {
return false;
}
// Make sure player's world is multiplayer
if (!player.getWorld().isMultiplayer()) {
return false;

View File

@@ -1,7 +1,8 @@
package emu.grasscutter.game.world;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.*;
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.ItemData;
@@ -11,32 +12,44 @@ import emu.grasscutter.data.excels.scene.SceneData;
import emu.grasscutter.data.excels.world.WorldLevelData;
import emu.grasscutter.data.server.Grid;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.dungeons.*;
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.inventory.GameItem;
import emu.grasscutter.game.managers.blossom.BlossomManager;
import emu.grasscutter.game.player.*;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.player.TeamInfo;
import emu.grasscutter.game.props.*;
import emu.grasscutter.game.quest.QuestGroupSuite;
import emu.grasscutter.game.world.data.TeleportProperties;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.proto.*;
import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult;
import emu.grasscutter.net.proto.EnterTypeOuterClass;
import emu.grasscutter.net.proto.SelectWorktopOptionReqOuterClass;
import emu.grasscutter.net.proto.VisionTypeOuterClass.VisionType;
import emu.grasscutter.scripts.*;
import emu.grasscutter.scripts.SceneIndexManager;
import emu.grasscutter.scripts.SceneScriptManager;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.*;
import emu.grasscutter.scripts.data.SceneBlock;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.event.entity.EntityCreationEvent;
import emu.grasscutter.server.event.player.PlayerTeleportEvent;
import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.server.scheduler.ServerTaskScheduler;
import emu.grasscutter.utils.objects.KahnsSort;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import lombok.Getter;
import lombok.Setter;
import lombok.val;
import javax.annotation.Nullable;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import lombok.*;
@@ -259,6 +272,13 @@ public final class Scene {
this.removeEntity(gadget);
}
// Remove player widget gadgets
this.getEntities().values().stream()
.filter(gameEntity -> gameEntity instanceof EntityVehicle)
.map(gameEntity -> (EntityVehicle) gameEntity)
.filter(entityVehicle -> entityVehicle.getOwner().equals(player))
.forEach(entityVehicle -> this.removeEntity(entityVehicle, VisionType.VISION_TYPE_REMOVE));
// Deregister scene if not in use
if (this.getPlayerCount() <= 0 && !this.dontDestroyWhenEmpty) {
this.getScriptManager().onDestroy();

View File

@@ -1,17 +1,20 @@
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.dungeon.DungeonData;
import emu.grasscutter.game.entity.*;
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.*;
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.game.quest.enums.QuestContent;
import emu.grasscutter.game.world.data.TeleportProperties;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.proto.ChatInfoOuterClass.ChatInfo.*;
import emu.grasscutter.net.proto.ChatInfoOuterClass.ChatInfo.SystemHint;
import emu.grasscutter.net.proto.ChatInfoOuterClass.ChatInfo.SystemHintType;
import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType;
import emu.grasscutter.scripts.data.SceneConfig;
import emu.grasscutter.server.event.player.PlayerTeleportEvent;
@@ -19,14 +22,23 @@ 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 it.unimi.dsi.fastutil.ints.*;
import java.util.*;
import lombok.*;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.Getter;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import static emu.grasscutter.server.event.player.PlayerTeleportEvent.TeleportType.SCRIPT;
public class World implements Iterable<Player> {
@Getter private final GameServer server;
@Getter private final Player host;
@Getter private Player host;
@Getter private final List<Player> players;
@Getter private final Int2ObjectMap<Scene> scenes;
@@ -65,6 +77,15 @@ public class World implements Iterable<Player> {
this.host.getServer().registerWorld(this);
}
public World(GameServer server, Player owner) {
this.server = server;
this.host = owner;
this.players = Collections.synchronizedList(new ArrayList<>());
this.scenes = Int2ObjectMaps.synchronize(new Int2ObjectOpenHashMap<>());
this.entity = new EntityWorld(this);
this.lastUpdateTime = System.currentTimeMillis();
}
public int getLevelEntityId() {
return entity.getId();
}
@@ -90,6 +111,10 @@ public class World implements Iterable<Player> {
this.worldLevel = worldLevel;
}
protected synchronized void setHost(Player host) {
this.host = host;
}
/**
* Gets an associated scene by ID. Creates a new instance of the scene if it doesn't exist.
*
@@ -179,6 +204,58 @@ public class World implements Iterable<Player> {
}
}
public synchronized void addPlayer(Player player, int newSceneId) {
// Check if player already in
if (this.getPlayers().contains(player)) {
return;
}
// Remove player from prev world
if (player.getWorld() != null) {
player.getWorld().removePlayer(player);
}
// Register
player.setWorld(this);
this.getPlayers().add(player);
// Set player variables
player.setPeerId(this.getNextPeerId());
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().setCurrentCharacterIndex(0);
if (player != this.getHost()) {
this.broadcastPacket(
new PacketPlayerChatNotify(
player,
0,
SystemHint.newBuilder()
.setType(SystemHintType.SYSTEM_HINT_TYPE_CHAT_ENTER_WORLD.getNumber())
.build()));
}
}
// Add to scene
player.setSceneId(newSceneId);
Scene scene = this.getSceneById(player.getSceneId());
scene.addPlayer(player);
// Info packet for other players
if (this.getPlayers().size() > 1) {
this.updatePlayerInfos(player);
}
}
public synchronized void removePlayer(Player player) {
// Remove team entities
player.sendPacket(
@@ -389,7 +466,7 @@ public class World implements Iterable<Player> {
return true;
}
private void updatePlayerInfos(Player paramPlayer) {
protected void updatePlayerInfos(Player paramPlayer) {
for (Player player : this.getPlayers()) {
// Dont send packets if player is logging in and filter out joining player
if (!player.hasSentLoginPackets() || player == paramPlayer) {
@@ -408,7 +485,7 @@ public class World implements Iterable<Player> {
}
// 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));