feat: implement home animals (#2329)

This commit is contained in:
hamusuke 2023-09-02 14:46:58 +09:00 committed by GitHub
parent c1045103ed
commit 97138d8c84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 354 additions and 34 deletions

View File

@ -4,29 +4,51 @@ import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.binout.*;
import emu.grasscutter.data.binout.config.*;
import emu.grasscutter.data.binout.routes.Route;
import emu.grasscutter.data.custom.*;
import emu.grasscutter.data.custom.TrialAvatarActivityCustomData;
import emu.grasscutter.data.custom.TrialAvatarCustomData;
import emu.grasscutter.data.excels.*;
import emu.grasscutter.data.excels.achievement.*;
import emu.grasscutter.data.excels.activity.*;
import emu.grasscutter.data.excels.achievement.AchievementData;
import emu.grasscutter.data.excels.achievement.AchievementGoalData;
import emu.grasscutter.data.excels.activity.ActivityCondExcelConfigData;
import emu.grasscutter.data.excels.activity.ActivityData;
import emu.grasscutter.data.excels.activity.ActivityShopData;
import emu.grasscutter.data.excels.activity.ActivityWatcherData;
import emu.grasscutter.data.excels.avatar.*;
import emu.grasscutter.data.excels.codex.*;
import emu.grasscutter.data.excels.dungeon.*;
import emu.grasscutter.data.excels.giving.*;
import emu.grasscutter.data.excels.monster.*;
import emu.grasscutter.data.excels.quest.*;
import emu.grasscutter.data.excels.reliquary.*;
import emu.grasscutter.data.excels.giving.GivingData;
import emu.grasscutter.data.excels.giving.GivingGroupData;
import emu.grasscutter.data.excels.monster.MonsterCurveData;
import emu.grasscutter.data.excels.monster.MonsterData;
import emu.grasscutter.data.excels.monster.MonsterDescribeData;
import emu.grasscutter.data.excels.monster.MonsterSpecialNameData;
import emu.grasscutter.data.excels.quest.QuestData;
import emu.grasscutter.data.excels.quest.QuestGlobalVarData;
import emu.grasscutter.data.excels.reliquary.ReliquaryAffixData;
import emu.grasscutter.data.excels.reliquary.ReliquaryLevelData;
import emu.grasscutter.data.excels.reliquary.ReliquaryMainPropData;
import emu.grasscutter.data.excels.reliquary.ReliquarySetData;
import emu.grasscutter.data.excels.scene.*;
import emu.grasscutter.data.excels.tower.*;
import emu.grasscutter.data.excels.tower.TowerFloorData;
import emu.grasscutter.data.excels.tower.TowerLevelData;
import emu.grasscutter.data.excels.tower.TowerScheduleData;
import emu.grasscutter.data.excels.trial.*;
import emu.grasscutter.data.excels.weapon.*;
import emu.grasscutter.data.excels.world.*;
import emu.grasscutter.data.excels.weapon.WeaponCurveData;
import emu.grasscutter.data.excels.weapon.WeaponLevelData;
import emu.grasscutter.data.excels.weapon.WeaponPromoteData;
import emu.grasscutter.data.excels.world.WeatherData;
import emu.grasscutter.data.excels.world.WorldAreaData;
import emu.grasscutter.data.excels.world.WorldLevelData;
import emu.grasscutter.data.server.*;
import emu.grasscutter.game.dungeons.DungeonDropEntry;
import emu.grasscutter.game.quest.*;
import emu.grasscutter.game.quest.QuestEncryptionKey;
import emu.grasscutter.game.quest.RewindData;
import emu.grasscutter.game.quest.TeleportData;
import emu.grasscutter.game.quest.enums.QuestCond;
import emu.grasscutter.game.world.GroupReplacementData;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.*;
import java.lang.reflect.Field;
import java.util.*;
import javax.annotation.Nullable;
@ -257,6 +279,10 @@ public final class GameData {
private static final Int2ObjectMap<GuideTriggerData> guideTriggerDataMap =
new Int2ObjectOpenHashMap<>();
@Getter
private static final Int2ObjectMap<HomeWorldAnimalData> homeWorldAnimalDataMap =
new Int2ObjectOpenHashMap<>();
@Getter
private static final Int2ObjectMap<HomeWorldBgmData> homeWorldBgmDataMap =
new Int2ObjectOpenHashMap<>();

View File

@ -0,0 +1,22 @@
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
@ResourceType(name = "HomeworldAnimalExcelConfigData.json")
@FieldDefaults(level = AccessLevel.PRIVATE)
@Getter
public class HomeWorldAnimalData extends GameResource {
int furnitureID;
int monsterID;
int isRebirth;
int rebirthCD;
@Override
public int getId() {
return this.furnitureID;
}
}

View File

@ -0,0 +1,80 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.HomeWorldAnimalData;
import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.game.world.Position;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.VisionTypeOuterClass;
import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify;
import emu.grasscutter.server.packet.send.PacketSceneEntityDisappearNotify;
import lombok.Getter;
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;
public EntityHomeAnimal(Scene scene, HomeWorldAnimalData data, Position pos) {
super(scene, GameData.getMonsterDataMap().get(data.getMonsterID()), pos, 1);
this.rebornPos = pos.clone();
this.rebirth = data.getIsRebirth();
this.rebirthCD = data.getRebirthCD();
}
@Override
public void damage(float amount, int killerId, ElementType attackType) {
}
@Override
public void onTick(int sceneTime) {
super.onTick(sceneTime);
if (this.isInCD()) {
this.rebornCDTickCount--;
if (this.rebornCDTickCount <= 0) {
this.reborn();
}
}
}
@Override
public void onCreate() {
}
@Override
public Position getRebornPos() {
return this.rebornPos;
}
@Override
public int getRebornCD() {
return this.rebirthCD;
}
@Override
public void onAiKillSelf() {
this.getScene().broadcastPacket(new PacketSceneEntityDisappearNotify(this, VisionTypeOuterClass.VisionType.VISION_TYPE_REMOVE));
this.rebornCDTickCount = this.getRebornCD();
this.disappeared = true;
}
@Override
public void reborn() {
if (this.disappeared) {
this.disappeared = false;
this.getPosition().set(this.getRebornPos());
this.getScene().broadcastPacket(new PacketSceneEntityAppearNotify(this));
}
}
@Override
public boolean isInCD() {
return this.disappeared;
}
}

View File

@ -0,0 +1,15 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.game.world.Position;
public interface Rebornable {
Position getRebornPos();
int getRebornCD();
void onAiKillSelf();
void reborn();
boolean isInCD();
}

View File

@ -0,0 +1,61 @@
package emu.grasscutter.game.home;
import emu.grasscutter.data.excels.scene.SceneData;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.server.packet.send.PacketSceneTimeNotify;
public class HomeScene extends Scene {
public HomeScene(HomeWorld world, SceneData sceneData) {
super(world, sceneData);
this.setDontDestroyWhenEmpty(true);
}
@Override
public boolean isPaused() {
return false;
}
@Override
public HomeWorld getWorld() {
return (HomeWorld) super.getWorld();
}
public GameHome getHome() {
return this.getWorld().getHome();
}
public HomeSceneItem getSceneItem() {
return this.getHome().getHomeSceneItem(this.getId());
}
@Override
public void setPaused(boolean paused) {
}
@Override
public void onTick() {
this.getEntities().values().forEach(gameEntity -> gameEntity.onTick(this.getSceneTimeSeconds()));
this.finishLoading();
this.checkPlayerRespawn();
if (this.tickCount++ % 10 == 0) this.broadcastPacket(new PacketSceneTimeNotify(this));
}
@Override
public void checkNpcGroup() {
}
@Override
public void checkSpawns() {
}
@Override
public void addItemEntity(int itemId, int amount, GameEntity bornForm) {
}
@Override
public void loadNpcForPlayerEnter(Player player) {
}
}

View File

@ -1,10 +1,17 @@
package emu.grasscutter.game.home;
import dev.morphia.annotations.*;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
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.world.Position;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.HomeSceneArrangementInfoOuterClass.HomeSceneArrangementInfo;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.*;
@ -71,6 +78,19 @@ public class HomeSceneItem {
return mainHouse == null || mainHouse.getAsItem() == null;
}
public List<EntityHomeAnimal> getAnimals(Scene scene) {
return this.blockItems.values().stream()
.map(HomeBlockItem::getDeployAnimalList)
.flatMap(Collection::stream)
.filter(homeAnimalItem -> GameData.getHomeWorldAnimalDataMap().containsKey(homeAnimalItem.getFurnitureId()))
.map(homeAnimalItem -> {
return new EntityHomeAnimal(scene,
GameData.getHomeWorldAnimalDataMap().get(homeAnimalItem.getFurnitureId()),
homeAnimalItem.getSpawnPos());
})
.toList();
}
public int calComfort() {
return this.blockItems.values().stream().mapToInt(HomeBlockItem::calComfort).sum();
}

View File

@ -1,11 +1,14 @@
package emu.grasscutter.game.home;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.entity.EntityTeam;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.*;
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.*;
import java.util.List;
import lombok.Getter;
@ -20,6 +23,21 @@ public class HomeWorld extends World {
server.registerHomeWorld(this);
}
@Override
public void registerScene(Scene scene) {
this.addAnimalsToScene((HomeScene) scene);
super.registerScene(scene);
}
@Override
public void deregisterScene(Scene scene) {
super.deregisterScene(scene);
}
private void addAnimalsToScene(HomeScene scene) {
scene.getSceneItem().getAnimals(scene).forEach(scene::addEntity);
}
@Override
public synchronized void addPlayer(Player player) {
// Check if player already in
@ -117,6 +135,23 @@ public class HomeWorld extends World {
.build()));
}
@Override
public HomeScene getSceneById(int sceneId) {
var scene = this.getScenes().get(sceneId);
if (scene instanceof HomeScene homeScene) {
return homeScene;
}
var sceneData = GameData.getSceneDataMap().get(sceneId);
if (sceneData != null) {
scene = new HomeScene(this, sceneData);
this.registerScene(scene);
return (HomeScene) scene;
}
return null;
}
@Override
public int getNextPeerId() {
return this.getPlayers().size() + 1;

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,14 +12,16 @@ 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;
@ -26,22 +29,27 @@ import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.proto.*;
import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult;
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 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.*;
public final class Scene {
public class Scene {
@Getter private final World world;
@Getter private final SceneData sceneData;
@Getter private final List<Player> players;
@ -66,7 +74,7 @@ public final class Scene {
@Getter @Setter private int killedMonsterCount;
private Set<SceneNpcBornEntry> npcBornEntrySet;
@Getter private boolean finishedLoading = false;
@Getter private int tickCount = 0;
@Getter protected int tickCount = 0;
@Getter private boolean isPaused = false;
private final List<Runnable> afterLoadedCallbacks = new ArrayList<>();
@ -456,7 +464,10 @@ public final class Scene {
public void showOtherEntities(Player player) {
GameEntity currentEntity = player.getTeamManager().getCurrentAvatarEntity();
List<GameEntity> entities =
this.getEntities().values().stream().filter(entity -> entity != currentEntity).toList();
this.getEntities().values().stream()
.filter(entity -> entity != currentEntity)
.filter(gameEntity -> !(gameEntity instanceof Rebornable rebornable) || !rebornable.isInCD())
.toList();
player.sendPacket(new PacketSceneEntityAppearNotify(entities, VisionType.VISION_TYPE_MEET));
}
@ -583,7 +594,7 @@ public final class Scene {
}
/** Validates a player's current position. Teleports the player if the player is out of bounds. */
private void checkPlayerRespawn() {
protected void checkPlayerRespawn() {
if (this.getScriptManager().getConfig() == null) return;
var diePos = this.getScriptManager().getConfig().die_y;

View File

@ -8,32 +8,41 @@ import emu.grasscutter.Grasscutter.ServerRunMode;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.battlepass.BattlePassSystem;
import emu.grasscutter.game.chat.*;
import emu.grasscutter.game.chat.ChatSystem;
import emu.grasscutter.game.chat.ChatSystemHandler;
import emu.grasscutter.game.combine.CombineManger;
import emu.grasscutter.game.drop.*;
import emu.grasscutter.game.drop.DropSystem;
import emu.grasscutter.game.drop.DropSystemLegacy;
import emu.grasscutter.game.dungeons.DungeonSystem;
import emu.grasscutter.game.expedition.ExpeditionSystem;
import emu.grasscutter.game.gacha.GachaSystem;
import emu.grasscutter.game.home.*;
import emu.grasscutter.game.managers.cooking.*;
import emu.grasscutter.game.home.HomeWorld;
import emu.grasscutter.game.home.HomeWorldMPSystem;
import emu.grasscutter.game.managers.cooking.CookingCompoundManager;
import emu.grasscutter.game.managers.cooking.CookingManager;
import emu.grasscutter.game.managers.energy.EnergyManager;
import emu.grasscutter.game.managers.stamina.StaminaManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.QuestSystem;
import emu.grasscutter.game.shop.ShopSystem;
import emu.grasscutter.game.systems.*;
import emu.grasscutter.game.systems.AnnouncementSystem;
import emu.grasscutter.game.systems.InventorySystem;
import emu.grasscutter.game.systems.MultiplayerSystem;
import emu.grasscutter.game.talk.TalkSystem;
import emu.grasscutter.game.tower.TowerSystem;
import emu.grasscutter.game.world.*;
import emu.grasscutter.game.world.World;
import emu.grasscutter.game.world.WorldDataSystem;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail;
import emu.grasscutter.server.dispatch.DispatchClient;
import emu.grasscutter.server.event.game.ServerTickEvent;
import emu.grasscutter.server.event.internal.*;
import emu.grasscutter.server.event.internal.ServerStartEvent;
import emu.grasscutter.server.event.internal.ServerStopEvent;
import emu.grasscutter.server.event.types.ServerEvent;
import emu.grasscutter.server.scheduler.ServerTaskScheduler;
import emu.grasscutter.task.TaskMap;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.*;
import java.net.*;
import java.time.*;
@ -283,6 +292,9 @@ public final class GameServer extends KcpServer implements Iterable<Player> {
// Tick worlds.
this.worlds.removeIf(World::onTick);
// Tick Home Worlds (Not remove, HomeWorld is constant).
this.homeWorlds.values().forEach(HomeWorld::onTick);
// Tick players.
this.players.values().forEach(Player::onTick);

View File

@ -0,0 +1,19 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.game.entity.Rebornable;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.EntityAiKillSelfNotifyOuterClass;
import emu.grasscutter.server.game.GameSession;
@Opcodes(PacketOpcodes.EntityAiKillSelfNotify)
public class HandlerEntityAiKillSelfNotify extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
var entityId = EntityAiKillSelfNotifyOuterClass.EntityAiKillSelfNotify.parseFrom(payload).getEntityId();
if (session.getPlayer().getScene().getEntityById(entityId) instanceof Rebornable rebornable) {
rebornable.onAiKillSelf();
}
}
}

View File

@ -1,9 +1,15 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.net.packet.*;
import emu.grasscutter.net.proto.*;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.HomeChangeEditModeReqOuterClass;
import emu.grasscutter.net.proto.RetcodeOuterClass;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.server.packet.send.PacketHomeBasicInfoNotify;
import emu.grasscutter.server.packet.send.PacketHomeChangeEditModeRsp;
import emu.grasscutter.server.packet.send.PacketHomeComfortInfoNotify;
import emu.grasscutter.server.packet.send.PacketHomePreChangeEditModeNotify;
@Opcodes(PacketOpcodes.HomeChangeEditModeReq)
public class HandlerHomeChangeEditModeReq extends PacketHandler {
@ -24,6 +30,11 @@ public class HandlerHomeChangeEditModeReq extends PacketHandler {
session.send(new PacketHomeBasicInfoNotify(session.getPlayer(), req.getIsEnterEditMode()));
session.send(new PacketHomeComfortInfoNotify(session.getPlayer()));
if (!req.getIsEnterEditMode()) {
var scene = session.getPlayer().getScene();
scene.addEntities(session.getPlayer().getCurHomeWorld().getHome().getHomeSceneItem(scene.getId()).getAnimals(scene));
}
session.send(new PacketHomeChangeEditModeRsp(req.getIsEnterEditMode()));
}
}

View File

@ -1,6 +1,10 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.net.packet.*;
import emu.grasscutter.game.entity.EntityHomeAnimal;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.VisionTypeOuterClass;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketHomeEnterEditModeFinishRsp;
@ -12,6 +16,10 @@ public class HandlerHomeEnterEditModeFinishReq extends PacketHandler {
/*
* This packet is about the edit mode
*/
var scene = session.getPlayer().getScene();
scene.removeEntities(scene.getEntities().values().stream().filter(gameEntity -> gameEntity instanceof EntityHomeAnimal).toList(), VisionTypeOuterClass.VisionType.VISION_TYPE_REMOVE);
session.send(new PacketHomeEnterEditModeFinishRsp());
}
}