feat: implement teapot suite (#2344)

* feat: implement teapot suite

* fix: home animals, check respawn, etc

* fix: NPE and cancel summon events

* fix: forgot to send eventId also
This commit is contained in:
hamusuke
2023-09-08 12:34:03 +09:00
committed by GitHub
parent 83602f78ae
commit fc42f665a7
36 changed files with 1125 additions and 210 deletions

View File

@@ -10,12 +10,14 @@ import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify;
import emu.grasscutter.server.packet.send.PacketSceneEntityDisappearNotify;
import lombok.Getter;
import java.util.concurrent.atomic.AtomicBoolean;
public class EntityHomeAnimal extends EntityMonster implements Rebornable {
private int rebornCDTickCount;
private final Position rebornPos;
@Getter private final int rebirth;
@Getter private final int rebirthCD;
private boolean disappeared;
private final AtomicBoolean disappeared = new AtomicBoolean();
public EntityHomeAnimal(Scene scene, HomeWorldAnimalData data, Position pos) {
super(scene, GameData.getMonsterDataMap().get(data.getMonsterID()), pos, 1);
@@ -60,13 +62,13 @@ public class EntityHomeAnimal extends EntityMonster implements Rebornable {
new PacketSceneEntityDisappearNotify(
this, VisionTypeOuterClass.VisionType.VISION_TYPE_REMOVE));
this.rebornCDTickCount = this.getRebornCD();
this.disappeared = true;
this.disappeared.set(true);
}
@Override
public void reborn() {
if (this.disappeared) {
this.disappeared = false;
if (this.disappeared.get()) {
this.disappeared.set(false);
this.getPosition().set(this.getRebornPos());
this.getScene().broadcastPacket(new PacketSceneEntityAppearNotify(this));
}
@@ -74,6 +76,6 @@ public class EntityHomeAnimal extends EntityMonster implements Rebornable {
@Override
public boolean isInCD() {
return this.disappeared;
return this.disappeared.get();
}
}

View File

@@ -12,15 +12,17 @@ import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.net.proto.HomeAvatarTalkFinishInfoOuterClass;
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;
import java.util.stream.Stream;
@Entity(value = "homes", useDiscriminator = false)
@Data
@@ -55,6 +57,7 @@ public class GameHome {
Set<Integer> unlockedHomeBgmList;
int enterHomeOption;
Map<Integer, Set<Integer>> finishedTalkIdMap;
Set<Integer> finishedRewardEventIdSet;
public static GameHome getByUid(Integer uid) {
var home = DatabaseHelper.getHomeByUid(uid);
@@ -62,7 +65,9 @@ public class GameHome {
home = GameHome.create(uid);
}
home.reassignIfNull();
home.fixMainHouseIfOld();
home.syncHomeAvatarCostume();
return home;
}
@@ -79,9 +84,19 @@ public class GameHome {
.mainHouseMap(new ConcurrentHashMap<>())
.unlockedHomeBgmList(new HashSet<>())
.finishedTalkIdMap(new HashMap<>())
.finishedRewardEventIdSet(new HashSet<>())
.build();
}
// avoid NPE caused by database remover.
private void reassignIfNull() {
this.getSceneMap().values().stream()
.map(HomeSceneItem::getBlockItems)
.map(Map::values)
.flatMap(Collection::stream)
.forEach(HomeBlockItem::reassignIfNull);
}
// Data fixer.
private void fixMainHouseIfOld() {
if (this.getMainHouseMap() == null) {
@@ -97,6 +112,18 @@ public class GameHome {
this.save();
}
private void syncHomeAvatarCostume() {
Stream.of(this.sceneMap, this.mainHouseMap)
.map(ConcurrentHashMap::values)
.flatMap(Collection::stream)
.map(HomeSceneItem::getBlockItems)
.map(Map::values)
.flatMap(Collection::stream)
.map(HomeBlockItem::getDeployNPCList)
.flatMap(Collection::stream)
.forEach(npc -> npc.setCostumeId(this.getPlayer().getCostumeFrom(npc.getAvatarId())));
}
public void save() {
DatabaseHelper.saveHome(this);
}
@@ -113,12 +140,12 @@ public class GameHome {
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);
}
return HomeSceneItem.parseFrom(defaultItem, sceneId);
});
}
@@ -149,6 +176,8 @@ public class GameHome {
this.getMainHouseMap().remove(outdoor); // delete main house in current scene.
this.getMainHouseItem(outdoor); // put new main house with default arrangement.
this.save();
this.getPlayer().getCurHomeWorld().getModuleManager().refreshMainHouse();
}
public void onOwnerLogin(Player player) {
@@ -160,6 +189,8 @@ public class GameHome {
player.getSession().send(new PacketHomeMarkPointNotify(player));
player.getSession().send(new PacketHomeAvatarTalkFinishInfoNotify(player));
player.getSession().send(new PacketHomeAllUnlockedBgmIdListNotify(player));
player.getSession().send(new PacketHomeAvatarRewardEventNotify(player));
player.getSession().send(new PacketHomeAvatarAllFinishRewardNotify(player));
checkAccumulatedResources(player);
player.getSession().send(new PacketHomeResourceNotify(player));
}
@@ -226,6 +257,20 @@ public class GameHome {
.toList();
}
public boolean onClaimAvatarRewards(int eventId) {
if (this.finishedRewardEventIdSet == null) {
this.finishedRewardEventIdSet = new HashSet<>();
}
var success = this.finishedRewardEventIdSet.add(eventId);
this.save();
return success;
}
public boolean isRewardEventFinished(int eventId) {
return this.finishedRewardEventIdSet != null && this.finishedRewardEventIdSet.contains(eventId);
}
public boolean addUnlockedHomeBgm(int homeBgmId) {
if (!getUnlockedHomeBgmList().add(homeBgmId)) return false;
@@ -404,7 +449,7 @@ public class GameHome {
newCoin = storedCoin + owedCoin;
}
// Ensure max is not exceeded
storedCoin = (maxCoin >= newCoin) ? newCoin : maxCoin;
storedCoin = Math.min(maxCoin, newCoin);
}
// Update fetter exp
@@ -416,7 +461,7 @@ public class GameHome {
newFetter = storedFetterExp + owedFetter;
}
// Ensure max is not exceeded
storedFetterExp = (maxFetter >= newFetter) ? newFetter : maxFetter;
storedFetterExp = Math.min(maxFetter, newFetter);
}
save();

View File

@@ -2,6 +2,8 @@ package emu.grasscutter.game.home;
import dev.morphia.annotations.*;
import emu.grasscutter.data.binout.HomeworldDefaultSaveData;
import emu.grasscutter.game.home.suite.HomeSuiteItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.HomeBlockArrangementInfoOuterClass.HomeBlockArrangementInfo;
import java.util.*;
import java.util.stream.Stream;
@@ -19,6 +21,7 @@ public class HomeBlockItem {
List<HomeFurnitureItem> persistentFurnitureList;
List<HomeAnimalItem> deployAnimalList;
List<HomeNPCItem> deployNPCList;
List<HomeSuiteItem> suiteList;
public static HomeBlockItem parseFrom(HomeworldDefaultSaveData.HomeBlock homeBlock) {
// create from default setting
@@ -37,10 +40,11 @@ public class HomeBlockItem {
.toList())
.deployAnimalList(List.of())
.deployNPCList(List.of())
.suiteList(List.of())
.build();
}
public void update(HomeBlockArrangementInfo homeBlockArrangementInfo) {
public void update(HomeBlockArrangementInfo homeBlockArrangementInfo, Player owner) {
this.blockId = homeBlockArrangementInfo.getBlockId();
this.deployFurnitureList =
@@ -60,8 +64,12 @@ public class HomeBlockItem {
this.deployNPCList =
homeBlockArrangementInfo.getDeployNpcListList().stream()
.map(HomeNPCItem::parseFrom)
.map(homeNpcData -> HomeNPCItem.parseFrom(homeNpcData, owner))
.toList();
this.suiteList = homeBlockArrangementInfo.getFurnitureSuiteListList().stream()
.map(HomeSuiteItem::parseFrom)
.toList();
}
public int calComfort() {
@@ -81,15 +89,16 @@ public class HomeBlockItem {
this.persistentFurnitureList.forEach(f -> proto.addPersistentFurnitureList(f.toProto()));
this.deployAnimalList.forEach(f -> proto.addDeployAnimalList(f.toProto()));
this.deployNPCList.forEach(f -> proto.addDeployNpcList(f.toProto()));
this.suiteList.forEach(f -> proto.addFurnitureSuiteList(f.toProto()));
return proto.build();
}
// TODO add more types (farm field and suite)
// TODO implement farm field.
public List<? extends HomeMarkPointProtoFactory> getMarkPointProtoFactories() {
this.reassignIfNull();
return Stream.of(this.deployFurnitureList, this.persistentFurnitureList, this.deployNPCList)
return Stream.of(this.deployFurnitureList, this.persistentFurnitureList, this.deployNPCList, this.suiteList)
.flatMap(Collection::stream)
.toList();
}
@@ -107,5 +116,8 @@ public class HomeBlockItem {
if (this.deployNPCList == null) {
this.deployNPCList = List.of();
}
if (this.suiteList == null) {
this.suiteList = List.of();
}
}
}

View File

@@ -0,0 +1,204 @@
package emu.grasscutter.game.home;
import com.github.davidmoten.guavamini.Lists;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.home.suite.event.HomeAvatarRewardEvent;
import emu.grasscutter.game.home.suite.event.HomeAvatarSummonEvent;
import emu.grasscutter.game.home.suite.event.SuiteEventType;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.HomeAvatarRewardEventNotifyOuterClass;
import emu.grasscutter.net.proto.HomeAvatarSummonAllEventNotifyOuterClass;
import emu.grasscutter.net.proto.RetcodeOuterClass;
import emu.grasscutter.server.packet.send.PacketHomeAvatarSummonAllEventNotify;
import emu.grasscutter.utils.Either;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
import java.util.*;
import java.util.stream.Stream;
@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
public class HomeModuleManager {
final Player homeOwner;
final HomeWorld homeWorld;
final GameHome home;
final int moduleId;
final HomeScene outdoor;
HomeScene indoor;
final List<HomeAvatarRewardEvent> rewardEvents;
final List<HomeAvatarSummonEvent> summonEvents;
public HomeModuleManager(HomeWorld homeWorld) {
this.homeOwner = homeWorld.getHost();
this.homeWorld = homeWorld;
this.home = homeWorld.getHome();
this.moduleId = this.homeOwner.getCurrentRealmId();
this.outdoor = homeWorld.getSceneById(homeWorld.getActiveOutdoorSceneId());
this.refreshMainHouse();
this.rewardEvents = Lists.newArrayList();
this.summonEvents = Collections.synchronizedList(Lists.newArrayList());
}
public void tick() {
this.outdoor.onTick();
this.indoor.onTick();
this.summonEvents.removeIf(HomeAvatarSummonEvent::isTimeOver);
}
public void refreshMainHouse() {
this.indoor = this.homeWorld.getSceneById(this.homeWorld.getActiveIndoorSceneId());
}
public void onUpdateArrangement() {
this.fireAllAvatarRewardEvent();
this.cancelSummonEventIfAvatarLeave();
}
private void fireAllAvatarRewardEvent() {
this.rewardEvents.clear();
var allBlockItems = Stream.of(this.getOutdoorSceneItem(), this.getIndoorSceneItem())
.map(HomeSceneItem::getBlockItems)
.map(Map::values)
.flatMap(Collection::stream)
.toList();
var suites = allBlockItems.stream()
.map(HomeBlockItem::getSuiteList)
.flatMap(Collection::stream)
.distinct()
.toList();
allBlockItems.stream()
.map(HomeBlockItem::getDeployNPCList)
.flatMap(Collection::stream)
.forEach(avatar -> {
suites.forEach(suite -> {
var data = SuiteEventType.HOME_AVATAR_REWARD_EVENT.getEventDataFrom(avatar.getAvatarId(), suite.getSuiteId());
if (data == null || this.home.isRewardEventFinished(data.getId())) {
return;
}
this.rewardEvents.add(new HomeAvatarRewardEvent(homeOwner, data.getId(), data.getRewardID(), data.getAvatarID(), data.getSuiteId(), suite.getGuid()));
});
});
if (this.summonEvents != null) {
var suiteIdList = this.rewardEvents.stream().map(HomeAvatarRewardEvent::getSuiteId).toList();
this.summonEvents.removeIf(event -> suiteIdList.contains(event.getSuiteId()));
}
}
private void cancelSummonEventIfAvatarLeave() {
var avatars = Stream.of(this.getOutdoorSceneItem(), this.getIndoorSceneItem())
.map(HomeSceneItem::getBlockItems)
.map(Map::values)
.flatMap(Collection::stream)
.map(HomeBlockItem::getDeployNPCList)
.flatMap(Collection::stream)
.map(HomeNPCItem::getAvatarId)
.toList();
this.summonEvents.removeIf(event -> !avatars.contains(event.getAvatarId()));
}
public Either<List<GameItem>, Integer> claimAvatarRewards(int eventId) {
if (this.rewardEvents.isEmpty()) {
return Either.right(RetcodeOuterClass.Retcode.RET_FAIL_VALUE);
}
var event = this.rewardEvents.remove(0);
if (event.getEventId() != eventId) {
return Either.right(RetcodeOuterClass.Retcode.RET_FAIL_VALUE);
}
if (!this.homeOwner.getHome().onClaimAvatarRewards(eventId)) {
return Either.right(RetcodeOuterClass.Retcode.RET_FAIL_VALUE);
}
return Either.left(event.giveRewards());
}
public Either<HomeAvatarSummonEvent, Integer> fireAvatarSummonEvent(Player owner, int avatarId, int guid, int suiteId) {
var targetSuite = ((HomeScene) owner.getScene()).getSceneItem().getBlockItems().values().stream()
.map(HomeBlockItem::getSuiteList)
.flatMap(Collection::stream)
.filter(suite -> suite.getGuid() == guid)
.findFirst()
.orElse(null);
if (this.isInRewardEvent(avatarId)) {
return Either.right(RetcodeOuterClass.Retcode.RET_DUPLICATE_AVATAR_VALUE);
}
if (this.rewardEvents.stream().anyMatch(event -> event.getGuid() == guid)) {
return Either.right(RetcodeOuterClass.Retcode.RET_HOME_FURNITURE_GUID_ERROR_VALUE);
}
this.summonEvents.removeIf(event -> event.getGuid() == guid || event.getAvatarId() == avatarId);
if (targetSuite == null) {
return Either.right(RetcodeOuterClass.Retcode.RET_HOME_CLIENT_PARAM_INVALID_VALUE);
}
var eventData = SuiteEventType.HOME_AVATAR_SUMMON_EVENT.getEventDataFrom(avatarId, suiteId);
if (eventData == null) {
return Either.right(RetcodeOuterClass.Retcode.RET_HOME_CLIENT_PARAM_INVALID_VALUE);
}
var event = new HomeAvatarSummonEvent(owner, eventData.getId(), eventData.getRewardID(), avatarId, suiteId, guid);
this.summonEvents.add(event);
owner.sendPacket(new PacketHomeAvatarSummonAllEventNotify(owner));
return Either.left(event);
}
public void onFinishSummonEvent(int eventId) {
this.summonEvents.removeIf(event -> event.getEventId() == eventId);
}
public HomeAvatarRewardEventNotifyOuterClass.HomeAvatarRewardEventNotify toRewardEventProto() {
var notify = HomeAvatarRewardEventNotifyOuterClass.HomeAvatarRewardEventNotify.newBuilder();
if (!this.rewardEvents.isEmpty()) {
notify.setRewardEvent(this.rewardEvents.get(0).toProto()).setIsEventTrigger(true);
notify.addAllPendingList(this.rewardEvents.subList(1, this.rewardEvents.size()).stream()
.map(HomeAvatarRewardEvent::toProto)
.toList());
}
return notify.build();
}
public HomeAvatarSummonAllEventNotifyOuterClass.HomeAvatarSummonAllEventNotify toSummonEventProto() {
return HomeAvatarSummonAllEventNotifyOuterClass.HomeAvatarSummonAllEventNotify.newBuilder()
.addAllSummonEventList(this.summonEvents.stream()
.map(HomeAvatarSummonEvent::toProto)
.toList())
.build();
}
public boolean isInRewardEvent(int avatarId) {
return this.rewardEvents.stream().anyMatch(e -> e.getAvatarId() == avatarId);
}
public HomeSceneItem getOutdoorSceneItem() {
return this.outdoor.getSceneItem();
}
public HomeSceneItem getIndoorSceneItem() {
return this.indoor.getSceneItem();
}
public void onSetModule() {
this.outdoor.addEntities(this.getOutdoorSceneItem().getAnimals(this.outdoor));
this.indoor.addEntities(this.getIndoorSceneItem().getAnimals(this.indoor));
this.fireAllAvatarRewardEvent();
}
public void onRemovedModule() {
this.outdoor.getEntities().clear();
this.indoor.getEntities().clear();
}
}

View File

@@ -2,6 +2,7 @@ package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.Position;
import emu.grasscutter.net.proto.HomeMarkPointFurnitureDataOuterClass;
import emu.grasscutter.net.proto.HomeMarkPointNPCDataOuterClass;
@@ -23,11 +24,12 @@ public class HomeNPCItem implements HomeMarkPointProtoFactory {
Position spawnRot;
int costumeId;
public static HomeNPCItem parseFrom(HomeNpcDataOuterClass.HomeNpcData homeNpcData) {
public static HomeNPCItem parseFrom(HomeNpcDataOuterClass.HomeNpcData homeNpcData, Player owner) {
return HomeNPCItem.of()
.avatarId(homeNpcData.getAvatarId())
.spawnPos(new Position(homeNpcData.getSpawnPos()))
.spawnRot(new Position(homeNpcData.getSpawnRot()))
.costumeId(owner.getCostumeFrom(homeNpcData.getAvatarId()))
.build();
}

View File

@@ -1,9 +1,12 @@
package emu.grasscutter.game.home;
import emu.grasscutter.data.excels.scene.SceneData;
import emu.grasscutter.game.entity.EntityHomeAnimal;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.entity.Rebornable;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.VisionTypeOuterClass;
import emu.grasscutter.server.packet.send.PacketSceneTimeNotify;
public class HomeScene extends Scene {
@@ -40,10 +43,31 @@ public class HomeScene extends Scene {
.forEach(gameEntity -> gameEntity.onTick(this.getSceneTimeSeconds()));
this.finishLoading();
this.checkPlayerRespawn();
if (this.tickCount++ % 10 == 0) this.broadcastPacket(new PacketSceneTimeNotify(this));
}
public void onEnterEditModeFinish() {
this.removeEntities(
this.getEntities().values().stream()
.filter(gameEntity -> gameEntity instanceof EntityHomeAnimal)
.toList(),
VisionTypeOuterClass.VisionType.VISION_TYPE_REMOVE);
}
public void onLeaveEditMode() {
this.addEntities(this.getSceneItem().getAnimals(this));
}
@Override
public void killEntity(GameEntity target, int attackerId) {
if (target instanceof Rebornable rebornable) {
rebornable.onAiKillSelf(); // Teapot animals will not die. They will revive!
return;
}
super.killEntity(target, attackerId);
}
@Override
public void checkNpcGroup() {}

View File

@@ -6,17 +6,20 @@ import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.HomeworldDefaultSaveData;
import emu.grasscutter.game.entity.EntityHomeAnimal;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.Position;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.HomeSceneArrangementInfoOuterClass.HomeSceneArrangementInfo;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import lombok.*;
import lombok.experimental.FieldDefaults;
@Entity
@Data
@@ -49,14 +52,14 @@ public class HomeSceneItem {
.build();
}
public void update(HomeSceneArrangementInfo arrangementInfo) {
public void update(HomeSceneArrangementInfo arrangementInfo, Player owner) {
for (var blockItem : arrangementInfo.getBlockArrangementInfoListList()) {
var block = this.blockItems.get(blockItem.getBlockId());
if (block == null) {
Grasscutter.getLogger().warn("Could not found the Home Block {}", blockItem.getBlockId());
continue;
}
block.update(blockItem);
block.update(blockItem, owner);
this.blockItems.put(blockItem.getBlockId(), block);
}
@@ -84,17 +87,13 @@ public class HomeSceneItem {
}
@Nullable public Position getTeleportPointPos(int guid) {
var pos = new AtomicReference<Position>();
this.getBlockItems().values().stream()
return this.getBlockItems().values().stream()
.map(HomeBlockItem::getDeployFurnitureList)
.flatMap(Collection::stream)
.filter(homeFurnitureItem -> homeFurnitureItem.getGuid() == guid)
.map(HomeFurnitureItem::getSpawnPos)
.findFirst()
.ifPresent(pos::set);
return pos.get();
.orElse(null);
}
public List<EntityHomeAnimal> getAnimals(Scene scene) {

View File

@@ -8,33 +8,57 @@ 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.*;
import java.util.List;
import emu.grasscutter.server.packet.send.PacketDelTeamEntityNotify;
import emu.grasscutter.server.packet.send.PacketPlayerChatNotify;
import emu.grasscutter.server.packet.send.PacketPlayerGameTimeNotify;
import lombok.Getter;
import java.util.List;
import java.util.function.Consumer;
public class HomeWorld extends World {
@Getter private final GameHome home;
@Getter private HomeModuleManager moduleManager;
public HomeWorld(GameServer server, Player owner) {
super(server, owner);
this.home = owner.isOnline() ? owner.getHome() : GameHome.getByUid(owner.getUid());
server.registerHomeWorld(this);
this.refreshModuleManager();
}
@Override
public void registerScene(Scene scene) {
this.addAnimalsToScene((HomeScene) scene);
super.registerScene(scene);
public boolean onTick() {
this.moduleManager.tick();
if (this.getTickCount() % 10 == 0) {
this.getPlayers().forEach(p -> p.sendPacket(new PacketPlayerGameTimeNotify(p)));
}
if (this.isInHome(this.getHost()) && this.getTickCount() % 60 == 0) {
this.getHost().updatePlayerGameTime(this.getCurrentWorldTime());
}
this.tickCount++;
return false;
}
@Override
public void deregisterScene(Scene scene) {
super.deregisterScene(scene);
public void refreshModuleManager() {
if (this.moduleManager != null) {
this.moduleManager.onRemovedModule();
}
this.moduleManager = new HomeModuleManager(this);
this.moduleManager.onSetModule();
}
private void addAnimalsToScene(HomeScene scene) {
scene.getSceneItem().getAnimals(scene).forEach(scene::addEntity);
public int getActiveOutdoorSceneId() {
return this.getHost().getCurrentRealmId() + 2000;
}
public int getActiveIndoorSceneId() {
return this.getSceneById(this.getActiveOutdoorSceneId()).getSceneItem().getRoomSceneId();
}
@Override
@@ -188,6 +212,12 @@ public class HomeWorld extends World {
return this.getPlayers().contains(player);
}
public void ifHost(Player hostOrGuest, Consumer<Player> ifHost) {
if (this.getHost().equals(hostOrGuest)) {
ifHost.accept(hostOrGuest);
}
}
public void sendPacketToHostIfOnline(BasePacket basePacket) {
if (this.getHost().isOnline()) {
this.getHost().sendPacket(basePacket);

View File

@@ -5,10 +5,7 @@ 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.net.proto.*;
import emu.grasscutter.server.event.player.PlayerEnterHomeEvent;
import emu.grasscutter.server.event.player.PlayerLeaveHomeEvent;
import emu.grasscutter.server.event.player.PlayerTeleportEvent;
@@ -215,6 +212,7 @@ public class HomeWorldMPSystem extends BaseGameSystem {
player.setCurHomeWorld(myHome);
myHome.getHome().onOwnerLogin(player);
player.sendPacket(new PacketPlayerQuitFromHomeNotify(PlayerQuitFromHomeNotifyOuterClass.PlayerQuitFromHomeNotify.QuitReason.BACK_TO_MY_WORLD));
player.sendPacket(
new PacketPlayerEnterSceneNotify(
player,
@@ -263,6 +261,7 @@ public class HomeWorldMPSystem extends BaseGameSystem {
victim.setCurHomeWorld(myHome);
myHome.getHome().onOwnerLogin(victim);
victim.sendPacket(new PacketPlayerQuitFromHomeNotify(PlayerQuitFromHomeNotifyOuterClass.PlayerQuitFromHomeNotify.QuitReason.KICK_BY_HOST));
victim.sendPacket(
new PacketPlayerEnterSceneNotify(
victim,

View File

@@ -0,0 +1,82 @@
package emu.grasscutter.game.home.suite;
import dev.morphia.annotations.Entity;
import emu.grasscutter.game.home.HomeMarkPointProtoFactory;
import emu.grasscutter.game.home.SpecialFurnitureType;
import emu.grasscutter.game.world.Position;
import emu.grasscutter.net.proto.HomeFurnitureSuiteDataOuterClass;
import emu.grasscutter.net.proto.HomeMarkPointFurnitureDataOuterClass;
import emu.grasscutter.net.proto.HomeMarkPointSuiteDataOuterClass;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Objects;
@Entity
@Builder(builderMethodName = "of")
@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
public class HomeSuiteItem implements HomeMarkPointProtoFactory {
public static final int SUITE_FURNITURE_ID = 377101;
int guid;
int suiteId;
Position pos;
List<Integer> includedFurnitureIndexList;
boolean isAllowSummon;
public static HomeSuiteItem parseFrom(HomeFurnitureSuiteDataOuterClass.HomeFurnitureSuiteData data) {
return HomeSuiteItem.of()
.guid(data.getGuid())
.suiteId(data.getSuiteId())
.pos(new Position(data.getSpawnPos()))
.includedFurnitureIndexList(data.getIncludedFurnitureIndexListList())
.isAllowSummon(data.getIsAllowSummon())
.build();
}
public HomeFurnitureSuiteDataOuterClass.HomeFurnitureSuiteData toProto() {
return HomeFurnitureSuiteDataOuterClass.HomeFurnitureSuiteData.newBuilder()
.setSuiteId(this.suiteId)
.setGuid(this.guid)
.setIsAllowSummon(this.isAllowSummon)
.addAllIncludedFurnitureIndexList(this.includedFurnitureIndexList)
.setSpawnPos(this.pos.toProto())
.build();
}
@Nullable
@Override
public HomeMarkPointFurnitureDataOuterClass.HomeMarkPointFurnitureData toMarkPointProto() {
return HomeMarkPointFurnitureDataOuterClass.HomeMarkPointFurnitureData.newBuilder()
.setFurnitureId(SUITE_FURNITURE_ID)
.setPos(this.pos.toProto())
.setFurnitureType(this.getType().getValue())
.setGuid(this.guid)
.setSuiteData(HomeMarkPointSuiteDataOuterClass.HomeMarkPointSuiteData.newBuilder()
.setSuiteId(this.suiteId)
.build())
.build();
}
@Override
public SpecialFurnitureType getType() {
return SpecialFurnitureType.FurnitureSuite;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HomeSuiteItem that = (HomeSuiteItem) o;
return suiteId == that.suiteId;
}
@Override
public int hashCode() {
return Objects.hash(suiteId);
}
}

View File

@@ -0,0 +1,54 @@
package emu.grasscutter.game.home.suite.event;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.utils.Utils;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
import java.util.List;
import java.util.Objects;
@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
public abstract class HomeAvatarEvent {
final Player homeOwner;
final int eventId;
final int rewardId;
final int avatarId;
final int suiteId;
final int guid;
final int randomPos;
public HomeAvatarEvent(Player homeOwner, int eventId, int rewardId, int avatarId, int suiteId, int guid) {
this.homeOwner = homeOwner;
this.eventId = eventId;
this.rewardId = rewardId;
this.avatarId = avatarId;
this.suiteId = suiteId;
this.guid = guid;
this.randomPos = this.generateRandomPos();
}
public int generateRandomPos() {
return Utils.randomRange(1, 97);
}
public List<GameItem> giveRewards() {
return List.of();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HomeAvatarEvent that = (HomeAvatarEvent) o;
return eventId == that.eventId;
}
@Override
public int hashCode() {
return Objects.hash(eventId);
}
}

View File

@@ -0,0 +1,37 @@
package emu.grasscutter.game.home.suite.event;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.HomeAvatarRewardEventInfoOuterClass;
import java.util.List;
public class HomeAvatarRewardEvent extends HomeAvatarEvent {
public HomeAvatarRewardEvent(Player homeOwner, int eventId, int rewardId, int avatarId, int suiteId, int guid) {
super(homeOwner, eventId, rewardId, avatarId, suiteId, guid);
}
public HomeAvatarRewardEventInfoOuterClass.HomeAvatarRewardEventInfo toProto() {
return HomeAvatarRewardEventInfoOuterClass.HomeAvatarRewardEventInfo.newBuilder()
.setAvatarId(this.getAvatarId())
.setEventId(this.getEventId())
.setGuid(this.getGuid())
.setSuiteId(this.getSuiteId())
.setRandomPosition(this.getRandomPos())
.build();
}
@Override
public List<GameItem> giveRewards() {
var data = GameData.getRewardDataMap().get(this.getRewardId());
if (data == null) {
return List.of();
}
var rewards = data.getRewardItemList().stream().map(GameItem::new).toList();
this.getHomeOwner().getInventory().addItems(rewards, ActionReason.HomeAvatarEventReward);
return rewards;
}
}

View File

@@ -0,0 +1,36 @@
package emu.grasscutter.game.home.suite.event;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.HomeAvatarSummonEventInfoOuterClass;
import emu.grasscutter.utils.Utils;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
public class HomeAvatarSummonEvent extends HomeAvatarEvent {
public static final int TIME_LIMIT_SECS = 240;
final int eventOverTime;
public HomeAvatarSummonEvent(Player homeOwner, int eventId, int rewardId, int avatarId, int suiteId, int guid) {
super(homeOwner, eventId, rewardId, avatarId, suiteId, guid);
this.eventOverTime = Utils.getCurrentSeconds() + TIME_LIMIT_SECS;
}
public HomeAvatarSummonEventInfoOuterClass.HomeAvatarSummonEventInfo toProto() {
return HomeAvatarSummonEventInfoOuterClass.HomeAvatarSummonEventInfo.newBuilder()
.setAvatarId(this.getAvatarId())
.setEventId(this.getEventId())
.setGuid(this.getGuid())
.setSuitId(this.getSuiteId())
.setRandomPosition(this.getRandomPos())
.setEventOverTime(this.eventOverTime)
.build();
}
public boolean isTimeOver() {
return Utils.getCurrentSeconds() > this.eventOverTime;
}
}

View File

@@ -0,0 +1,19 @@
package emu.grasscutter.game.home.suite.event;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.HomeWorldEventData;
import javax.annotation.Nullable;
public enum SuiteEventType {
HOME_AVATAR_REWARD_EVENT,
HOME_AVATAR_SUMMON_EVENT;
@Nullable
public HomeWorldEventData getEventDataFrom(int avatarId, int suiteId) {
return GameData.getHomeWorldEventDataMap().values().stream()
.filter(data -> data.getEventType() == this && data.getAvatarID() == avatarId && data.getSuiteId() == suiteId)
.findFirst()
.orElse(null);
}
}

View File

@@ -950,6 +950,13 @@ public class Player implements PlayerHook, FieldFetch {
this.sendPacket(new PacketAvatarGainCostumeNotify(costumeId));
}
public int getCostumeFrom(int avatarId) {
var avatars = this.getAvatars();
avatars.loadFromDatabase();
var avatar = avatars.getAvatarById(avatarId);
return avatar == null ? 0 : avatar.getCostume();
}
public void addPersonalLine(int personalLineId) {
this.getPersonalLineList().add(personalLineId);
session.getPlayer().getQuestManager().queueEvent(QuestCond.QUEST_COND_PERSONAL_LINE_UNLOCK, personalLineId);

View File

@@ -177,7 +177,8 @@ public enum ActionReason {
ChannellerSlabLoopDungeonFirstPassReward(1090),
ChannellerSlabLoopDungeonScoreReward(1091),
HomeLimitedShopBuy(1092),
HomeCoinCollect(1093);
HomeCoinCollect(1093),
HomeAvatarEventReward(1100);
private static final Int2ObjectMap<ActionReason> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, ActionReason> stringMap = new HashMap<>();

View File

@@ -39,7 +39,7 @@ public class World implements Iterable<Player> {
@Getter private boolean timeLocked;
private long lastUpdateTime;
@Getter private int tickCount = 0;
@Getter protected int tickCount = 0;
@Getter private boolean isPaused = false;
@Getter private long currentWorldTime;